Coverage for website/partners/models.py: 44.86%

145 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1from django.core.exceptions import ValidationError 

2from django.core.files.storage import storages 

3from django.core.validators import RegexValidator, URLValidator 

4from django.db import models 

5from django.urls import reverse 

6from django.utils.translation import gettext 

7from django.utils.translation import gettext_lazy as _ 

8 

9from thumbnails.fields import ImageField 

10from tinymce.models import HTMLField 

11 

12from utils import countries 

13from utils.media.services import get_upload_to_function 

14 

15 

16class Partner(models.Model): 

17 """Model describing partner.""" 

18 

19 is_active = models.BooleanField(default=False) 

20 is_main_partner = models.BooleanField(default=False) 

21 is_local_partner = models.BooleanField(default=False) 

22 name = models.CharField(max_length=255) 

23 slug = models.SlugField(unique=True) 

24 link = models.CharField(max_length=255, blank=True, validators=[URLValidator()]) 

25 company_profile = HTMLField(blank=True) 

26 

27 logo = ImageField( 

28 upload_to=get_upload_to_function("partners/logos/"), 

29 resize_source_to="source_png", 

30 storage=storages["public"], 

31 ) 

32 

33 alternate_logo = ImageField( 

34 upload_to=get_upload_to_function("partners/logos/"), 

35 resize_source_to="source_png", 

36 storage=storages["public"], 

37 blank=True, 

38 null=True, 

39 help_text=_( 

40 "If set, this logo will be shown on the frontpage banner. Please use files with proper transparency." 

41 ), 

42 ) 

43 

44 site_header = ImageField( 

45 upload_to=get_upload_to_function("partners/headers/"), 

46 resize_source_to="source_png", 

47 storage=storages["public"], 

48 null=True, 

49 blank=True, 

50 ) 

51 

52 address = models.CharField( 

53 max_length=100, 

54 validators=[ 

55 RegexValidator( 

56 regex=r"^([^/@:;%_]+) \d+([^/@:;%_]*)", 

57 message="Enter a valid address", 

58 ) 

59 ], 

60 ) 

61 

62 address2 = models.CharField( 

63 max_length=100, 

64 verbose_name=_("Second address line"), 

65 blank=True, 

66 null=True, 

67 ) 

68 

69 zip_code = models.CharField( 

70 max_length=12, 

71 ) 

72 

73 city = models.CharField(max_length=100) 

74 

75 country = models.CharField( 

76 max_length=2, choices=countries.EUROPE, verbose_name=_("Country") 

77 ) 

78 

79 def __init__(self, *args, **kwargs): 

80 super().__init__(*args, **kwargs) 

81 self._orig_logo = self.logo.name if self.logo else None 

82 self._orig_site_header = self.site_header.name if self.site_header else None 

83 

84 def delete(self, using=None, keep_parents=False): 

85 if self.logo.name: 

86 self.logo.delete() 

87 if self.site_header.name: 

88 self.site_header.delete() 

89 return super().delete(using, keep_parents) 

90 

91 def save(self, **kwargs): 

92 """Save a partner and set main/local partners.""" 

93 if self.is_main_partner: 

94 self.is_local_partner = False 

95 self._reset_main_partner() 

96 

97 super().save(**kwargs) 

98 

99 if self._orig_logo and self._orig_logo != self.logo.name: 

100 self.logo.storage.delete(self._orig_logo) 

101 self._orig_logo = None 

102 if self._orig_site_header and self._orig_site_header != self.site_header.name: 

103 self.site_header.storage.delete(self._orig_site_header) 

104 self._orig_site_header = None 

105 

106 def _reset_main_partner(self): 

107 """Reset the main partner status. 

108 

109 If this partner is not main partner, 

110 remove the main partner status from the main partner. 

111 """ 

112 try: 

113 current_main_partner = Partner.objects.get(is_main_partner=True) 

114 if self != current_main_partner: 

115 current_main_partner.is_main_partner = False 

116 current_main_partner.save() 

117 except Partner.DoesNotExist: 

118 pass 

119 

120 def __str__(self): 

121 """Return the name of the partner.""" 

122 return str(self.name) 

123 

124 def get_absolute_url(self): 

125 """Return the url of the partner page.""" 

126 return reverse("partners:partner", args=(self.slug,)) 

127 

128 class Meta: 

129 """Meta class for partner model.""" 

130 

131 ordering = ("name",) 

132 

133 

134class PartnerImage(models.Model): 

135 """Model to save partner image.""" 

136 

137 partner = models.ForeignKey( 

138 Partner, on_delete=models.CASCADE, related_name="images" 

139 ) 

140 image = ImageField( 

141 upload_to="partners/images/", 

142 resize_source_to="source_png", 

143 storage=storages["public"], 

144 ) 

145 

146 def __init__(self, *args, **kwargs): 

147 super().__init__(*args, **kwargs) 

148 self._orig_image = self.image.name if self.image else None 

149 

150 def delete(self, using=None, keep_parents=False): 

151 if self.image.name: 

152 self.image.delete() 

153 return super().delete(using, keep_parents) 

154 

155 def save(self, **kwargs): 

156 super().save(**kwargs) 

157 

158 if self._orig_image and self._orig_image != self.image.name: 

159 self.image.storage.delete(self._orig_image) 

160 self._orig_image = None 

161 

162 def __str__(self): 

163 """Return string representation of partner name.""" 

164 return gettext("image of {}").format(self.partner.name) 

165 

166 

167class VacancyCategory(models.Model): 

168 """Model describing vacancy categories.""" 

169 

170 name = models.CharField(max_length=30) 

171 slug = models.SlugField(unique=True) 

172 

173 def __str__(self): 

174 """Return the category name.""" 

175 return str(self.name) 

176 

177 class Meta: 

178 """Meta class for vacancy category model.""" 

179 

180 verbose_name_plural = _("Vacancy Categories") 

181 

182 

183class Vacancy(models.Model): 

184 """Model describing vacancies.""" 

185 

186 title = models.CharField(_("title"), max_length=255) 

187 description = HTMLField(_("description")) 

188 link = models.CharField( 

189 _("link"), max_length=255, blank=True, validators=[URLValidator()] 

190 ) 

191 location = models.CharField(_("location"), max_length=255, null=True, blank=True) 

192 keywords = models.TextField( 

193 _("keywords"), 

194 default="", 

195 help_text="Comma separated list of keywords, for example: " 

196 "Django,Python,Concrexit", 

197 blank=True, 

198 ) 

199 

200 partner = models.ForeignKey( 

201 Partner, 

202 verbose_name=_("partner"), 

203 on_delete=models.PROTECT, 

204 null=True, 

205 blank=True, 

206 help_text=_( 

207 "When you use a partner, the company name and logo " 

208 "below will not be used." 

209 ), 

210 ) 

211 

212 company_name = models.CharField(_("company name"), max_length=255, blank=True) 

213 company_logo = ImageField( 

214 _("company logo"), 

215 upload_to="partners/vacancy-logos/", 

216 resize_source_to="source_png", 

217 storage=storages["public"], 

218 null=True, 

219 blank=True, 

220 ) 

221 

222 categories = models.ManyToManyField(VacancyCategory, blank=True) 

223 

224 def get_company_name(self): 

225 """Return company or partner name.""" 

226 if self.partner: 226 ↛ 227line 226 didn't jump to line 227 because the condition on line 226 was never true

227 return self.partner.name 

228 return self.company_name 

229 

230 def get_company_logo(self): 

231 """Return company or partner logo.""" 

232 if self.partner: 

233 return self.partner.logo 

234 return self.company_logo 

235 

236 def __str__(self): 

237 """Return vacancy partner or company and title.""" 

238 return f"{self.get_company_name()}{self.title}" 

239 

240 def get_absolute_url(self): 

241 """Return partner or vacancy url.""" 

242 url = reverse("partners:vacancies") 

243 if self.partner and self.partner.is_active: 

244 url = reverse("partners:partner", args=(self.partner.slug,)) 

245 return f"{url}#vacancy-{self.pk}" 

246 

247 def __init__(self, *args, **kwargs): 

248 super().__init__(*args, **kwargs) 

249 self._orig_logo = self.company_logo.name if self.company_logo else None 

250 

251 def delete(self, using=None, keep_parents=False): 

252 if self.company_logo.name: 

253 self.company_logo.delete() 

254 return super().delete(using, keep_parents) 

255 

256 def save(self, **kwargs): 

257 super().save(**kwargs) 

258 

259 if self._orig_logo and self._orig_logo != self.company_logo.name: 

260 self.company_logo.storage.delete(self._orig_logo) 

261 self._orig_logo = None 

262 

263 def clean(self): 

264 """Validate the vacancy.""" 

265 super().clean() 

266 errors = {} 

267 

268 msg = _("If no partner is used then both a company name and logo are required.") 

269 if not self.partner and self.company_name and not self.company_logo: 

270 errors.update({"company_logo": msg}) 

271 if not self.partner and not self.company_name and self.company_logo: 

272 errors.update({"company_name": msg}) 

273 

274 msg = _("Either select a partner or provide a company name and logo.") 

275 if self.partner and (self.company_name or self.company_logo): 

276 errors.update({"partner": msg}) 

277 if self.company_name: 

278 errors.update({"company_name": msg}) 

279 if self.company_logo: 

280 errors.update({"company_logo": msg}) 

281 if not self.partner and not self.company_name and not self.company_logo: 

282 errors.update( 

283 { 

284 "partner": msg, 

285 "company_name": msg, 

286 "company_logo": msg, 

287 } 

288 ) 

289 

290 if errors: 

291 raise ValidationError(errors) 

292 

293 class Meta: 

294 """Meta class for vacancy model.""" 

295 

296 ordering = ["-pk"] 

297 verbose_name_plural = _("Vacancies")