Exemple #1
0
class Post(models.Model):
    title = models.CharField(max_length=150)
    tags = models.ManyToManyField(Tag)
    slug = models.SlugField(max_length=150, blank=True)
    timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
    text = SplitField()

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        slug_str = translify(self.title)
        unique_slugify(self, slug_str)
        self.slug = slugify(slug_str)
        super(Post, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('post_view', args=[str(self.slug)])

    def get_context_data(self, **kwargs):
        context = super(Post, self).get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

    def get_tags(self):
        return self.tags.all()
Exemple #2
0
class NoRendered(models.Model):
    """
    Test that the no_excerpt_field keyword arg works. This arg should
    never be used except by the South model-freezing.

    """
    body = SplitField(no_excerpt_field=True)
Exemple #3
0
class LotionDailyFollowUp(TimeStampedModel):
    """
    Model for LotionDailyFollowUp
    """
    daily_follow_up = models.ForeignKey(
        to="DailyFollowUp",
        verbose_name=_("Daily follow-up"),
        on_delete=models.CASCADE,
        blank=False,
        null=False
    )
    lotion = models.ForeignKey(
        to="Lotion",
        verbose_name=_("Lotion"),
        on_delete=models.SET_NULL,
        blank=False,
        null=True
    )
    comment = SplitField(_("Comment"), blank=True, null=True)

    class Meta:
        ordering = ("daily_follow_up",)
        verbose_name = _("Lotion daily follow-up")
        verbose_name_plural = _("Lotion daily follow-ups")

    def __str__(self):
        return "Lotion: {} - {}".format(self.daily_follow_up, self.lotion.name)
Exemple #4
0
class Medication(TimeStampedModel):
    """
    Model for
    """

    def attachment_file_medication(self, filename):
        f, ext = os.path.splitext(filename)
        upload_to = "docs/medication/{}/".format(self.child.slug)
        return '%s%s%s' % (upload_to, uuid.uuid4().hex, ext)

    type_medication = models.ForeignKey(
        to="TypeMedication",
        verbose_name=_("Type medication"),
        on_delete=models.SET_NULL,
        blank=False,
        null=True
    )
    from_date = models.DateField(_("From date"))
    end_date = models.DateField(_("End date"))
    child = models.ForeignKey(
        to=Child,
        verbose_name=_("Child"),
        on_delete=models.CASCADE
    )
    comment = SplitField(_("Comment"), blank=True, null=True)
    attachment = models.FileField(_("Attachment"), upload_to=attachment_file_medication, blank=True, null=True)

    class Meta:
        ordering = ("from_date", 'end_date', "type_medication")
        verbose_name = _("Medication")
        verbose_name_plural = _("Medications")

    def __str__(self):
        return _("Medication: {} - {} | {}").format(self.from_date, self.end_date, self.type_medication.name)
Exemple #5
0
class Activity(TimeStampedModel):
    """
    Model for Activity
    """
    daily_follow_up = models.ForeignKey(
        to="DailyFollowUp",
        verbose_name=_("Daily follow-up"),
        on_delete=models.CASCADE,
        blank=False,
        null=False
    )
    type_activity = models.ForeignKey(
        to="TypeActivity",
        verbose_name=_("Type activity"),
        on_delete=models.SET_NULL,
        blank=False,
        null=True
    )
    comment = SplitField(_("Comment"), blank=True, null=True)

    class Meta:
        ordering = ('daily_follow_up', 'type_activity',)
        verbose_name = _('Activity')
        verbose_name_plural = _('Activities')

    def __str__(self):
        return _("Activity: {} - {} - {}").format(self.daily_follow_up, self.type_activity.name,
                                                  self.type_activity.group.name)
Exemple #6
0
class Article(Authorable, Creatable, Coauthorable, Featurable):
    title = models.CharField(max_length=250)
    subtitle = models.CharField(max_length=250, blank=True)
    created_date = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)
    published = models.BooleanField(default=False)
    published_date = models.DateTimeField(null=True)
    slug = models.SlugField()
    body = SplitField()
    labels = models.ManyToManyField(
        'Label',
        blank=True,
    )
    category = models.ForeignKey('Category',
                                 on_delete=models.CASCADE,
                                 blank=True,
                                 null=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('article-detail', args=(self.slug, ))
Exemple #7
0
class SplitFieldAbstractParent(models.Model):
    content = SplitField()

    class Meta:
        abstract = True
Exemple #8
0
class Article(models.Model):
    title = models.CharField(max_length=50)
    body = SplitField()
class PostBase(TimeStampedModel):
    """
    Base class for Post-like models
    
    This class defines basic fields and logic for a blog post,
    including author, title, status (whether it's published or not)
    and content, plus timestamp fields.
    
    By default, it uses an InheritanceManager, allowing subclasses
    to be included in a queryset.
    """
    STATUS = Choices(
        ('draft', 'Draft'),
        ('public', 'Public'),
        ('hidden', 'Hidden'),
    )

    # metadata
    author = models.ForeignKey(User)
    published = models.DateTimeField(blank=True, null=True)
    status = StatusField(default=STATUS.draft)
    status_changed = MonitorField(monitor='status', editable=False)

    # content
    title = models.CharField(max_length=255, blank=True)
    slug = models.SlugField(max_length=255)
    excerpt = models.TextField(blank=True, help_text="Add a manual excerpt")
    content = SplitField(blank=True)

    allow_comments = models.BooleanField(default=True)
    tags = TaggableManager(blank=True)

    # manager
    objects = PassThroughManager(PostQuerySet)
    versions = LatestManager()

    class Meta:
        abstract = True
        get_latest_by = "published"
        ordering = ('-published', '-created')

    def __unicode__(self):
        return self.title

    @models.permalink
    def get_absolute_url(self):
        return ('scrivo_post_detail', None, {
            'year': self.published.strftime('%Y'),
            'month': self.published.strftime('%b').lower(),
            'day': self.published.strftime('%d'),
            'slug': self.slug
        })

    def publish(self, *args, **kwargs):
        if not self.published:
            self.published = datetime.datetime.now()
        self.status = self.STATUS.public
        self.save(*args, **kwargs)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super(PostBase, self).save(*args, **kwargs)
Exemple #10
0
class Post(StatusModel, TimeStampedModel, TimeFramedModel):
    objects = PostManager()

    STATUS = Choices(
        ('draft', _('draft')),
        ('published', _('published')),
        ('hidden', _('hidden')),
    )

    title = models.CharField(_('title'), max_length=255)
    body = SplitField(_('body'))
    pub_date = MonitorField(_('published date/time'),
                            monitor='status',
                            when=['published'])
    slug = AutoSlugField(populate_from='title',
                         max_length=255,
                         allow_unicode=True,
                         unique_for_date='pub_date')
    featured = models.BooleanField(_('featured'), default=False)
    views = models.PositiveIntegerField(_('views'), default=0)
    lead = models.TextField(_('lead'), blank=True)
    comment_enabled = models.BooleanField(_('comments enabled'), default=True)

    cover = models.ForeignKey('covers.Cover',
                              verbose_name=_('cover'),
                              blank=True,
                              null=True,
                              on_delete=models.SET_NULL)
    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               verbose_name=_('author'))
    category = models.ForeignKey('categories.Category',
                                 verbose_name='category')

    tags = TaggableManager(blank=True)
    comments = GenericRelation(MyComment,
                               object_id_field='object_pk',
                               content_type_field='content_type')

    class Meta:
        ordering = ('-pub_date', )
        get_latest_by = 'pub_date'
        verbose_name = _('Post')
        verbose_name_plural = _('Posts')

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        self.cover = self.cover or self.category.cover
        super(Post, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('blog:detail',
                       kwargs={
                           'year': self.pub_date.strftime('%Y'),
                           'month': self.pub_date.strftime('%m'),
                           'day': self.pub_date.strftime('%d'),
                           'slug': self.slug,
                       })

    @property
    def body_html(self):
        return mark_safe(markdownify(self.body.content))

    @property
    def toc(self):
        md = markdown.Markdown(
            extensions=settings.MARKDOWNX_MARKDOWN_EXTENSIONS)
        md.convert(self.body.content)
        return mark_safe(md.toc)

    @property
    def excerpt_html(self):
        return mark_safe(markdownify(self.body.excerpt))

    @property
    def lead_html(self):
        return mark_safe(markdownify(self.lead))

    def root_comments(self):
        return self.comments.filter(parent__isnull=True)

    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])
Exemple #11
0
class Match(models.Model):
    """ Represents a match.

        Note: both fixtures and results are classed as matches:
            Fixtures are just matches in the future
            Results are matches in the past
    """

    # Avoid magic numbers. However this simple constant definition is insufficient if
    # these values ever change!
    POINTS_FOR_WIN = 3
    POINTS_FOR_DRAW = 1
    POINTS_FOR_LOSS = 0

    # Walk-over matches must be either 3-0 or 5-0 (depending on the league)
    WALKOVER_SCORE_W1 = 3
    WALKOVER_SCORE_W2 = 5
    WALKOVER_SCORE_L = 0

    COMMENTARY_START_MINS = 15
    COMMENTARY_END_MINS = 120

    # The Cambridge South team playing in this match
    our_team = models.ForeignKey(ClubTeam,
                                 on_delete=models.PROTECT,
                                 verbose_name="Our team")

    # The opposition team
    opp_team = models.ForeignKey(Team,
                                 on_delete=models.PROTECT,
                                 verbose_name="Opposition team")

    # The match venue
    venue = models.ForeignKey(Venue,
                              null=True,
                              blank=True,
                              on_delete=models.SET_NULL,
                              related_name="matches")

    # Is the match a home or away fixture for South
    home_away = models.CharField("Home/Away",
                                 max_length=5,
                                 choices=HomeAway.Choices,
                                 default=HomeAway.Home)

    # The type of fixture
    fixture_type = models.CharField("Fixture type",
                                    max_length=10,
                                    choices=FixtureType.Choices,
                                    default=FixtureType.League)

    # The fixture date
    date = models.DateField("Fixture date")

    # The fixture start time. This can be left blank if its not known.
    time = models.TimeField("Start time", null=True, blank=True, default=None)

    # The alternative match outcome if it wasn't actually played
    alt_outcome = models.CharField("Alternative outcome",
                                   max_length=10,
                                   null=True,
                                   blank=True,
                                   default=None,
                                   choices=AlternativeOutcome.Choices)

    # Cambridge South's final score
    our_score = models.PositiveSmallIntegerField("Our score",
                                                 null=True,
                                                 blank=True,
                                                 default=None)

    # The opposition's final score
    opp_score = models.PositiveSmallIntegerField("Opposition's score",
                                                 null=True,
                                                 blank=True,
                                                 default=None)

    # Cambridge South's half time score
    our_ht_score = models.PositiveSmallIntegerField("Our half-time score",
                                                    null=True,
                                                    blank=True,
                                                    default=None)

    # The opposition's half time score
    opp_ht_score = models.PositiveSmallIntegerField(
        "Opposition's half-time score", null=True, blank=True, default=None)

    # The total number of own goals scored by the opposition.
    # Note - Cambs South own goals are recored in the Appearance model (as we care about
    # who scored the own goal!)
    opp_own_goals = models.PositiveSmallIntegerField("Opposition own-goals",
                                                     default=0)

    # A short paragraph that can be used to hype up the match before its played - can be HTML
    # TODO: Prevent entering pre-match hype for matches in the past?
    pre_match_hype = SplitField("Pre-match hype", blank=True, null=True)

    # The (optional) title of the match report
    report_title = models.CharField("Match report title",
                                    max_length=200,
                                    blank=True,
                                    null=True)

    # The (optional) match report author
    report_author = models.ForeignKey(Member,
                                      verbose_name="Match report author",
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name="match_reports")

    # The actual match report text - can be HTML
    report_body = SplitField("Match report", blank=True, null=True)

    # The datetime at which the report was first published
    report_pub_timestamp = models.DateTimeField(
        "Match report publish timestamp",
        editable=False,
        default=None,
        null=True)

    # Advanced fields - typically leave as default value ######################

    # If True, this match should NOT count towards Goal King stats
    ignore_for_goal_king = models.BooleanField(
        default=False,
        help_text="Ignore this match when compiling Goal King stats")

    # If True, this match should NOT count towards Southerners League stats
    ignore_for_southerners = models.BooleanField(
        default=False,
        help_text="Ignore this match when compiling Southerners League stats")

    # Sometimes despite the clubs' kit clashing, we still play in our normal home kit. This can be recorded here.
    override_kit_clash = models.BooleanField(
        default=False,
        help_text="Ignore normal kit-clash with this club for this match")

    # Sometimes goals scored in shorter matches (e.g. in a tournament) will count a different amount towards the 'goals-per-game' stats
    gpg_pro_rata = models.FloatField(
        default=1.0,
        help_text=
        "Goals-per-game multiplier. Only change this from the default value for matches of a different length."
    )

    # Derived attributes ######################################################
    # - these values cannot be entered in a form - they are derived based on the other attributes
    season = models.ForeignKey('competitions.Season',
                               on_delete=models.PROTECT,
                               editable=False)
    division = models.ForeignKey('competitions.Division',
                                 null=True,
                                 blank=True,
                                 editable=False,
                                 on_delete=models.PROTECT)
    cup = models.ForeignKey('competitions.Cup',
                            null=True,
                            blank=True,
                            editable=False,
                            on_delete=models.PROTECT)

    # Convenience attribute listing all members who made an appearance in this match
    players = models.ManyToManyField('members.Member',
                                     through="Appearance",
                                     related_name="matches")

    objects = MatchQuerySet.as_manager()

    class Meta:
        """ Meta-info for the Match model."""
        app_label = 'matches'
        verbose_name_plural = "matches"
        ordering = ['date', 'time', 'our_team__position']

    def __str__(self):
        return str("{} vs {} ({}, {})".format(self.our_team, self.opp_team,
                                              self.fixture_type, self.date))

    @models.permalink
    def get_absolute_url(self):
        """ Returns the url for this match."""
        return ('match_detail', [self.pk])

    def clean(self):
        # If its a walkover, check the score is a valid walkover score
        if (self.alt_outcome == AlternativeOutcome.Walkover and
                not Match.is_walkover_score(self.our_score, self.opp_score)):
            raise ValidationError(
                "A walk-over score must be 3-0, 5-0, 0-3 or 0-5. Score = {}-{}"
                .format(self.our_score, self.opp_score))

        # If its cancelled or postponed or BYE, check the scores are not entered
        if ((self.alt_outcome == AlternativeOutcome.Cancelled
             or self.alt_outcome == AlternativeOutcome.Postponed
             or self.alt_outcome == AlternativeOutcome.BYE)
                and (self.our_score is not None or self.opp_score is not None
                     or self.our_ht_score is not None
                     or self.opp_ht_score is not None)):
            raise ValidationError(
                "A cancelled or postponed match should not have scores")

        if self.alt_outcome is None or self.alt_outcome == AlternativeOutcome.Abandoned:
            # You can't specify one score without the other
            if ((self.our_score is not None and self.opp_score is None) or
                (self.our_score is None and self.opp_score is not None)):
                raise ValidationError("Both scores must be provided")

            # ...same goes for half time scores
            if ((self.our_ht_score is not None and self.opp_ht_score is None)
                    or
                (self.our_ht_score is None and self.opp_ht_score is not None)):
                raise ValidationError("Both half-time scores must be provided")

            # The opposition can't score more own goals than our total score
            if (self.our_score is not None
                    and self.opp_own_goals > self.our_score):
                raise ValidationError("Too many opposition own goals")

            # Half-time scores must be <= the final scores
            if self.all_scores_provided():
                if (self.our_ht_score > self.our_score
                        or self.opp_ht_score > self.opp_score):
                    raise ValidationError(
                        "Half-time scores cannot be greater than final scores")

        # Automatically set the season based on the date
        try:
            self.season = Season.objects.by_date(self.date)
        except Season.DoesNotExist:
            raise ValidationError(
                "This date appears to be outside of any recognised season.")

        if self.fixture_type == FixtureType.League:
            # Automatically set the division based on the team and season
            try:
                self.division = ClubTeamSeasonParticipation.objects.get(
                    team=self.our_team, season=self.season).division
            except Division.DoesNotExist:
                raise ValidationError(
                    "{} is not participating in a division in the season {}".
                    format(self.our_team, self.season))
        else:
            self.division = None  # Clear the division field

        if self.fixture_type == FixtureType.Cup:
            # Automatically set the cup based on the team and season
            try:
                self.cup = ClubTeamSeasonParticipation.objects.get(
                    team=self.our_team, season=self.season).cup
            except Cup.DoesNotExist:
                raise ValidationError(
                    "{} is not participating in a cup in the season {}".format(
                        self.our_team, self.season))
        else:
            self.cup = None  # Clear the cup field

    def save(self, *args, **kwargs):
        """ Set a few automatic fields and then save """
        # Timestamp the report publish datetime when its first created
        if self.report_pub_timestamp is None and self.report_body.content:
            self.report_pub_timestamp = datetime.now()

        super(Match, self).save(*args, **kwargs)

    @property
    def is_home(self):
        """ Returns True if this is a home fixture"""
        return self.home_away == HomeAway.Home

    def home_away_abbrev(self):
        """ Returns an abbreviated representation of the home/away status ('H'/'A') """
        return 'H' if self.is_home else 'A'

    def kit_clash(self):
        """Returns true if there is a kit-clash for this fixture

            Note that this takes into account the override_kit_clash field.
            Home    Clash   Override    Return
            F       F       F           F
            F       F       T           T
            F       T       F           T
            F       T       T           F
            T       F       F           F
            T       F       T           T
            T       T       F           F
            T       T       T           T
        """
        if self.is_home:
            return self.override_kit_clash
        clash = self.opp_team.club.kit_clash(self.our_team.gender)
        return (clash and not self.override_kit_clash) or (
            not clash and self.override_kit_clash)

    def has_report(self):
        """ Returns True if this match has a match report"""
        return self.report_body and self.report_body.content

    def datetime(self):
        """ Convenience method to retrieve the date and time as one datetime object.
            Returns just the date if the time is not set.
        """
        if self.time is not None:
            return datetime.combine(self.date, self.time)
        return datetime.combine(self.date, time())

    def is_off(self):
        """ Returns True if the match is postponed or cancelled."""
        return self.alt_outcome in (AlternativeOutcome.Postponed,
                                    AlternativeOutcome.Cancelled)

    def is_in_past(self):
        """ Returns True if the match date/datetime is in the past."""
        if self.time is not None:
            return self.datetime() < datetime.now()
        return self.date < datetime.today().date()

    def commentary_is_active(self):
        """
        Returns True if commentary should be active (i.e. if its within COMMENTARY_START_MINS of the start of
        the match and COMMENTARY_END_MINS after the end of the match)
        """
        if not self.time:
            return False
        now = datetime.now()
        match_dt = self.datetime()
        commentary_start = match_dt - \
            timedelta(minutes=Match.COMMENTARY_START_MINS)
        commentary_end = match_dt + \
            timedelta(minutes=Match.COMMENTARY_END_MINS)
        return (now >= commentary_start) and (now <= commentary_end)

    def time_display(self):
        """ Gets a formatted display of the match time.

            If the match time is not known, returns '???'
            if the match is in the past or 'TBD' if the match
            is in the future.
        """
        if self.time:
            return self.time.strftime('%H:%M')
        elif self.is_in_past():
            return '???'
        return 'TBD'

    def match_title_text(self):
        """ Gets an appropriate match title regardless of the status of the match.
            Examples include:
                "Men's 1sts thrash St Neots"
                "M1 vs St Neots Men's 1sts"
                "M1 vs St Neots Men's 1sts - POSTPONED"
                "M1 vs St Neots Men's 1sts - CANCELLED"
                "M1 3-0 St Neots Men's 1sts (WALK-OVER)"
                "M1 5-1 St Neots Men's 1sts"
        """
        if self.report_title:
            return self.report_title
        return self.fixture_title()

    def vs_title(self):
        """ e.g. "M1 vs St Neots Men's 1" """
        return "{} vs {}".format(self.our_team, self.opp_team)

    def fixture_title(self):
        """ Returns the title of this fixture in one of the following formats:
            Fixtures:- "M1 vs St Neots Men's 1"
            Results:-  "M1 3-0 St Neots Men's 1"
        """
        if self.alt_outcome == AlternativeOutcome.Walkover:
            return "{} {}-{} {} (WALK-OVER)".format(self.our_team,
                                                    self.our_score,
                                                    self.opp_score,
                                                    self.opp_team)

        elif self.alt_outcome == AlternativeOutcome.Abandoned:
            return "{} {}-{} {} (Abandoned)".format(self.our_team,
                                                    self.our_score,
                                                    self.opp_score,
                                                    self.opp_team)

        elif self.alt_outcome is not None:
            return "{} vs {} - {}".format(self.our_team, self.opp_team,
                                          self.get_alt_outcome_display())

        elif not self.final_scores_provided():
            return self.vs_title()

        return "{} {}-{} {}".format(self.our_team, self.our_score,
                                    self.opp_score, self.opp_team)

    @staticmethod
    def is_walkover_score(score1, score2):
        """ Checks if the given scores are valid walk-over scores.
            Valid results are 3-0, 5-0, 0-3, 0-5.
        """
        if score1 in (Match.WALKOVER_SCORE_W1, Match.WALKOVER_SCORE_W2):
            return score2 == Match.WALKOVER_SCORE_L
        elif score2 in (Match.WALKOVER_SCORE_W1, Match.WALKOVER_SCORE_W2):
            return score1 == Match.WALKOVER_SCORE_L
        return False

    def ht_scores_provided(self):
        """ Returns true if both half-time scores are not None."""
        return (self.our_ht_score is not None
                and self.opp_ht_score is not None)

    def final_scores_provided(self):
        """ Returns true if both full-time/final scores are not None."""
        return (self.our_score is not None and self.opp_score is not None)

    def all_scores_provided(self):
        """ Returns true if both half-time and full-time scores are provided."""
        return self.final_scores_provided() and self.ht_scores_provided()

    def was_won(self):
        """ Returns true if our team won the match. """
        return (self.final_scores_provided()
                and self.our_score > self.opp_score)

    def was_lost(self):
        """ Returns true if our team lost the match. """
        return (self.final_scores_provided()
                and self.our_score < self.opp_score)

    def was_drawn(self):
        """ Returns true if the match was drawn. """
        return (self.final_scores_provided()
                and self.our_score == self.opp_score)

    def score_display(self):
        """ Convenience method for displaying the score.
            Examples include:
            "3-2"         (normal result)
            ""            (blank - no result yet)
            "Cancelled"   (alt_outcome not None)
        """
        if self.alt_outcome is not None:
            return self.get_alt_outcome_display()
        if not self.final_scores_provided():
            return "-"
        return "{}-{}".format(self.our_score, self.opp_score)

    def result_display(self):
        """ Returns a textual representation of the result (or alternative outcome) """
        if self.final_scores_provided():
            if self.our_score > self.opp_score:
                return 'won'
            elif self.our_score < self.opp_score:
                return 'lost'
            else:
                return 'drawn'
        else:
            return self.get_alt_outcome_display()

    def simple_venue_name(self):
        """ Returns 'Away' if this is not a home match.
            Otherwise returns the short_name attribute value.
        """
        if self.venue:
            if self.is_home:
                if self.venue.short_name is not None:
                    return self.venue.short_name
                return self.venue.name
            return "Away"
        elif self.is_in_past():
            return '???'
        return 'TBD'