Coverage for website/members/models/membership.py: 46.15%
40 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1import datetime
3from django.conf import settings
4from django.core.exceptions import ValidationError
5from django.db import models
6from django.utils import timezone
7from django.utils.translation import gettext_lazy as _
8from django.utils.translation import pgettext_lazy
9from utils.snippets import overlaps
12class Membership(models.Model):
13 MEMBER = "member"
14 BENEFACTOR = "benefactor"
15 HONORARY = "honorary"
17 MEMBERSHIP_TYPES = (
18 (MEMBER, _("Member")),
19 (BENEFACTOR, _("Benefactor")),
20 (HONORARY, _("Honorary Member")),
21 )
23 type = models.CharField(
24 max_length=40,
25 choices=MEMBERSHIP_TYPES,
26 verbose_name=_("Membership type"),
27 )
29 user = models.ForeignKey(
30 settings.AUTH_USER_MODEL,
31 on_delete=models.CASCADE,
32 verbose_name=_("User"),
33 )
35 since = models.DateField(
36 verbose_name=_("Membership since"),
37 help_text=_("The date the member started holding this membership."),
38 default=datetime.date.today,
39 )
41 until = models.DateField(
42 verbose_name=_("Membership until"),
43 help_text=_("The date the member stops holding this membership."),
44 blank=True,
45 null=True, # This is only for honorary members
46 )
48 study_long = models.BooleanField(
49 verbose_name=_("Study long"),
50 help_text="Whether the member has paid to be member throughout their studies.",
51 default=False,
52 )
54 def __str__(self):
55 s = _("Membership of type {} for {} ({}) starting {}").format(
56 self.get_type_display(),
57 self.user.get_full_name(),
58 self.user.username,
59 self.since,
60 )
61 if self.until is not None:
62 s += pgettext_lazy("Membership until x", " until {}").format(self.until)
63 return s
65 def clean(self):
66 super().clean()
68 errors = {}
69 if self.until and (not self.since or self.until < self.since):
70 raise ValidationError({"until": "End date can't be before start date"})
72 memberships = self.user.membership_set.all()
73 if self.since is not None and overlaps(self, memberships):
74 errors.update(
75 {
76 "since": "A membership already exists for that period.",
77 "until": "A membership already exists for that period.",
78 }
79 )
81 if self.type != self.HONORARY and self.until is None:
82 errors.update({"until": "A non-honorary membership must have an end date."})
84 if self.type == self.BENEFACTOR and self.study_long:
85 errors.update(
86 {"study_long": "Benefactors cannot have a study long membership."}
87 )
89 if errors:
90 raise ValidationError(errors)
92 def is_active(self):
93 today = timezone.now().date()
94 return self.since <= today and (not self.until or self.until > today)