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

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 _ 

6 

7 

8class Document(models.Model): 

9 """Describes a base document.""" 

10 

11 class Meta: 

12 verbose_name = _("Document") 

13 verbose_name_plural = _("Documents") 

14 

15 DOCUMENT_CATEGORIES = ( 

16 ("annual", _("Annual document")), 

17 ("association", _("Association document")), 

18 ("event", _("Event document")), 

19 ("minutes", _("Minutes")), 

20 ("misc", _("Miscellaneous document")), 

21 ) 

22 

23 name = models.CharField(verbose_name=_("name"), max_length=200) 

24 

25 created = models.DateTimeField( 

26 verbose_name=_("created"), 

27 auto_now_add=True, 

28 ) 

29 

30 last_updated = models.DateTimeField(verbose_name=_("last updated"), auto_now=True) 

31 

32 category = models.CharField( 

33 max_length=40, 

34 choices=DOCUMENT_CATEGORIES, 

35 verbose_name=_("category"), 

36 default="misc", 

37 ) 

38 

39 file = models.FileField( 

40 verbose_name=_("file"), 

41 upload_to="documents/", 

42 validators=[FileExtensionValidator(["txt", "pdf", "jpg", "jpeg", "png"])], 

43 ) 

44 

45 members_only = models.BooleanField(verbose_name=_("members only"), default=True) 

46 

47 def get_absolute_url(self): 

48 return reverse("documents:document", kwargs={"pk": self.pk}) 

49 

50 def __str__(self): 

51 return f"{self.name} ({self.created.date()})" 

52 

53 

54class AnnualDocument(Document): 

55 """Describes an annual document.""" 

56 

57 class Meta: 

58 verbose_name = "Annual document" 

59 verbose_name_plural = "Annual documents" 

60 unique_together = ("subcategory", "year") 

61 

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" 

68 

69 subcategory = models.CharField( 

70 max_length=40, 

71 choices=Subcategory.choices, 

72 verbose_name="category", 

73 default=Subcategory.REPORT, 

74 ) 

75 

76 year = models.IntegerField( 

77 verbose_name="year", 

78 validators=[MinValueValidator(1990)], 

79 ) 

80 

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) 

94 

95 

96class AssociationDocumentManager(models.Manager): 

97 """Custom manager to filter for association documents.""" 

98 

99 def get_queryset(self): 

100 return super().get_queryset().filter(category="association") 

101 

102 

103class AssociationDocument(Document): 

104 """Describes an association document.""" 

105 

106 class Meta: 

107 verbose_name = _("Miscellaneous association document") 

108 verbose_name_plural = _("Miscellaneous association documents") 

109 proxy = True 

110 

111 objects = AssociationDocumentManager() 

112 

113 def save(self, **kwargs): 

114 self.category = "association" 

115 super().save(**kwargs) 

116 

117 

118class MiscellaneousDocumentManager(models.Manager): 

119 """Custom manager to filter for misc documents.""" 

120 

121 def get_queryset(self): 

122 return super().get_queryset().filter(category="misc") 

123 

124 

125class MiscellaneousDocument(Document): 

126 """Describes a miscellaneous document.""" 

127 

128 class Meta: 

129 ordering = ["-created"] 

130 verbose_name = _("Miscellaneous document") 

131 verbose_name_plural = _("Miscellaneous documents") 

132 proxy = True 

133 

134 objects = MiscellaneousDocumentManager() 

135 

136 def save(self, **kwargs): 

137 self.category = "misc" 

138 super().save(**kwargs) 

139 

140 

141class GeneralMeeting(models.Model): 

142 """Describes a general meeting.""" 

143 

144 class Meta: 

145 verbose_name = _("General meeting") 

146 verbose_name_plural = _("General meetings") 

147 ordering = ["datetime"] 

148 

149 documents = models.ManyToManyField( 

150 Document, 

151 verbose_name=_("documents"), 

152 blank=True, 

153 ) 

154 

155 datetime = models.DateTimeField( 

156 verbose_name=_("datetime"), 

157 ) 

158 

159 location = models.CharField(verbose_name=_("location"), max_length=200) 

160 

161 def __str__(self): 

162 return timezone.localtime(self.datetime).strftime("%Y-%m-%d") 

163 

164 

165class Minutes(Document): 

166 """Describes a minutes document.""" 

167 

168 class Meta: 

169 verbose_name = _("Minutes") 

170 verbose_name_plural = _("Minutes") 

171 

172 meeting = models.OneToOneField( 

173 GeneralMeeting, blank=True, null=True, on_delete=models.CASCADE 

174 ) 

175 

176 def save(self, **kwargs): 

177 self.category = "minutes" 

178 self.name = f"Minutes {self.meeting.datetime.date()}" 

179 super().save(**kwargs)