Coverage for website/documents/models.py: 71.13%
89 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
1from django.core.validators import FileExtensionValidator, MinValueValidator
2from django.db import models
3from django.urls import reverse
4from django.utils import timezone
5from django.utils.translation import gettext_lazy as _
8class Document(models.Model):
9 """Describes a base document."""
11 class Meta:
12 verbose_name = _("Document")
13 verbose_name_plural = _("Documents")
15 DOCUMENT_CATEGORIES = (
16 ("annual", _("Annual document")),
17 ("association", _("Association document")),
18 ("event", _("Event document")),
19 ("minutes", _("Minutes")),
20 ("misc", _("Miscellaneous document")),
21 )
23 name = models.CharField(verbose_name=_("name"), max_length=200)
25 created = models.DateTimeField(
26 verbose_name=_("created"),
27 auto_now_add=True,
28 )
30 last_updated = models.DateTimeField(verbose_name=_("last updated"), auto_now=True)
32 category = models.CharField(
33 max_length=40,
34 choices=DOCUMENT_CATEGORIES,
35 verbose_name=_("category"),
36 default="misc",
37 )
39 file = models.FileField(
40 verbose_name=_("file"),
41 upload_to="documents/",
42 validators=[FileExtensionValidator(["txt", "pdf", "jpg", "jpeg", "png"])],
43 )
45 members_only = models.BooleanField(verbose_name=_("members only"), default=True)
47 def get_absolute_url(self):
48 return reverse("documents:document", kwargs={"pk": self.pk})
50 def __str__(self):
51 return f"{self.name} ({self.created.date()})"
54class AnnualDocument(Document):
55 """Describes an annual document."""
57 class Meta:
58 verbose_name = "Annual document"
59 verbose_name_plural = "Annual documents"
60 unique_together = ("subcategory", "year")
62 class Subcategory(models.TextChoices):
63 REPORT = "report", "Annual report"
64 FINANCIAL = "financial", "Financial report"
65 POLICY = "policy", "Policy document"
66 SEMI_REPORT = "semi-report", "Semi-annual report"
67 SEMI_FINANCIAL = "semi-financial", "Semi-annual financial report"
69 subcategory = models.CharField(
70 max_length=40,
71 choices=Subcategory.choices,
72 verbose_name="category",
73 default=Subcategory.REPORT,
74 )
76 year = models.IntegerField(
77 verbose_name="year",
78 validators=[MinValueValidator(1990)],
79 )
81 def save(self, **kwargs):
82 self.category = "annual"
83 if self.subcategory == AnnualDocument.Subcategory.REPORT:
84 self.name = f"Annual report {self.year}"
85 elif self.subcategory == AnnualDocument.Subcategory.FINANCIAL:
86 self.name = f"Financial report {self.year}"
87 elif self.subcategory == AnnualDocument.Subcategory.POLICY:
88 self.name = f"Policy document {self.year}"
89 elif self.subcategory == AnnualDocument.Subcategory.SEMI_REPORT:
90 self.name = f"Semi-annual report {self.year}"
91 else:
92 self.name = f"Semi-annual financial report {self.year}"
93 super().save(**kwargs)
96class AssociationDocumentManager(models.Manager):
97 """Custom manager to filter for association documents."""
99 def get_queryset(self):
100 return super().get_queryset().filter(category="association")
103class AssociationDocument(Document):
104 """Describes an association document."""
106 class Meta:
107 verbose_name = _("Miscellaneous association document")
108 verbose_name_plural = _("Miscellaneous association documents")
109 proxy = True
111 objects = AssociationDocumentManager()
113 def save(self, **kwargs):
114 self.category = "association"
115 super().save(**kwargs)
118class MiscellaneousDocumentManager(models.Manager):
119 """Custom manager to filter for misc documents."""
121 def get_queryset(self):
122 return super().get_queryset().filter(category="misc")
125class MiscellaneousDocument(Document):
126 """Describes a miscellaneous document."""
128 class Meta:
129 ordering = ["-created"]
130 verbose_name = _("Miscellaneous document")
131 verbose_name_plural = _("Miscellaneous documents")
132 proxy = True
134 objects = MiscellaneousDocumentManager()
136 def save(self, **kwargs):
137 self.category = "misc"
138 super().save(**kwargs)
141class GeneralMeeting(models.Model):
142 """Describes a general meeting."""
144 class Meta:
145 verbose_name = _("General meeting")
146 verbose_name_plural = _("General meetings")
147 ordering = ["datetime"]
149 documents = models.ManyToManyField(
150 Document,
151 verbose_name=_("documents"),
152 blank=True,
153 )
155 datetime = models.DateTimeField(
156 verbose_name=_("datetime"),
157 )
159 location = models.CharField(verbose_name=_("location"), max_length=200)
161 def __str__(self):
162 return timezone.localtime(self.datetime).strftime("%Y-%m-%d")
165class Minutes(Document):
166 """Describes a minutes document."""
168 class Meta:
169 verbose_name = _("Minutes")
170 verbose_name_plural = _("Minutes")
172 meeting = models.OneToOneField(
173 GeneralMeeting, blank=True, null=True, on_delete=models.CASCADE
174 )
176 def save(self, **kwargs):
177 self.category = "minutes"
178 self.name = f"Minutes {self.meeting.datetime.date()}"
179 super().save(**kwargs)