예제 #1
0
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
예제 #2
0
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
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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
예제 #6
0
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()
예제 #7
0
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
예제 #8
0
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