1# Copyright 2013 The Distro Tracker Developers 

2# See the COPYRIGHT file at the top-level directory of this distribution and 

3# at https://deb.li/DTAuthors 

4# 

5# This file is part of Distro Tracker. It is subject to the license terms 

6# in the LICENSE file found in the top-level directory of this 

7# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

8# including this file, may be copied, modified, propagated, or distributed 

9# except according to the terms contained in the LICENSE file. 

10""" 

11Defines models specific for the :py:mod:`distro_tracker.mail` app. 

12""" 

13from django.conf import settings 

14from django.db import models 

15 

16from distro_tracker.core.models import ( 

17 Confirmation, 

18 ConfirmationManager, 

19 UserEmail 

20) 

21 

22from django_email_accounts.models import UserEmailManager 

23 

24 

25class CommandConfirmationManager(ConfirmationManager): 

26 """ 

27 A custom manager for the :py:class:`CommandConfirmation` model. 

28 """ 

29 def create_for_commands(self, commands): 

30 """ 

31 Creates a :py:class:`CommandConfirmation` object for the given commands. 

32 

33 :param commands: An iterable of commands for which a confirmation is 

34 requested. 

35 :raises distro_tracker.mail.models.CommandConfirmationException: If it 

36 is unable to generate a unique key. 

37 """ 

38 commands = '\n'.join(commands) 

39 return self.create_confirmation(commands, **{ 

40 'commands': commands, 

41 }) 

42 

43 

44class CommandConfirmation(Confirmation): 

45 """ 

46 A model representing pending confirmations for email interface commands. 

47 """ 

48 commands = models.TextField() 

49 

50 objects = CommandConfirmationManager() 

51 

52 def __str__(self): 

53 return self.commands 

54 

55 @property 

56 def command_list(self): 

57 """ 

58 :return: A list of strings representing commands which are confirmed 

59 by this instance. 

60 """ 

61 return self.commands.splitlines() 

62 

63 

64class UserEmailBounceStatsManager(UserEmailManager): 

65 """ 

66 A custom :py:class:`Manager <django.db.models.Manager>` for the 

67 :py:class:`UserEmailBounceStats` model. 

68 """ 

69 def get_bounce_stats(self, email, date): 

70 """ 

71 Gets the :py:class:`UserEmailBounceStats` instance for the given 

72 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` on the 

73 given ``date`` 

74 

75 :param email: The email of the 

76 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` 

77 :type email: string 

78 

79 :param date: The date of the required stats 

80 :type date: :py:class:`datetime.datetime` 

81 """ 

82 user = self.get(email__iexact=email) 

83 bounce_stats, created = user.bouncestats_set.get_or_create(date=date) 

84 if created: 

85 self.limit_bounce_information(email) 

86 return bounce_stats 

87 

88 def add_bounce_for_user(self, email, date): 

89 """ 

90 Registers a bounced email for a given 

91 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` 

92 

93 :param email: The email of the 

94 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` for 

95 which a bounce will be logged 

96 :type email: string 

97 

98 :param date: The date of the bounce 

99 :type date: :py:class:`datetime.datetime` 

100 """ 

101 bounce_stats = self.get_bounce_stats(email, date) 

102 bounce_stats.mails_bounced += 1 

103 bounce_stats.save() 

104 

105 def add_sent_for_user(self, email, date): 

106 """ 

107 Registers a sent email for a given 

108 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` 

109 

110 :param email: The email of the 

111 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` for 

112 which a sent email will be logged 

113 :type email: string 

114 

115 :param date: The date of the sent email 

116 :type date: :py:class:`datetime.datetime` 

117 """ 

118 bounce_stats = self.get_bounce_stats(email, date) 

119 bounce_stats.mails_sent += 1 

120 bounce_stats.save() 

121 

122 def limit_bounce_information(self, email): 

123 """ 

124 Makes sure not to keep more records than the number of days set by 

125 DISTRO_TRACKER_MAX_DAYS_TOLERATE_BOUNCE 

126 """ 

127 user = self.get(email__iexact=email) 

128 days = settings.DISTRO_TRACKER_MAX_DAYS_TOLERATE_BOUNCE 

129 for info in user.bouncestats_set.all()[days:]: 

130 info.delete() 

131 

132 

133class UserEmailBounceStats(UserEmail): 

134 """ 

135 A proxy model for the :py:class:`UserEmail 

136 <distro_tracker.core.models.UserEmail>` model. 

137 It is defined in order to implement additional bounce stats-related 

138 methods without needlessly adding them to the public interface of 

139 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>` when only the 

140 :py:mod:`distro_tracker.mail.dispatch` app should use them. 

141 """ 

142 class Meta: 

143 proxy = True 

144 

145 objects = UserEmailBounceStatsManager() 

146 

147 def has_too_many_bounces(self): 

148 """ 

149 Checks if the user has too many bounces. 

150 """ 

151 days = settings.DISTRO_TRACKER_MAX_DAYS_TOLERATE_BOUNCE 

152 count = 0 

153 for stats in self.bouncestats_set.all()[:days]: 

154 # If no mails were sent on a particular day nothing could bounce 

155 if stats.mails_sent: 

156 if stats.mails_bounced >= stats.mails_sent: 

157 count += 1 

158 return count == days 

159 

160 

161class BounceStats(models.Model): 

162 """ 

163 A model representing a user's bounce statistics. 

164 

165 It stores the number of sent and bounced mails for a particular date. 

166 """ 

167 user_email = models.ForeignKey(UserEmailBounceStats, 

168 on_delete=models.CASCADE) 

169 mails_sent = models.IntegerField(default=0) 

170 mails_bounced = models.IntegerField(default=0) 

171 date = models.DateField() 

172 

173 class Meta: 

174 ordering = ['-date'] 

175 unique_together = ('user_email', 'date') 

176 

177 def __str__(self): 

178 return ( 

179 'Got {bounced} bounces out of {sent} ' 

180 'mails to {email} on {date}'.format( 

181 email=self.user_email, 

182 date=self.date, 

183 sent=self.mails_sent, 

184 bounced=self.mails_bounced) 

185 )