Coverage for sites/comments_site/comments_database/models.py: 97%

69 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-05-19 19:20 +0000

1from typing import Optional 

2 

3from django.contrib.auth.models import AbstractUser 

4from django.db import models 

5from django.utils.translation import gettext_lazy as _ 

6 

7from comments_api.constants import COMMENT_STATUS_CHOICES 

8from comments_api.constants import MODERATION_STATUS_CHOICES 

9from comments_api.constants import STATUS_SUBMITTED 

10from ptf.site_register import SITE_REGISTER 

11 

12 

13class User(AbstractUser): 

14 """ 

15 Custom user class. 

16 

17 A regular (API) user represents a mathdoc website. 

18 The username must match an entry in site_register.py for mathdoc websites. 

19 """ 

20 

21 first_name = None 

22 last_name = None 

23 url = models.URLField(_("URL"), max_length=258, unique=True, null=True, blank=True) 

24 mathdoc_site = models.BooleanField(_("Mathdoc site"), default=False) 

25 

26 class Meta: 

27 constraints = [ 

28 models.UniqueConstraint( 

29 fields=["mathdoc_site", "username"], name="unique_mathdoc_site" 

30 ) 

31 ] 

32 

33 def save(self, *args, **kwargs) -> None: 

34 if self.mathdoc_site: 

35 assert self.username in SITE_REGISTER, ( 

36 "ERROR: the user to save is flagged as a mathdoc site but its" 

37 "username does not match an entry in site_register.py" 

38 ) 

39 return super().save(*args, **kwargs) 

40 

41 

42class Moderator(models.Model): 

43 """ 

44 Represents a comment moderator. 

45 """ 

46 

47 id = models.IntegerField(_("Trammel user ID"), primary_key=True) 

48 

49 def __str__(self) -> str: 

50 return f"Trammel user #{self.id}" 

51 

52 

53class Comment(models.Model): 

54 """ 

55 Represents a Resource comment. 

56 """ 

57 

58 site = models.ForeignKey( 

59 User, on_delete=models.PROTECT, null=False, blank=False, related_name="comments" 

60 ) 

61 doi = models.CharField(max_length=64, null=False, blank=False) 

62 parent: "models.ForeignKey[Comment]" = models.ForeignKey( 

63 "self", on_delete=models.PROTECT, null=True, blank=True, related_name="children" 

64 ) # type:ignore 

65 date_submitted = models.DateTimeField(blank=True) 

66 date_last_modified = models.DateTimeField(blank=True) 

67 

68 # Author related data 

69 # Null values are allowed for author fields, corresponding to deleted comments 

70 # We do not create an author model because this data can vary for each comment 

71 # of the same author (mainly provider & provider UID but also e-mail) 

72 author_id = models.BigIntegerField(null=True, blank=False) 

73 author_email = models.EmailField(null=True, blank=False) 

74 author_first_name = models.CharField(max_length=128, null=True, blank=False) 

75 author_last_name = models.CharField(max_length=128, null=True, blank=False) 

76 author_provider = models.CharField(max_length=64, null=True, blank=False) 

77 author_provider_uid = models.CharField(max_length=128, null=True, blank=False) 

78 

79 # We store the both the raw, unsanitized HTML value of the comment 

80 # and the sanitized one. 

81 # The API should always return the sanitized version of this field. 

82 raw_html = models.TextField() 

83 sanitized_html = models.TextField() 

84 status = models.CharField( 

85 _("Status"), 

86 default=STATUS_SUBMITTED, 

87 choices=COMMENT_STATUS_CHOICES, 

88 blank=False, 

89 max_length=127, 

90 ) 

91 

92 # Moderator related 

93 moderators = models.ManyToManyField( 

94 Moderator, through="ModerationRights", related_name="comments" 

95 ) 

96 article_author_comment = models.BooleanField(default=False) 

97 editorial_team_comment = models.BooleanField(default=False) 

98 hide_author_name = models.BooleanField(default=False) 

99 

100 # Boolean used to flag "new" comments. The value is used and updated by a cron 

101 # script responsible for sending e-mail when there are comments with is_new = True 

102 is_new = models.BooleanField(default=True) 

103 # Boolean used to track if a comment has already been "Validated". As comments 

104 # can be re-moderated, we use it to not send an e-mail when a comment gets 

105 # validated again. 

106 validation_email_sent = models.BooleanField(default=False) 

107 

108 def get_base_url(self) -> str: 

109 """ 

110 Return the base_url of the website where the comment was posted. 

111 The URL is by default the one in site_register.py but it can be overriden 

112 with the "URL" field in the user form (useful for local and test env). 

113 """ 

114 url = self.site.url if self.site.url else SITE_REGISTER[self.site.username]["site_domain"] 

115 if url.endswith("/"): 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true

116 url = url[:-1] 

117 if not url.startswith("http"): 

118 url = f"https://{url}" 

119 return url 

120 

121 def __str__(self) -> str: 

122 return f"Comment {self.pk} - {self.doi}" 

123 

124 def author_full_name(self) -> str | None: 

125 """ 

126 Returns the author's actual full name. 

127 """ 

128 if self.author_first_name and self.author_last_name: 

129 return f"{self.author_first_name} {self.author_last_name}" 

130 return None 

131 

132 def get_moderators(self): 

133 return self.moderators.values_list("pk", flat=True) 

134 

135 def get_moderation_data(self) -> Optional["ModerationRights"]: 

136 """ 

137 Returns the moderation data. 

138 This corresponds to the published date of the comment for a validated comment. 

139 """ 

140 return ( 

141 self.moderationrights_set.filter(date_moderated__isnull=False, status=self.status) 

142 .order_by("date_moderated") 

143 .first() 

144 ) 

145 

146 

147class ModerationRights(models.Model): 

148 """ 

149 ManyToMany model between Moderator & Comment 

150 """ 

151 

152 moderator = models.ForeignKey(Moderator, on_delete=models.CASCADE) 

153 comment = models.ForeignKey(Comment, on_delete=models.CASCADE) 

154 date_created = models.DateTimeField(null=False, blank=False) 

155 date_moderated = models.DateTimeField(null=True, blank=True) 

156 status = models.CharField( 

157 _("Status"), choices=MODERATION_STATUS_CHOICES, blank=False, max_length=127 

158 ) 

159 

160 class Meta: 

161 unique_together = ["moderator", "comment"]