Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Imports 

2 

3from django.conf import settings 

4from django.db import models 

5from django.utils.timezone import now 

6from django.utils.translation import gettext_lazy as _ 

7import logging 

8from superdjango.db.lookups.models import StringLookupModel 

9from .constants import DEFAULT_ICONS, LEVEL, STAGE 

10 

11log = logging.getLogger(__name__) 

12 

13# Exports 

14 

15__all__ = ( 

16 "Category", 

17 "Notification", 

18 "NotificationUser", 

19 "UserPreferences", 

20) 

21 

22# Constants 

23 

24AUTH_USER_MODEL = settings.AUTH_USER_MODEL 

25 

26NOTIFICATION_ICONS = getattr(settings, "SUPERDJANGO_NOTIFICATION_ICONS", DEFAULT_ICONS) 

27"""Load notification icons from settings. Useful, for example, to include pro icons.""" 

28 

29# Models 

30 

31 

32class Category(StringLookupModel): 

33 """Notification categories.""" 

34 

35 class Meta: 

36 ordering = ["label"] 

37 verbose_name = _("Category") 

38 verbose_name_plural = _("Categories") 

39 

40 

41class Notification(models.Model): 

42 """A notification sent to a user.""" 

43 

44 action_text = models.CharField( 

45 _("action text"), 

46 blank=True, 

47 help_text=_('The text to display for the action. Defaults to "More" in templates.'), 

48 max_length=128, 

49 null=True 

50 ) 

51 

52 action_url = models.CharField( 

53 _("action URL"), 

54 blank=True, 

55 help_text=_("The URL to which users should be directed to take action or get more information."), 

56 max_length=1024, 

57 null=True 

58 ) 

59 

60 added_dt = models.DateTimeField( 

61 _("added date/time"), 

62 auto_now_add=True, 

63 help_text=_("Date and time the notification was added."), 

64 ) 

65 

66 category = models.ForeignKey( 

67 Category, 

68 blank=True, 

69 help_text=_("The category of the message."), 

70 limit_choices_to={'is_enabled': True}, 

71 null=True, 

72 on_delete=models.SET_NULL, 

73 related_name="notifications", 

74 verbose_name=_("category") 

75 ) 

76 

77 body = models.TextField( 

78 _("body"), 

79 help_text=_("The body of the message."), 

80 ) 

81 

82 body_sms = models.TextField( 

83 _("SMS body"), 

84 blank=True, 

85 help_text=_("The SMS/text body of the message."), 

86 null=True 

87 ) 

88 

89 email_enabled = models.BooleanField( 

90 _("email enabled"), 

91 default=False, 

92 help_text=_("Indicates the message should be sent via email.") 

93 ) 

94 

95 has_been_sent = models.BooleanField( 

96 _("sent"), 

97 default=False, 

98 help_text=_("Indicates the notification has been sent.") 

99 ) 

100 

101 html_enabled = models.BooleanField( 

102 _("HTML enabled"), 

103 default=False, 

104 help_text=_("Indicates HTML email output should be supported.") 

105 ) 

106 

107 icon_override = models.CharField( 

108 _("icon override"), 

109 blank=True, 

110 help_text=_("Override the default icon."), 

111 max_length=128, 

112 null=True 

113 ) 

114 

115 is_mandatory = models.BooleanField( 

116 _("mandatory"), 

117 default=False, 

118 help_text=_("Indicates the notification should be sent regardless of user preferences.") 

119 ) 

120 

121 # https://www.codexworld.com/create-top-notification-bar-html-css-jquery/ 

122 is_promoted = models.BooleanField( 

123 _("promoted"), 

124 default=False, 

125 help_text=_("Indicates the notification should be promoted to prominence.") 

126 ) 

127 

128 LEVEL_CHOICES = ( 

129 (LEVEL.DEBUG, _("Debug")), 

130 (LEVEL.INFO, _("Info")), 

131 (LEVEL.SUCCESS, _("Success")), 

132 (LEVEL.WARNING, _("Warning")), 

133 (LEVEL.ERROR, _("Error")), 

134 ) 

135 level = models.PositiveSmallIntegerField( 

136 _("level"), 

137 choices=LEVEL_CHOICES, 

138 default=LEVEL.INFO, 

139 help_text=_("The level of the notification.") 

140 ) 

141 

142 sent_dt = models.DateTimeField( 

143 _("sent date/time"), 

144 blank=True, 

145 help_text=_("Date and time the notification was sent."), 

146 null=True 

147 ) 

148 

149 sms_enabled = models.BooleanField( 

150 _("SMS enabled"), 

151 default=False, 

152 help_text=_("Indicates the message should be sent via SMS/text.") 

153 ) 

154 

155 STAGE_CHOICES = ( 

156 (STAGE.DRAFT, _("Draft")), 

157 (STAGE.QUEUED, _("Queued")), 

158 (STAGE.SENT, _("Sent")), 

159 (STAGE.ARCHIVED, _("Archived")), 

160 ) 

161 stage = models.PositiveSmallIntegerField( 

162 _("stage"), 

163 choices=STAGE_CHOICES, 

164 default=STAGE.QUEUED, 

165 help_text=_("The current stage of the notification.") 

166 ) 

167 

168 subject = models.CharField( 

169 _("subject"), 

170 help_text=_("The subject line of the message."), 

171 max_length=128 

172 ) 

173 

174 users = models.ManyToManyField( 

175 AUTH_USER_MODEL, 

176 blank=True, 

177 help_text=_("The users to which the notification is sent."), 

178 related_name="sd_notifications", 

179 verbose_name=_("users") 

180 ) 

181 

182 class Meta: 

183 get_latest_by = "added_dt" 

184 ordering = ["-added_dt"] 

185 verbose_name = _("Notification") 

186 verbose_name_plural = _("Notifications") 

187 

188 def __str__(self): 

189 return self.subject 

190 

191 @classmethod 

192 def create_for(cls, body, subject, users, action_text=None, action_url=None, body_sms=None, category=None, 

193 email_enabled=False, html_enabled=False, icon_override=None, is_mandatory=False, is_promoted=False, 

194 level=LEVEL.INFO, sms_enabled=False): 

195 """Create a notification for the given users. 

196 

197 :param body: Required. The body of the message. 

198 :type body: str 

199 

200 :param subject: Required. The subject line of the message. 

201 :type subject: str 

202 

203 :param users: An iterable of users to which the notification should be sent. 

204 

205 :param action_text: The action text to display for the URL. Defaults to "More" in templates. 

206 :type action_text: str 

207 

208 :param action_url: Optional. The action URL. 

209 :type action_url: str 

210 

211 :param body_sms: Optional. The SMS/text version of the message. 

212 :type body_sms: str 

213 

214 :param category: The message category. 

215 :type category: str | Category 

216 

217 :param email_enabled: Indicates email should be sent. Requires additional setup. 

218 :type email_enabled: bool 

219 

220 :param html_enabled: Indicates the body of the message is HTML. 

221 :type html_enabled: bool 

222 

223 :param icon_override: Overrides the default icon. 

224 :type icon_override: str 

225 

226 :param is_mandatory: Indicates the message should be sent regardless of user preferences. 

227 :type is_mandatory: bool 

228 

229 :param is_promoted: Indicates the message should be displayed with prominence. 

230 :type is_promoted: bool 

231 

232 :param level: The level of the message. 

233 :type level: int 

234 

235 :param sms_enabled: Indicates email should be sent. Requires additional setup. 

236 :type sms_enabled: bool 

237 

238 :rtype: Notification 

239 

240 .. warning:: 

241 This creates a notification that is immediately queued for delivery. 

242 

243 """ 

244 _category = None 

245 if category is not None: 

246 if isinstance(category, Category): 

247 _category = category 

248 else: 

249 try: 

250 _category = Category.objects.get(value=category) 

251 except Category.DoesNotExist: 

252 log.debug("Notification category does not exist: %s" % category) 

253 

254 notification = cls( 

255 action_text=action_text, 

256 action_url=action_url, 

257 body=body, 

258 body_sms=body_sms, 

259 category=_category, 

260 email_enabled=email_enabled, 

261 html_enabled=html_enabled, 

262 icon_override=icon_override, 

263 is_mandatory=is_mandatory, 

264 is_promoted=is_promoted, 

265 level=level, 

266 sms_enabled=sms_enabled, 

267 subject=subject 

268 ) 

269 notification.save() 

270 

271 for user in users: 

272 notification.users.add(user) 

273 

274 # for user in users: 

275 # link = NotificationUser( 

276 # notification=notification, 

277 # user=user 

278 # ) 

279 # link.save() 

280 

281 return notification 

282 

283 def get_icon(self): 

284 """Get the Font Awesome icon associated with the level. 

285 

286 :rtype: str 

287 

288 """ 

289 if self.icon_override: 

290 return self.icon_override 

291 

292 return NOTIFICATION_ICONS[self.level] 

293 

294 @property 

295 def icon(self): 

296 """Alias for ``get_icon()``.""" 

297 return self.get_icon() 

298 

299 def mark_sent(self, commit=True): 

300 """Mark the notification as sent. 

301 

302 :param commit: Save after updating the fields. 

303 :type commit: bool 

304 

305 """ 

306 self.has_been_sent = True 

307 self.sent_dt = now() 

308 self.stage = STAGE.SENT 

309 

310 if commit: 

311 self.save(update_fields=["has_been_sent", "sent_dt", "stage"]) 

312 

313 

314class NotificationUser(models.Model): 

315 """Track the users that have seen a notification.""" 

316 

317 has_been_viewed = models.BooleanField( 

318 _("viewed"), 

319 default=False, 

320 help_text=_("Indicates the notification has been viewed.") 

321 ) 

322 

323 notification = models.ForeignKey( 

324 Notification, 

325 help_text=_("The notification being tracked."), 

326 on_delete=models.CASCADE, 

327 related_name="user_views", 

328 verbose_name=_("notification") 

329 ) 

330 

331 sent_email = models.NullBooleanField( 

332 _("email sent"), 

333 help_text=_("Indicates whether an email was sent for this notification.") 

334 ) 

335 

336 sent_sms = models.NullBooleanField( 

337 _("SMS sent"), 

338 help_text=_("Indicates whether a SMS/text was sent for this notification.") 

339 ) 

340 

341 user = models.ForeignKey( 

342 AUTH_USER_MODEL, 

343 help_text=_("The user to which the notification is sent."), 

344 on_delete=models.CASCADE, 

345 related_name="notifications", 

346 verbose_name=_("user") 

347 ) 

348 

349 viewed_dt = models.DateTimeField( 

350 _("viewed date/time"), 

351 blank=True, 

352 help_text=_("Date and time the notification was viewed."), 

353 null=True 

354 ) 

355 

356 class Meta: 

357 get_latest_by = "notification__added_dt" 

358 ordering = ["-viewed_dt"] 

359 unique_together = ["notification", "user"] 

360 verbose_name = _("Notification View") 

361 verbose_name_plural = _("Notification Views") 

362 

363 def __str__(self): 

364 return "%s %s" % (self.notification.subject, self.user) 

365 

366 @property 

367 def action_url(self): 

368 """Alias on ``notification``.""" 

369 return self.notification.action_url 

370 

371 @property 

372 def added_dt(self): 

373 """Alias on ``notification``.""" 

374 return self.notification.added_dt 

375 

376 @property 

377 def body(self): 

378 """Alias on ``notification``.""" 

379 return self.notification.body 

380 

381 @property 

382 def body_sms(self): 

383 """Alias on ``notification``.""" 

384 return self.notification.body_sms 

385 

386 @property 

387 def email_enabled(self): 

388 """Alias on ``notification``.""" 

389 return self.notification.email_enabled 

390 

391 @property 

392 def has_been_sent(self): 

393 """Alias on ``notification``.""" 

394 return self.notification.has_been_sent 

395 

396 @property 

397 def icon(self): 

398 """Alias on ``notification``.""" 

399 return self.notification.get_icon() 

400 

401 @property 

402 def is_mandatory(self): 

403 """Alias on ``notification``.""" 

404 return self.notification.is_mandatory 

405 

406 @property 

407 def is_promoted(self): 

408 """Alias on ``notification``.""" 

409 return self.notification.is_promoted 

410 

411 @property 

412 def level(self): 

413 """Alias on ``notification``.""" 

414 return self.notification.level 

415 

416 def get_level_display(self): 

417 """Alias on ``notification``.""" 

418 return self.notification.get_level_display() 

419 

420 def mark_viewed(self, commit=True): 

421 """Mark the record as viewed. 

422 

423 :param commit: Indicates the record should be saved after updating the field. 

424 :type commit: bool 

425 

426 """ 

427 self.has_been_viewed = True 

428 self.viewed_dt = now() 

429 

430 if commit: 

431 self.save(update_fields=["has_been_viewed", "viewed_dt"]) 

432 

433 def mark_unviewed(self, commit=True): 

434 """Mark the record as "un-viewed", removing the last viewed data. 

435 

436 :param commit: Indicates the record should be saved after updating the field. 

437 :type commit: bool 

438 

439 """ 

440 self.has_been_viewed = False 

441 self.viewed_dt = None 

442 

443 if commit: 

444 self.save(update_fields=["has_been_viewed", "viewed_dt"]) 

445 

446 @property 

447 def sent_dt(self): 

448 """Alias on ``notification``.""" 

449 return self.notification.sent_dt 

450 

451 @property 

452 def sms_enabled(self): 

453 """Alias on ``notification``.""" 

454 return self.notification.sms_enabled 

455 

456 @property 

457 def stage(self): 

458 """Alias on ``notification``.""" 

459 return self.notification.stage 

460 

461 @property 

462 def subject(self): 

463 """Alias on ``notification``.""" 

464 return self.notification.subject 

465 

466 

467class UserPreferences(models.Model): 

468 """User notification preferences.""" 

469 

470 categories = models.ManyToManyField( 

471 Category, 

472 blank=True, 

473 help_text=_("The category or categories of notifications to be received."), 

474 limit_choices_to={'is_enabled': True}, 

475 related_name="user_preferences", 

476 verbose_name=_("categories") 

477 ) 

478 

479 email_enabled = models.BooleanField( 

480 _("email enabled"), 

481 default=True, 

482 help_text=_("Indicates email notifications are enabled. Some mandatory messages may be sent even " 

483 "when disabled.") 

484 ) 

485 

486 LEVEL_CHOICES = ( 

487 (LEVEL.DEBUG, _("Debug")), 

488 (LEVEL.INFO, _("Info")), 

489 (LEVEL.SUCCESS, _("Success")), 

490 (LEVEL.WARNING, _("Warning")), 

491 (LEVEL.ERROR, _("Error")), 

492 ) 

493 level = models.PositiveSmallIntegerField( 

494 _("level"), 

495 choices=LEVEL_CHOICES, 

496 default=LEVEL.ERROR, 

497 help_text=_("The minimum level of the notifications to receive.") 

498 ) 

499 

500 mobile_number = models.CharField( 

501 _("mobile number"), 

502 blank=True, 

503 help_text=_("The mobile number at which SMS/text notifications may be received. Note: Mobile charges " 

504 "may apply."), 

505 max_length=128, 

506 null=True 

507 ) 

508 

509 sms_enabled = models.BooleanField( 

510 _("SMS enabled"), 

511 default=False, 

512 help_text=_("Indicates messages may be sent via SMS/text.") 

513 ) 

514 

515 user = models.OneToOneField( 

516 AUTH_USER_MODEL, 

517 help_text=_("The user to which the preferences belong."), 

518 on_delete=models.CASCADE, 

519 related_name="notification_preferences", 

520 verbose_name=_("user") 

521 ) 

522 

523 class Meta: 

524 verbose_name = _("User Notification Preferences") 

525 verbose_name_plural = _("User Notification Preferences")