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
16from distro_tracker.core.models import (
17 Confirmation,
18 ConfirmationManager,
19 UserEmail
20)
22from django_email_accounts.models import UserEmailManager
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.
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 })
44class CommandConfirmation(Confirmation):
45 """
46 A model representing pending confirmations for email interface commands.
47 """
48 commands = models.TextField()
50 objects = CommandConfirmationManager()
52 def __str__(self):
53 return self.commands
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()
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``
75 :param email: The email of the
76 :py:class:`UserEmail <distro_tracker.core.models.UserEmail>`
77 :type email: string
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
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>`
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
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()
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>`
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
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()
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()
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
145 objects = UserEmailBounceStatsManager()
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
161class BounceStats(models.Model):
162 """
163 A model representing a user's bounce statistics.
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()
173 class Meta:
174 ordering = ['-date']
175 unique_together = ('user_email', 'date')
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 )