class Gap(_TaxonomyModel): question = models.ForeignKey(Question, related_name="gaps") on = models.BooleanField() class Meta(_TaxonomyModel.Meta): unique_together = ('on', 'question') def __str__(self): return "Gap for Q %s" % self.question
class Gap(_TaxonomyModel): question = models.ForeignKey( Question, on_delete=models.CASCADE, related_name="gaps" ) on = models.BooleanField() class Meta(_TaxonomyModel.Meta): unique_together = ("on", "question") def __str__(self): return "Gap for Q %s" % self.question
class Answer(models.Model): assessment = models.ForeignKey(Assessment) question = models.ForeignKey(Question) value = models.BooleanField() objects = AnswerManager() class Meta: unique_together = ("question", "assessment") def __str__(self): return "Question %s for assessment %d" % (self.question.full_order, self.assessment.pk)
class Question(mptt.models.MPTTModel): text = models.CharField(max_length=1024) parent = mptt.models.TreeForeignKey( "self", null=True, blank=True, on_delete=models.CASCADE, related_name="children" ) parent_answer = models.BooleanField(default=None, null=True) order = models.IntegerField(blank=True) classification = models.ForeignKey( TaxonomyClassification, on_delete=models.CASCADE, null=True, blank=True ) class Meta: verbose_name = "Question" verbose_name_plural = "Questions" class MPTTMeta: order_insertion_by = ["order"] def save(self, *args, **kwargs): if not self.order: self.order = utils.set_order(self.classification, self.parent) super(Question, self).save(*args, **kwargs) @property def full_order(self): return ".".join( [str(question.order) for question in self.get_ancestors(include_self=True)] ) def __str__(self): if self.parent: return "Question: %s with parent answer: %s" % ( self.full_order, self.parent_answer, ) else: return "C: %s Question: %s" % (self.classification.code, self.order)
class Bid(mptt.models.MPTTModel): objects = BidManager() event = models.ForeignKey( 'Event', on_delete=models.PROTECT, verbose_name='Event', null=True, blank=True, related_name='bids', help_text='Required for top level bids if Run is not set', ) speedrun = models.ForeignKey( 'SpeedRun', on_delete=models.PROTECT, verbose_name='Run', null=True, blank=True, related_name='bids', ) parent = mptt.models.TreeForeignKey( 'self', on_delete=models.PROTECT, verbose_name='Parent', editable=False, null=True, blank=True, related_name='options', ) name = models.CharField(max_length=64) state = models.CharField( max_length=32, db_index=True, default='OPENED', choices=( ('PENDING', 'Pending'), ('DENIED', 'Denied'), ('HIDDEN', 'Hidden'), ('OPENED', 'Opened'), ('CLOSED', 'Closed'), ), ) description = models.TextField(max_length=1024, blank=True) shortdescription = models.TextField( max_length=256, blank=True, verbose_name='Short Description', help_text='Alternative description text to display in tight spaces', ) goal = models.DecimalField(decimal_places=2, max_digits=20, null=True, blank=True, default=None) istarget = models.BooleanField( default=False, verbose_name='Target', help_text= "Set this if this bid is a 'target' for donations (bottom level choice or challenge)", ) allowuseroptions = models.BooleanField( default=False, verbose_name='Allow User Options', help_text= 'If set, this will allow donors to specify their own options on the donate page (pending moderator approval)', ) option_max_length = models.PositiveSmallIntegerField( 'Max length of user suggestions', blank=True, null=True, default=None, validators=[MinValueValidator(1), MaxValueValidator(64)], help_text= 'If allowuseroptions is set, this sets the maximum length of user-submitted bid suggestions', ) revealedtime = models.DateTimeField(verbose_name='Revealed Time', null=True, blank=True) biddependency = models.ForeignKey( 'self', on_delete=models.PROTECT, verbose_name='Dependency', null=True, blank=True, related_name='dependent_bids', ) total = models.DecimalField(decimal_places=2, max_digits=20, editable=False, default=Decimal('0.00')) count = models.IntegerField(editable=False) class Meta: app_label = 'tracker' unique_together = (( 'event', 'name', 'speedrun', 'parent', ), ) ordering = [ 'event__datetime', 'speedrun__starttime', 'parent__name', 'name' ] permissions = ( ('top_level_bid', 'Can create new top level bids'), ('delete_all_bids', 'Can delete bids with donations attached'), ('view_hidden_bid', 'Can view hidden bids'), ) class MPTTMeta: order_insertion_by = ['name'] def get_absolute_url(self): return reverse('tracker:bid', args=(self.id, )) def natural_key(self): if self.parent: return ( self.event.natural_key(), self.name, self.speedrun.natural_key() if self.speedrun else None, self.parent.natural_key(), ) elif self.speedrun: return (self.event.natural_key(), self.name, self.speedrun.natural_key()) else: return (self.event.natural_key(), self.name) def clean(self): # Manually de-normalize speedrun/event/state to help with searching # TODO: refactor this logic, it should be correct, but is probably not minimal if self.option_max_length: if not self.allowuseroptions: raise ValidationError( _('Cannot set option_max_length without allowuseroptions'), code='invalid', ) # FIXME: why is this printing 'please enter a whole number'? # raise ValidationError( # { # 'option_max_length': ValidationError( # _('Cannot set option_max_length without allowuseroptions'), # code='invalid', # ), # } # ) if self.pk: for child in self.get_children(): if len(child.name) > self.option_max_length: raise ValidationError( _('Cannot set option_max_length to %(length)d, child name `%(name)s` is too long' ), code='invalid', params={ 'length': self.option_max_length, 'name': child.name, }, ) # TODO: why is this printing 'please enter a whole number'? # raise ValidationError({ # 'option_max_length': ValidationError( # _('Cannot set option_max_length to %(length), child name %(name) is too long'), # code='invalid', # params={ # 'length': self.option_max_length, # 'name': child.name, # } # ), # }) if self.parent: max_len = self.parent.option_max_length if max_len and len(self.name) > max_len: raise ValidationError({ 'name': ValidationError( _('Name is longer than %(limit)s characters'), params={'limit': max_len}, code='invalid', ), }) if self.biddependency: if self.parent or self.speedrun: if self.event != self.biddependency.event: raise ValidationError( 'Dependent bids must be on the same event') if not self.parent: if not self.get_event(): raise ValidationError( 'Top level bids must have their event set') if not self.goal: self.goal = None elif self.goal <= Decimal('0.0'): raise ValidationError('Goal should be a positive value') if self.state in ['PENDING', 'DENIED' ] and (not self.istarget or not self.parent or not self.parent.allowuseroptions): raise ValidationError({ 'state': f'State `{self.state}` can only be set on targets with parents that allow user options' }) if self.istarget and self.options.count() != 0: raise ValidationError('Targets cannot have children') if self.parent and self.parent.istarget: raise ValidationError('Cannot set that parent, parent is a target') if self.istarget and self.allowuseroptions: raise ValidationError( 'A bid target cannot allow user options, since it cannot have children.' ) if (not self.allowuseroptions and self.pk and self.get_children().filter(state__in=['PENDING', 'DENIED'])): raise ValidationError({ 'allowuseroptions': 'Bid has pending/denied children, cannot remove allowing user options' }) same_name = Bid.objects.filter( speedrun=self.speedrun, event=self.event, parent=self.parent, name__iexact=self.name, ).exclude(pk=self.pk) if same_name.exists(): raise ValidationError( 'Cannot have a bid under the same event/run/parent with the same name' ) def save(self, *args, skip_parent=False, **kwargs): if self.parent: self.check_parent() if self.speedrun: self.event = self.speedrun.event if self.state in ['OPENED', 'CLOSED'] and not self.revealedtime: self.revealedtime = datetime.utcnow().replace(tzinfo=pytz.utc) if self.biddependency: self.event = self.biddependency.event if not self.speedrun: self.speedrun = self.biddependency.speedrun self.update_total() super(Bid, self).save(*args, **kwargs) if self.pk: for option in self.get_descendants(): if option.check_parent(): option.save(skip_parent=True) if self.parent and not skip_parent: self.parent.save() def check_parent(self): changed = False if self.speedrun != self.parent.speedrun: self.speedrun = self.parent.speedrun changed = True if self.event != self.parent.event: self.event = self.parent.event changed = True if self.state not in ['PENDING', 'DENIED' ] and self.state != self.parent.state: self.state = self.parent.state changed = True return changed @property def has_options(self): return self.allowuseroptions or self.public_options.exists() @property def public_options(self): return self.options.filter(Q(state='OPENED') | Q(state='CLOSED')).order_by('-total') def update_total(self): if self.istarget: self.total = self.bids.filter( donation__transactionstate='COMPLETED').aggregate( Sum('amount'))['amount__sum'] or Decimal('0.00') self.count = self.bids.filter( donation__transactionstate='COMPLETED').count() # auto close this if it's a challenge with no children and the goal's been met if (self.goal and self.state == 'OPENED' and self.total >= self.goal and self.istarget): self.state = 'CLOSED' else: options = self.options.exclude(state__in=('DENIED', 'PENDING')).aggregate( Sum('total'), Sum('count')) self.total = options['total__sum'] or Decimal('0.00') self.count = options['count__sum'] or 0 def get_event(self): if self.speedrun: return self.speedrun.event else: return self.event def full_label(self, addMoney=True): result = [self.fullname()] if self.speedrun: result = [self.speedrun.name_with_category(), ' : '] + result if addMoney: result += [' $', '%0.2f' % self.total] if self.goal: result += [' / ', '%0.2f' % self.goal] return ''.join(result) def __str__(self): if self.parent: return f'{self.parent} (Parent) -- {self.name}' elif self.speedrun: return f'{self.speedrun.name_with_category()} (Run) -- {self.name}' else: return f'{self.event} (Event) -- {self.name}' def fullname(self): parent = self.parent.fullname() + ' -- ' if self.parent else '' return parent + self.name
class Legislation(_TaxonomyModel): title = models.CharField(max_length=256) abstract = models.CharField(max_length=1024, blank=True, null=True) country = models.ForeignKey(Country, related_name="legislations") language = models.CharField(choices=constants.ALL_LANGUAGES, default=constants.DEFAULT_LANGUAGE_VALUE, max_length=64) law_type = models.CharField(choices=constants.LEGISLATION_TYPE, default=constants.LEGISLATION_DEFAULT_VALUE, max_length=64) year = models.IntegerField(default=constants.LEGISLATION_YEAR_RANGE[-1]) year_amendment = models.IntegerField( default=constants.LEGISLATION_DEFAULT_YEAR, blank=True, null=True) year_mention = models.CharField(max_length=1024, blank=True, null=True) geo_coverage = models.CharField( choices=constants.GEOGRAPHICAL_COVERAGE, default=constants.GEOGRAPHICAL_COVERAGE_DEFAULT_VALUE, max_length=64, null=True) source = models.CharField(max_length=256, blank=True, null=True) source_type = models.CharField(choices=constants.SOURCE_TYPE, default=constants.SOURCE_TYPE_DEFAULT_VALUE, max_length=64, blank=True, null=True) website = models.URLField(max_length=2000, blank=True, null=True) legispro_article = models.CharField(max_length=512, blank=True, null=True) import_from_legispro = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) pdf_file = models.FileField(null=True, blank=True) pdf_file_name = models.CharField(null=True, max_length=256) objects = LegislationManager() @property def country_name(self): return self.country.name @property def country_iso(self): return self.country.iso @property def other_legislations(self): other = {} for classification in self.classifications.all(): other[classification] = Legislation.objects.filter( classifications__id__exact=classification.pk).exclude( pk=self.pk).all()[:3] return other # @TODO: Change the __str__ to something more appropriate def __str__(self): return "Legislation: " + ' | '.join([self.country.name, self.law_type]) def highlighted_title(self): """ If this law was returned as a result of an elasticsearch query, return the title with the search terms highlighted. If not, return the original title. """ return getattr(self, '_highlighted_title', self.title) def highlighted_abstract(self): """ If this law was returned as a result of an elasticsearch query, return the abstract with the search terms highlighted. If not, return an empty string. """ return getattr(self, '_highlighted_abstract', '') def highlighted_pdf_text(self): """ If this law was returned as a result of an elasticsearch query, return the pdf_text with the search terms highlighted. If not, return an empty string. """ return getattr(self, '_highlighted_pdf_text', '') def highlighted_classifications(self): """ If this law was returned as a result of an elasticsearch query, return a list of classification names with the search terms highlighted. If not, return the original list of classification names. """ return getattr( self, '_highlighted_classifications', self.classifications.all().values_list('name', flat=True)) def highlighted_tags(self): """ If this law was returned as a result of an elasticsearch query, return a list of tag names with the search terms highlighted. If not, return the original list of tag names. """ return getattr(self, '_highlighted_tags', self.tags.all().values_list('name', flat=True)) def highlighted_articles(self): """ If this law was returned as a result of an elasticsearch query, return a list of dictionaries representing articles with the search terms highlighted in the text field. If not, return an empty list. """ return getattr(self, '_highlighted_articles', []) def save_pdf_pages(self): if settings.DEBUG: time_to_load_pdf = time.time() if settings.DEBUG: print("INFO: FS pdf file load time: %fs" % (time.time() - time_to_load_pdf)) time_begin_transaction = time.time() with transaction.atomic(): pdf = pdftotext.PDF(self.pdf_file) for idx, page in enumerate(pdf): page = page.replace('\x00', '') LegislationPage(page_text="<pre>%s</pre>" % page, page_number=idx + 1, legislation=self).save() if settings.DEBUG: print("INFO: ORM models.LegislationPages save time: %fs" % (time.time() - time_begin_transaction)) # This is necessary in order to trigger the signal that will update the # ElasticSearch index. self.save()
class CountryBase(models.Model): class Meta: abstract = True cw = models.BooleanField('Commonwealth (Member country)', default=False) small_cw = models.BooleanField('Small commonwealth country', default=False) un = models.BooleanField('United Nations (Member state)', default=False) ldc = models.BooleanField('Least developed country (LDC)', default=False) lldc = models.BooleanField('Landlocked developing country (LLDC)', default=False) sid = models.BooleanField('Small island developing state (SID)', default=False) region = models.ForeignKey(Region, null=True, blank=True) sub_region = models.ForeignKey(SubRegion, null=True, blank=True) legal_system = models.ForeignKey(LegalSystem, null=True, blank=True) population = models.FloatField("Population ('000s) 2018", null=True) hdi2015 = models.FloatField('HDI2015', null=True) gdp_capita = models.FloatField('GDP per capita, US$ 2016', null=True) ghg_no_lucf = models.FloatField( 'Total GHG Emissions excluding LUCF MtCO2e 2014', null=True) ghg_lucf = models.FloatField( 'Total GHG Emissions including LUCF MtCO2e 2014', null=True) cvi2015 = models.FloatField('Climate vulnerability index 2015', null=True, blank=True) mitigation_focus_areas = models.ManyToManyField(FocusArea, blank=True) adaptation_priority_sectors = models.ManyToManyField(PrioritySector, blank=True) @property def population_range(self): return _format_range(_range_from_value(POP_RANGES, self.population)) @property def hdi2015_range(self): return (_format_range(_range_from_value(HDI_RANGES, self.hdi2015)) if self.hdi2015 else 'N/A') @property def gdp_capita_range(self): label = itemgetter(2) return (label(_range_from_value(GDP_RANGES, self.gdp_capita)) if self.gdp_capita else 'N/A') @property def ghg_no_lucf_range(self): return (_format_range(_range_from_value(GHG_NO_LUCF, self.ghg_no_lucf)) if self.ghg_no_lucf else None) @property def ghg_lucf_range(self): return (_format_range(_range_from_value(GHG_LUCF, self.ghg_lucf)) if self.ghg_lucf else None) def clone_to_profile(self, user_profile): fields = [ 'cw', 'small_cw', 'un', 'ldc', 'lldc', 'sid', 'region_id', 'sub_region_id', 'legal_system_id', 'population', 'hdi2015', 'gdp_capita', 'ghg_no_lucf', 'ghg_lucf', 'cvi2015' ] data = {key: getattr(self, key) for key in fields} data['user'] = user_profile data['country'] = self clone = AssessmentProfile.objects.create(**data) clone.save() # copy many to many fields m2m = (f.name for f in self._meta.get_fields() if isinstance(f, models.ManyToManyField)) for name in m2m: val = (getattr(self, name).all() if hasattr(self, name) else []) setattr(clone, name, val) return clone
class Bid(mptt.models.MPTTModel): objects = BidManager() event = models.ForeignKey('Event', on_delete=models.PROTECT, verbose_name='Event', null=True, blank=True, related_name='bids', help_text='Required for top level bids if Run is not set') speedrun = models.ForeignKey('SpeedRun', on_delete=models.PROTECT, verbose_name='Run', null=True, blank=True, related_name='bids') parent = mptt.models.TreeForeignKey('self', on_delete=models.PROTECT, verbose_name='Parent', editable=False, null=True, blank=True, related_name='options') name = models.CharField(max_length=64) state = models.CharField(max_length=32, choices=(('PENDING', 'Pending'), ('DENIED', 'Denied'), ( 'HIDDEN', 'Hidden'), ('OPENED', 'Opened'), ('CLOSED', 'Closed')), default='OPENED') description = models.TextField(max_length=1024, blank=True) shortdescription = models.TextField(max_length=256, blank=True, verbose_name='Short Description', help_text="Alternative description text to display in tight spaces") goal = models.DecimalField( decimal_places=2, max_digits=20, null=True, blank=True, default=None) istarget = models.BooleanField(default=False, verbose_name='Target', help_text="Set this if this bid is a 'target' for donations (bottom level choice or challenge)") allowuseroptions = models.BooleanField(default=False, verbose_name="Allow User Options", help_text="If set, this will allow donors to specify their own options on the donate page (pending moderator approval)") revealedtime = models.DateTimeField( verbose_name='Revealed Time', null=True, blank=True) biddependency = models.ForeignKey('self', on_delete=models.PROTECT, verbose_name='Dependency', null=True, blank=True, related_name='dependent_bids') total = models.DecimalField( decimal_places=2, max_digits=20, editable=False, default=Decimal('0.00')) count = models.IntegerField(editable=False) class Meta: app_label = 'tracker' unique_together = (('event', 'name', 'speedrun', 'parent',),) ordering = ['event__datetime', 'speedrun__starttime', 'parent__name', 'name'] permissions = ( ('top_level_bid', 'Can create new top level bids'), ('delete_all_bids', 'Can delete bids with donations attached'), ('view_hidden', 'Can view hidden bids'), ) class MPTTMeta: order_insertion_by = ['name'] def natural_key(self): if self.parent: return (self.event.natural_key(), self.name, self.speedrun.natural_key() if self.speedrun else None, self.parent.natural_key()) elif self.speedrun: return (self.event.natural_key(), self.name, self.speedrun.natural_key()) else: return (self.event.natural_key(), self.name) def clean(self): # Manually de-normalize speedrun/event/state to help with searching # TODO: refactor this logic, it should be correct, but is probably not minimal if self.speedrun: self.event = self.speedrun.event if self.parent: curr = self.parent while curr.parent != None: curr = curr.parent root = curr self.speedrun = root.speedrun self.event = root.event if self.state != 'PENDING' and self.state != 'DENIED': self.state = root.state if self.biddependency: if self.parent or self.speedrun: if self.event != self.biddependency.event: raise ValidationError( 'Dependent bids must be on the same event') self.event = self.biddependency.event if not self.speedrun: self.speedrun = self.biddependency.speedrun if not self.parent: if not self.get_event(): raise ValidationError( 'Top level bids must have their event set') if self.id: for option in self.get_descendants(): option.speedrun = self.speedrun option.event = self.event if option.state != 'PENDING' and option.state != 'DENIED': option.state = self.state option.save() if not self.goal: self.goal = None elif self.goal <= Decimal('0.0'): raise ValidationError('Goal should be a positive value') if self.istarget and self.options.count() != 0: raise ValidationError('Targets cannot have children') if self.parent and self.parent.istarget: raise ValidationError('Cannot set that parent, parent is a target') if self.istarget and self.allowuseroptions: raise ValidationError( 'A bid target cannot allow user options, since it cannot have children.') sameName = Bid.objects.filter( speedrun=self.speedrun, event=self.event, parent=self.parent, name__iexact=self.name) if sameName.exists(): if sameName.count() > 1 or sameName[0].id != self.id: raise ValidationError( 'Cannot have a bid under the same event/run/parent with the same name') if self.id == None or (sameName.exists() and sameName[0].state == 'HIDDEN' and self.state == 'OPENED'): self.revealedtime = datetime.utcnow().replace(tzinfo=pytz.utc) self.update_total() @property def has_options(self): return self.allowuseroptions or self.public_options.exists() @property def public_options(self): return self.options.filter(Q(state='OPENED') | Q(state='CLOSED')).order_by('-total') def update_total(self): if self.istarget: self.total = self.bids.filter(donation__transactionstate='COMPLETED').aggregate( Sum('amount'))['amount__sum'] or Decimal('0.00') self.count = self.bids.filter( donation__transactionstate='COMPLETED').count() # auto close this if it's a challenge with no children and the goal's been met if self.goal and self.state == 'OPENED' and self.total >= self.goal and self.istarget: self.state = 'CLOSED' else: options = self.options.exclude(state__in=( 'HIDDEN', 'DENIED', 'PENDING')).aggregate(Sum('total'), Sum('count')) self.total = options['total__sum'] or Decimal('0.00') self.count = options['count__sum'] or 0 def get_event(self): if self.speedrun: return self.speedrun.event else: return self.event def full_label(self, addMoney=True): result = [self.fullname()] if self.speedrun: result = [self.speedrun.name_with_category(), ' : '] + result if addMoney: result += [' $', '%0.2f' % self.total] if self.goal: result += [' / ', '%0.2f' % self.goal] return ''.join(result) def __unicode__(self): if self.parent: return unicode(self.parent) + ' -- ' + self.name elif self.speedrun: return self.speedrun.name_with_category() + ' -- ' + self.name else: return unicode(self.event) + ' -- ' + self.name def fullname(self): return ((self.parent.fullname() + ' -- ') if self.parent else '') + self.name