We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[v18] Date incorrecte dans les notifications
J'ai reçu une notification pour un topic que je suivais avant la v18.

Selon la notification elle datait d'il y'a 7h, sauf qu'en cliquant dessus, je suis tombé sur un message qui date d'il y'a 7 minutes. On peut le voir sur les screens ci-dessous.

**Notification avant le clic** : 

![capture d ecran de 2016-05-28 09-03-31](https://cloud.githubusercontent.com/assets/6066015/15625756/7d2d85ba-24b3-11e6-8537-0055b9ebc892.png)

**Notification après le clic** : 

![capture d ecran de 2016-05-28 09-03-48](https://cloud.githubusercontent.com/assets/6066015/15625757/81da4634-24b3-11e6-9cfd-22e48d7535e2.png)

Il faudrait retomber sur ce cas pour identifier facilement le bug. Si jamais j'ai une meilleur idée du truc j'édite le titre qui n'est pas très parlant.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zds/notification/models.py`
Content:
```
1 # -*- coding: utf-8 -*-s
2 from smtplib import SMTPException
3 
4 from django.contrib.auth.models import User
5 from django.contrib.contenttypes.fields import GenericForeignKey
6 from django.contrib.contenttypes.models import ContentType
7 from django.core.mail import EmailMultiAlternatives
8 from django.db import models
9 from django.template.loader import render_to_string
10 from django.utils.translation import ugettext_lazy as _
11 
12 from zds import settings
13 from zds.forum.models import Topic
14 from zds.notification.managers import NotificationManager, SubscriptionManager, TopicFollowedManager, \
15     TopicAnswerSubscriptionManager
16 from zds.utils.misc import convert_camel_to_underscore
17 
18 
19 class Subscription(models.Model):
20     """
21     Model used to register the subscription of a user to a set of notifications (regarding a tutorial, a forum, ...)
22     """
23 
24     class Meta:
25         verbose_name = _(u'Abonnement')
26         verbose_name_plural = _(u'Abonnements')
27 
28     user = models.ForeignKey(User, related_name='subscriber', db_index=True)
29     pubdate = models.DateTimeField(_(u'Date de création'), auto_now_add=True, db_index=True)
30     is_active = models.BooleanField(_(u'Actif'), default=True, db_index=True)
31     by_email = models.BooleanField(_(u'Recevoir un email'), default=False)
32     content_type = models.ForeignKey(ContentType)
33     object_id = models.PositiveIntegerField(db_index=True)
34     content_object = GenericForeignKey('content_type', 'object_id')
35     last_notification = models.ForeignKey(u'Notification', related_name="last_notification", null=True, default=None)
36 
37     def __unicode__(self):
38         return _(u'<Abonnement du membre "{0}" aux notifications pour le {1}, #{2}>')\
39             .format(self.user.username, self.content_type, self.object_id)
40 
41     def activate(self):
42         """
43         Activates the subscription if it's inactive. Does nothing otherwise
44         """
45         if not self.is_active:
46             self.is_active = True
47             self.save()
48 
49     def activate_email(self):
50         """
51         Activates the notifications by email (and the subscription itself if needed)
52         """
53         if not self.is_active or not self.by_email:
54             self.is_active = True
55             self.by_email = True
56             self.save()
57 
58     def deactivate(self):
59         """
60         Deactivate the subscription if it is active. Does nothing otherwise
61         """
62         if self.is_active:
63             self.is_active = False
64             self.save()
65 
66     def deactivate_email(self):
67         """
68         Deactivate the email if it is active. Does nothing otherwise
69         """
70         if self.is_active and self.by_email:
71             self.by_email = False
72             self.save()
73 
74     def set_last_notification(self, notification):
75         """
76         Replace last_notification by the one given
77         """
78         self.last_notification = notification
79         self.save()
80 
81     def send_email(self, notification):
82         """
83         Sends an email notification
84         """
85 
86         assert hasattr(self, "module")
87 
88         subject = _(u"{} - {} : {}").format(settings.ZDS_APP['site']['litteral_name'], self.module, notification.title)
89         from_email = _(u"{} <{}>").format(settings.ZDS_APP['site']['litteral_name'],
90                                           settings.ZDS_APP['site']['email_noreply'])
91 
92         receiver = self.user
93         context = {
94             'username': receiver.username,
95             'title': notification.title,
96             'url': settings.ZDS_APP['site']['url'] + notification.url,
97             'author': notification.sender.username,
98             'site_name': settings.ZDS_APP['site']['litteral_name']
99         }
100         message_html = render_to_string(
101             'email/notification/' + convert_camel_to_underscore(self._meta.object_name) + '.html', context)
102         message_txt = render_to_string(
103             'email/notification/' + convert_camel_to_underscore(self._meta.object_name) + '.txt', context)
104 
105         msg = EmailMultiAlternatives(subject, message_txt, from_email, [receiver.email])
106         msg.attach_alternative(message_html, "text/html")
107         try:
108             msg.send()
109         except SMTPException:
110             pass
111 
112 
113 class SingleNotificationMixin(object):
114     """
115     Mixin for the subscription that can only have one active notification at a time
116     """
117     def send_notification(self, content=None, send_email=True, sender=None):
118         """
119         Sends the notification about the given content
120         :param content:  the content the notification is about
121         :param sender: the user whose action triggered the notification
122         :param send_email : whether an email must be sent if the subscription by email is active
123         """
124         assert hasattr(self, 'last_notification')
125         assert hasattr(self, 'set_last_notification')
126         assert hasattr(self, 'get_notification_url')
127         assert hasattr(self, 'get_notification_title')
128         assert hasattr(self, 'send_email')
129 
130         if self.last_notification is None or self.last_notification.is_read:
131             # If there isn't a notification yet or the last one is read, we generate a new one.
132             try:
133                 notification = Notification.objects.get(subscription=self)
134             except Notification.DoesNotExist:
135                 notification = Notification(subscription=self, content_object=content, sender=sender)
136             notification.content_object = content
137             notification.sender = sender
138             notification.url = self.get_notification_url(content)
139             notification.title = self.get_notification_title(content)
140             notification.is_read = False
141             notification.save()
142             self.set_last_notification(notification)
143             self.save()
144 
145             if send_email and self.by_email:
146                 self.send_email(notification)
147         elif self.last_notification is not None:
148             # Update last notification if the new content is older (case of unreading answer)
149             if not self.last_notification.is_read and self.last_notification.pubdate > content.pubdate:
150                 self.last_notification.content_object = content
151                 self.last_notification.save()
152 
153     def mark_notification_read(self):
154         """
155         Marks the notification of the subscription as read.
156         As there's only one active unread notification at all time,
157         no need for more precision
158         """
159         if self.last_notification is not None:
160             self.last_notification.is_read = True
161             self.last_notification.save()
162 
163 
164 class MultipleNotificationsMixin(object):
165 
166     def send_notification(self, content=None, send_email=True, sender=None):
167         """
168         Sends the notification about the given content
169         :param content:  the content the notification is about
170         :param sender: the user whose action triggered the notification
171         :param send_email : whether an email must be sent if the subscription by email is active
172         """
173 
174         assert hasattr(self, "get_notification_url")
175         assert hasattr(self, "get_notification_title")
176         assert hasattr(self, "send_email")
177 
178         notification = Notification(subscription=self, content_object=content, sender=sender)
179         notification.url = self.get_notification_url(content)
180         notification.title = self.get_notification_title(content)
181         notification.save()
182         self.set_last_notification(notification)
183 
184         if send_email and self.by_email:
185             self.send_email(notification)
186 
187     def mark_notification_read(self, content):
188         """
189         Marks the notification of the subscription as read.
190         :param content : the content whose notification has been read
191         """
192         if content is None:
193             raise Exception('Object content of notification must be defined')
194 
195         content_notification_type = ContentType.objects.get_for_model(content)
196         try:
197             notification = Notification.objects.get(subscription=self,
198                                                     content_type__pk=content_notification_type.pk,
199                                                     object_id=content.pk, is_read=False)
200             if notification is not None:
201                 notification.is_read = True
202                 notification.save()
203         except Notification.DoesNotExist:
204             pass
205 
206 
207 class AnswerSubscription(Subscription):
208     """
209     Subscription to new answer, either in a topic, a article or a tutorial
210     NOT used directly, use one of its subtype
211     """
212     def __unicode__(self):
213         return _(u'<Abonnement du membre "{0}" aux réponses au {1} #{2}>')\
214             .format(self.user.username, self.content_type, self.object_id)
215 
216     def get_notification_url(self, answer):
217         return answer.get_absolute_url()
218 
219     def get_notification_title(self, answer):
220         return self.content_object.title
221 
222 
223 class TopicAnswerSubscription(AnswerSubscription, SingleNotificationMixin):
224     """
225     Subscription to new answer in a topic
226     """
227     module = _(u'Forum')
228     objects = TopicAnswerSubscriptionManager()
229 
230     def __unicode__(self):
231         return _(u'<Abonnement du membre "{0}" aux réponses au sujet #{1}>')\
232             .format(self.user.username, self.object_id)
233 
234 
235 class PrivateTopicAnswerSubscription(AnswerSubscription, SingleNotificationMixin):
236     """
237     Subscription to new answer in a private topic.
238     """
239     module = _(u'Message privé')
240     objects = SubscriptionManager()
241 
242     def __unicode__(self):
243         return _(u'<Abonnement du membre "{0}" aux réponses à la conversation privée #{1}>')\
244             .format(self.user.username, self.object_id)
245 
246 
247 class ContentReactionAnswerSubscription(AnswerSubscription, SingleNotificationMixin):
248     """
249     Subscription to new answer in a publishable content.
250     """
251     module = _(u'Contenu')
252     objects = SubscriptionManager()
253 
254     def __unicode__(self):
255         return _(u'<Abonnement du membre "{0}" aux réponses du contenu #{1}>')\
256             .format(self.user.username, self.object_id)
257 
258 
259 class Notification(models.Model):
260     """
261     A notification
262     """
263     class Meta:
264         verbose_name = _(u'Notification')
265         verbose_name_plural = _(u'Notifications')
266 
267     subscription = models.ForeignKey(Subscription, related_name='subscription', db_index=True)
268     pubdate = models.DateTimeField(_(u'Date de création'), auto_now_add=True, db_index=True)
269     content_type = models.ForeignKey(ContentType)
270     object_id = models.PositiveIntegerField(db_index=True)
271     content_object = GenericForeignKey('content_type', 'object_id')
272     is_read = models.BooleanField(_(u'Lue'), default=False, db_index=True)
273     url = models.CharField('URL', max_length=255)
274     sender = models.ForeignKey(User, related_name='sender', db_index=True)
275     title = models.CharField('Titre', max_length=200)
276     objects = NotificationManager()
277 
278     def __unicode__(self):
279         return _(u'Notification du membre "{0}" à propos de : {1} #{2} ({3})')\
280             .format(self.subscription.user, self.content_type, self.content_object.pk, self.subscription)
281 
282 
283 class TopicFollowed(models.Model):
284     """
285     This model tracks which user follows which topic.
286     It serves only to manual topic following.
287     This model also indicates if the topic is followed by email.
288     """
289 
290     class Meta:
291         verbose_name = 'Sujet suivi'
292         verbose_name_plural = 'Sujets suivis'
293 
294     topic = models.ForeignKey(Topic, db_index=True)
295     user = models.ForeignKey(User, related_name='topics_followed', db_index=True)
296     email = models.BooleanField('Notification par courriel', default=False, db_index=True)
297     objects = TopicFollowedManager()
298 
299     def __unicode__(self):
300         return u'<Sujet "{0}" suivi par {1}>'.format(self.topic.title,
301                                                      self.user.username)
302 
303 # used to fix Django 1.9 Warning
304 # https://github.com/zestedesavoir/zds-site/issues/3451
305 import receivers  # noqa
306
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zds/notification/models.py b/zds/notification/models.py
--- a/zds/notification/models.py
+++ b/zds/notification/models.py
@@ -137,6 +137,7 @@
             notification.sender = sender
             notification.url = self.get_notification_url(content)
             notification.title = self.get_notification_title(content)
+            notification.pubdate = content.pubdate
             notification.is_read = False
             notification.save()
             self.set_last_notification(notification)

{"golden_diff": "diff --git a/zds/notification/models.py b/zds/notification/models.py\n--- a/zds/notification/models.py\n+++ b/zds/notification/models.py\n@@ -137,6 +137,7 @@\n             notification.sender = sender\n             notification.url = self.get_notification_url(content)\n             notification.title = self.get_notification_title(content)\n+            notification.pubdate = content.pubdate\n             notification.is_read = False\n             notification.save()\n             self.set_last_notification(notification)\n", "issue": "[v18] Date incorrecte dans les notifications\nJ'ai re\u00e7u une notification pour un topic que je suivais avant la v18.\n\nSelon la notification elle datait d'il y'a 7h, sauf qu'en cliquant dessus, je suis tomb\u00e9 sur un message qui date d'il y'a 7 minutes. On peut le voir sur les screens ci-dessous.\n\n**Notification avant le clic** : \n\n![capture d ecran de 2016-05-28 09-03-31](https://cloud.githubusercontent.com/assets/6066015/15625756/7d2d85ba-24b3-11e6-8537-0055b9ebc892.png)\n\n**Notification apr\u00e8s le clic** : \n\n![capture d ecran de 2016-05-28 09-03-48](https://cloud.githubusercontent.com/assets/6066015/15625757/81da4634-24b3-11e6-9cfd-22e48d7535e2.png)\n\nIl faudrait retomber sur ce cas pour identifier facilement le bug. Si jamais j'ai une meilleur id\u00e9e du truc j'\u00e9dite le titre qui n'est pas tr\u00e8s parlant.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-s\nfrom smtplib import SMTPException\n\nfrom django.contrib.auth.models import User\nfrom django.contrib.contenttypes.fields import GenericForeignKey\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.db import models\nfrom django.template.loader import render_to_string\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom zds import settings\nfrom zds.forum.models import Topic\nfrom zds.notification.managers import NotificationManager, SubscriptionManager, TopicFollowedManager, \\\n    TopicAnswerSubscriptionManager\nfrom zds.utils.misc import convert_camel_to_underscore\n\n\nclass Subscription(models.Model):\n    \"\"\"\n    Model used to register the subscription of a user to a set of notifications (regarding a tutorial, a forum, ...)\n    \"\"\"\n\n    class Meta:\n        verbose_name = _(u'Abonnement')\n        verbose_name_plural = _(u'Abonnements')\n\n    user = models.ForeignKey(User, related_name='subscriber', db_index=True)\n    pubdate = models.DateTimeField(_(u'Date de cr\u00e9ation'), auto_now_add=True, db_index=True)\n    is_active = models.BooleanField(_(u'Actif'), default=True, db_index=True)\n    by_email = models.BooleanField(_(u'Recevoir un email'), default=False)\n    content_type = models.ForeignKey(ContentType)\n    object_id = models.PositiveIntegerField(db_index=True)\n    content_object = GenericForeignKey('content_type', 'object_id')\n    last_notification = models.ForeignKey(u'Notification', related_name=\"last_notification\", null=True, default=None)\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux notifications pour le {1}, #{2}>')\\\n            .format(self.user.username, self.content_type, self.object_id)\n\n    def activate(self):\n        \"\"\"\n        Activates the subscription if it's inactive. Does nothing otherwise\n        \"\"\"\n        if not self.is_active:\n            self.is_active = True\n            self.save()\n\n    def activate_email(self):\n        \"\"\"\n        Activates the notifications by email (and the subscription itself if needed)\n        \"\"\"\n        if not self.is_active or not self.by_email:\n            self.is_active = True\n            self.by_email = True\n            self.save()\n\n    def deactivate(self):\n        \"\"\"\n        Deactivate the subscription if it is active. Does nothing otherwise\n        \"\"\"\n        if self.is_active:\n            self.is_active = False\n            self.save()\n\n    def deactivate_email(self):\n        \"\"\"\n        Deactivate the email if it is active. Does nothing otherwise\n        \"\"\"\n        if self.is_active and self.by_email:\n            self.by_email = False\n            self.save()\n\n    def set_last_notification(self, notification):\n        \"\"\"\n        Replace last_notification by the one given\n        \"\"\"\n        self.last_notification = notification\n        self.save()\n\n    def send_email(self, notification):\n        \"\"\"\n        Sends an email notification\n        \"\"\"\n\n        assert hasattr(self, \"module\")\n\n        subject = _(u\"{} - {} : {}\").format(settings.ZDS_APP['site']['litteral_name'], self.module, notification.title)\n        from_email = _(u\"{} <{}>\").format(settings.ZDS_APP['site']['litteral_name'],\n                                          settings.ZDS_APP['site']['email_noreply'])\n\n        receiver = self.user\n        context = {\n            'username': receiver.username,\n            'title': notification.title,\n            'url': settings.ZDS_APP['site']['url'] + notification.url,\n            'author': notification.sender.username,\n            'site_name': settings.ZDS_APP['site']['litteral_name']\n        }\n        message_html = render_to_string(\n            'email/notification/' + convert_camel_to_underscore(self._meta.object_name) + '.html', context)\n        message_txt = render_to_string(\n            'email/notification/' + convert_camel_to_underscore(self._meta.object_name) + '.txt', context)\n\n        msg = EmailMultiAlternatives(subject, message_txt, from_email, [receiver.email])\n        msg.attach_alternative(message_html, \"text/html\")\n        try:\n            msg.send()\n        except SMTPException:\n            pass\n\n\nclass SingleNotificationMixin(object):\n    \"\"\"\n    Mixin for the subscription that can only have one active notification at a time\n    \"\"\"\n    def send_notification(self, content=None, send_email=True, sender=None):\n        \"\"\"\n        Sends the notification about the given content\n        :param content:  the content the notification is about\n        :param sender: the user whose action triggered the notification\n        :param send_email : whether an email must be sent if the subscription by email is active\n        \"\"\"\n        assert hasattr(self, 'last_notification')\n        assert hasattr(self, 'set_last_notification')\n        assert hasattr(self, 'get_notification_url')\n        assert hasattr(self, 'get_notification_title')\n        assert hasattr(self, 'send_email')\n\n        if self.last_notification is None or self.last_notification.is_read:\n            # If there isn't a notification yet or the last one is read, we generate a new one.\n            try:\n                notification = Notification.objects.get(subscription=self)\n            except Notification.DoesNotExist:\n                notification = Notification(subscription=self, content_object=content, sender=sender)\n            notification.content_object = content\n            notification.sender = sender\n            notification.url = self.get_notification_url(content)\n            notification.title = self.get_notification_title(content)\n            notification.is_read = False\n            notification.save()\n            self.set_last_notification(notification)\n            self.save()\n\n            if send_email and self.by_email:\n                self.send_email(notification)\n        elif self.last_notification is not None:\n            # Update last notification if the new content is older (case of unreading answer)\n            if not self.last_notification.is_read and self.last_notification.pubdate > content.pubdate:\n                self.last_notification.content_object = content\n                self.last_notification.save()\n\n    def mark_notification_read(self):\n        \"\"\"\n        Marks the notification of the subscription as read.\n        As there's only one active unread notification at all time,\n        no need for more precision\n        \"\"\"\n        if self.last_notification is not None:\n            self.last_notification.is_read = True\n            self.last_notification.save()\n\n\nclass MultipleNotificationsMixin(object):\n\n    def send_notification(self, content=None, send_email=True, sender=None):\n        \"\"\"\n        Sends the notification about the given content\n        :param content:  the content the notification is about\n        :param sender: the user whose action triggered the notification\n        :param send_email : whether an email must be sent if the subscription by email is active\n        \"\"\"\n\n        assert hasattr(self, \"get_notification_url\")\n        assert hasattr(self, \"get_notification_title\")\n        assert hasattr(self, \"send_email\")\n\n        notification = Notification(subscription=self, content_object=content, sender=sender)\n        notification.url = self.get_notification_url(content)\n        notification.title = self.get_notification_title(content)\n        notification.save()\n        self.set_last_notification(notification)\n\n        if send_email and self.by_email:\n            self.send_email(notification)\n\n    def mark_notification_read(self, content):\n        \"\"\"\n        Marks the notification of the subscription as read.\n        :param content : the content whose notification has been read\n        \"\"\"\n        if content is None:\n            raise Exception('Object content of notification must be defined')\n\n        content_notification_type = ContentType.objects.get_for_model(content)\n        try:\n            notification = Notification.objects.get(subscription=self,\n                                                    content_type__pk=content_notification_type.pk,\n                                                    object_id=content.pk, is_read=False)\n            if notification is not None:\n                notification.is_read = True\n                notification.save()\n        except Notification.DoesNotExist:\n            pass\n\n\nclass AnswerSubscription(Subscription):\n    \"\"\"\n    Subscription to new answer, either in a topic, a article or a tutorial\n    NOT used directly, use one of its subtype\n    \"\"\"\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses au {1} #{2}>')\\\n            .format(self.user.username, self.content_type, self.object_id)\n\n    def get_notification_url(self, answer):\n        return answer.get_absolute_url()\n\n    def get_notification_title(self, answer):\n        return self.content_object.title\n\n\nclass TopicAnswerSubscription(AnswerSubscription, SingleNotificationMixin):\n    \"\"\"\n    Subscription to new answer in a topic\n    \"\"\"\n    module = _(u'Forum')\n    objects = TopicAnswerSubscriptionManager()\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses au sujet #{1}>')\\\n            .format(self.user.username, self.object_id)\n\n\nclass PrivateTopicAnswerSubscription(AnswerSubscription, SingleNotificationMixin):\n    \"\"\"\n    Subscription to new answer in a private topic.\n    \"\"\"\n    module = _(u'Message priv\u00e9')\n    objects = SubscriptionManager()\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses \u00e0 la conversation priv\u00e9e #{1}>')\\\n            .format(self.user.username, self.object_id)\n\n\nclass ContentReactionAnswerSubscription(AnswerSubscription, SingleNotificationMixin):\n    \"\"\"\n    Subscription to new answer in a publishable content.\n    \"\"\"\n    module = _(u'Contenu')\n    objects = SubscriptionManager()\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses du contenu #{1}>')\\\n            .format(self.user.username, self.object_id)\n\n\nclass Notification(models.Model):\n    \"\"\"\n    A notification\n    \"\"\"\n    class Meta:\n        verbose_name = _(u'Notification')\n        verbose_name_plural = _(u'Notifications')\n\n    subscription = models.ForeignKey(Subscription, related_name='subscription', db_index=True)\n    pubdate = models.DateTimeField(_(u'Date de cr\u00e9ation'), auto_now_add=True, db_index=True)\n    content_type = models.ForeignKey(ContentType)\n    object_id = models.PositiveIntegerField(db_index=True)\n    content_object = GenericForeignKey('content_type', 'object_id')\n    is_read = models.BooleanField(_(u'Lue'), default=False, db_index=True)\n    url = models.CharField('URL', max_length=255)\n    sender = models.ForeignKey(User, related_name='sender', db_index=True)\n    title = models.CharField('Titre', max_length=200)\n    objects = NotificationManager()\n\n    def __unicode__(self):\n        return _(u'Notification du membre \"{0}\" \u00e0 propos de : {1} #{2} ({3})')\\\n            .format(self.subscription.user, self.content_type, self.content_object.pk, self.subscription)\n\n\nclass TopicFollowed(models.Model):\n    \"\"\"\n    This model tracks which user follows which topic.\n    It serves only to manual topic following.\n    This model also indicates if the topic is followed by email.\n    \"\"\"\n\n    class Meta:\n        verbose_name = 'Sujet suivi'\n        verbose_name_plural = 'Sujets suivis'\n\n    topic = models.ForeignKey(Topic, db_index=True)\n    user = models.ForeignKey(User, related_name='topics_followed', db_index=True)\n    email = models.BooleanField('Notification par courriel', default=False, db_index=True)\n    objects = TopicFollowedManager()\n\n    def __unicode__(self):\n        return u'<Sujet \"{0}\" suivi par {1}>'.format(self.topic.title,\n                                                     self.user.username)\n\n# used to fix Django 1.9 Warning\n# https://github.com/zestedesavoir/zds-site/issues/3451\nimport receivers  # noqa\n", "path": "zds/notification/models.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-s\nfrom smtplib import SMTPException\n\nfrom django.contrib.auth.models import User\nfrom django.contrib.contenttypes.fields import GenericForeignKey\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.db import models\nfrom django.template.loader import render_to_string\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom zds import settings\nfrom zds.forum.models import Topic\nfrom zds.notification.managers import NotificationManager, SubscriptionManager, TopicFollowedManager, \\\n    TopicAnswerSubscriptionManager\nfrom zds.utils.misc import convert_camel_to_underscore\n\n\nclass Subscription(models.Model):\n    \"\"\"\n    Model used to register the subscription of a user to a set of notifications (regarding a tutorial, a forum, ...)\n    \"\"\"\n\n    class Meta:\n        verbose_name = _(u'Abonnement')\n        verbose_name_plural = _(u'Abonnements')\n\n    user = models.ForeignKey(User, related_name='subscriber', db_index=True)\n    pubdate = models.DateTimeField(_(u'Date de cr\u00e9ation'), auto_now_add=True, db_index=True)\n    is_active = models.BooleanField(_(u'Actif'), default=True, db_index=True)\n    by_email = models.BooleanField(_(u'Recevoir un email'), default=False)\n    content_type = models.ForeignKey(ContentType)\n    object_id = models.PositiveIntegerField(db_index=True)\n    content_object = GenericForeignKey('content_type', 'object_id')\n    last_notification = models.ForeignKey(u'Notification', related_name=\"last_notification\", null=True, default=None)\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux notifications pour le {1}, #{2}>')\\\n            .format(self.user.username, self.content_type, self.object_id)\n\n    def activate(self):\n        \"\"\"\n        Activates the subscription if it's inactive. Does nothing otherwise\n        \"\"\"\n        if not self.is_active:\n            self.is_active = True\n            self.save()\n\n    def activate_email(self):\n        \"\"\"\n        Activates the notifications by email (and the subscription itself if needed)\n        \"\"\"\n        if not self.is_active or not self.by_email:\n            self.is_active = True\n            self.by_email = True\n            self.save()\n\n    def deactivate(self):\n        \"\"\"\n        Deactivate the subscription if it is active. Does nothing otherwise\n        \"\"\"\n        if self.is_active:\n            self.is_active = False\n            self.save()\n\n    def deactivate_email(self):\n        \"\"\"\n        Deactivate the email if it is active. Does nothing otherwise\n        \"\"\"\n        if self.is_active and self.by_email:\n            self.by_email = False\n            self.save()\n\n    def set_last_notification(self, notification):\n        \"\"\"\n        Replace last_notification by the one given\n        \"\"\"\n        self.last_notification = notification\n        self.save()\n\n    def send_email(self, notification):\n        \"\"\"\n        Sends an email notification\n        \"\"\"\n\n        assert hasattr(self, \"module\")\n\n        subject = _(u\"{} - {} : {}\").format(settings.ZDS_APP['site']['litteral_name'], self.module, notification.title)\n        from_email = _(u\"{} <{}>\").format(settings.ZDS_APP['site']['litteral_name'],\n                                          settings.ZDS_APP['site']['email_noreply'])\n\n        receiver = self.user\n        context = {\n            'username': receiver.username,\n            'title': notification.title,\n            'url': settings.ZDS_APP['site']['url'] + notification.url,\n            'author': notification.sender.username,\n            'site_name': settings.ZDS_APP['site']['litteral_name']\n        }\n        message_html = render_to_string(\n            'email/notification/' + convert_camel_to_underscore(self._meta.object_name) + '.html', context)\n        message_txt = render_to_string(\n            'email/notification/' + convert_camel_to_underscore(self._meta.object_name) + '.txt', context)\n\n        msg = EmailMultiAlternatives(subject, message_txt, from_email, [receiver.email])\n        msg.attach_alternative(message_html, \"text/html\")\n        try:\n            msg.send()\n        except SMTPException:\n            pass\n\n\nclass SingleNotificationMixin(object):\n    \"\"\"\n    Mixin for the subscription that can only have one active notification at a time\n    \"\"\"\n    def send_notification(self, content=None, send_email=True, sender=None):\n        \"\"\"\n        Sends the notification about the given content\n        :param content:  the content the notification is about\n        :param sender: the user whose action triggered the notification\n        :param send_email : whether an email must be sent if the subscription by email is active\n        \"\"\"\n        assert hasattr(self, 'last_notification')\n        assert hasattr(self, 'set_last_notification')\n        assert hasattr(self, 'get_notification_url')\n        assert hasattr(self, 'get_notification_title')\n        assert hasattr(self, 'send_email')\n\n        if self.last_notification is None or self.last_notification.is_read:\n            # If there isn't a notification yet or the last one is read, we generate a new one.\n            try:\n                notification = Notification.objects.get(subscription=self)\n            except Notification.DoesNotExist:\n                notification = Notification(subscription=self, content_object=content, sender=sender)\n            notification.content_object = content\n            notification.sender = sender\n            notification.url = self.get_notification_url(content)\n            notification.title = self.get_notification_title(content)\n            notification.pubdate = content.pubdate\n            notification.is_read = False\n            notification.save()\n            self.set_last_notification(notification)\n            self.save()\n\n            if send_email and self.by_email:\n                self.send_email(notification)\n        elif self.last_notification is not None:\n            # Update last notification if the new content is older (case of unreading answer)\n            if not self.last_notification.is_read and self.last_notification.pubdate > content.pubdate:\n                self.last_notification.content_object = content\n                self.last_notification.save()\n\n    def mark_notification_read(self):\n        \"\"\"\n        Marks the notification of the subscription as read.\n        As there's only one active unread notification at all time,\n        no need for more precision\n        \"\"\"\n        if self.last_notification is not None:\n            self.last_notification.is_read = True\n            self.last_notification.save()\n\n\nclass MultipleNotificationsMixin(object):\n\n    def send_notification(self, content=None, send_email=True, sender=None):\n        \"\"\"\n        Sends the notification about the given content\n        :param content:  the content the notification is about\n        :param sender: the user whose action triggered the notification\n        :param send_email : whether an email must be sent if the subscription by email is active\n        \"\"\"\n\n        assert hasattr(self, \"get_notification_url\")\n        assert hasattr(self, \"get_notification_title\")\n        assert hasattr(self, \"send_email\")\n\n        notification = Notification(subscription=self, content_object=content, sender=sender)\n        notification.url = self.get_notification_url(content)\n        notification.title = self.get_notification_title(content)\n        notification.save()\n        self.set_last_notification(notification)\n\n        if send_email and self.by_email:\n            self.send_email(notification)\n\n    def mark_notification_read(self, content):\n        \"\"\"\n        Marks the notification of the subscription as read.\n        :param content : the content whose notification has been read\n        \"\"\"\n        if content is None:\n            raise Exception('Object content of notification must be defined')\n\n        content_notification_type = ContentType.objects.get_for_model(content)\n        try:\n            notification = Notification.objects.get(subscription=self,\n                                                    content_type__pk=content_notification_type.pk,\n                                                    object_id=content.pk, is_read=False)\n            if notification is not None:\n                notification.is_read = True\n                notification.save()\n        except Notification.DoesNotExist:\n            pass\n\n\nclass AnswerSubscription(Subscription):\n    \"\"\"\n    Subscription to new answer, either in a topic, a article or a tutorial\n    NOT used directly, use one of its subtype\n    \"\"\"\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses au {1} #{2}>')\\\n            .format(self.user.username, self.content_type, self.object_id)\n\n    def get_notification_url(self, answer):\n        return answer.get_absolute_url()\n\n    def get_notification_title(self, answer):\n        return self.content_object.title\n\n\nclass TopicAnswerSubscription(AnswerSubscription, SingleNotificationMixin):\n    \"\"\"\n    Subscription to new answer in a topic\n    \"\"\"\n    module = _(u'Forum')\n    objects = TopicAnswerSubscriptionManager()\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses au sujet #{1}>')\\\n            .format(self.user.username, self.object_id)\n\n\nclass PrivateTopicAnswerSubscription(AnswerSubscription, SingleNotificationMixin):\n    \"\"\"\n    Subscription to new answer in a private topic.\n    \"\"\"\n    module = _(u'Message priv\u00e9')\n    objects = SubscriptionManager()\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses \u00e0 la conversation priv\u00e9e #{1}>')\\\n            .format(self.user.username, self.object_id)\n\n\nclass ContentReactionAnswerSubscription(AnswerSubscription, SingleNotificationMixin):\n    \"\"\"\n    Subscription to new answer in a publishable content.\n    \"\"\"\n    module = _(u'Contenu')\n    objects = SubscriptionManager()\n\n    def __unicode__(self):\n        return _(u'<Abonnement du membre \"{0}\" aux r\u00e9ponses du contenu #{1}>')\\\n            .format(self.user.username, self.object_id)\n\n\nclass Notification(models.Model):\n    \"\"\"\n    A notification\n    \"\"\"\n    class Meta:\n        verbose_name = _(u'Notification')\n        verbose_name_plural = _(u'Notifications')\n\n    subscription = models.ForeignKey(Subscription, related_name='subscription', db_index=True)\n    pubdate = models.DateTimeField(_(u'Date de cr\u00e9ation'), auto_now_add=True, db_index=True)\n    content_type = models.ForeignKey(ContentType)\n    object_id = models.PositiveIntegerField(db_index=True)\n    content_object = GenericForeignKey('content_type', 'object_id')\n    is_read = models.BooleanField(_(u'Lue'), default=False, db_index=True)\n    url = models.CharField('URL', max_length=255)\n    sender = models.ForeignKey(User, related_name='sender', db_index=True)\n    title = models.CharField('Titre', max_length=200)\n    objects = NotificationManager()\n\n    def __unicode__(self):\n        return _(u'Notification du membre \"{0}\" \u00e0 propos de : {1} #{2} ({3})')\\\n            .format(self.subscription.user, self.content_type, self.content_object.pk, self.subscription)\n\n\nclass TopicFollowed(models.Model):\n    \"\"\"\n    This model tracks which user follows which topic.\n    It serves only to manual topic following.\n    This model also indicates if the topic is followed by email.\n    \"\"\"\n\n    class Meta:\n        verbose_name = 'Sujet suivi'\n        verbose_name_plural = 'Sujets suivis'\n\n    topic = models.ForeignKey(Topic, db_index=True)\n    user = models.ForeignKey(User, related_name='topics_followed', db_index=True)\n    email = models.BooleanField('Notification par courriel', default=False, db_index=True)\n    objects = TopicFollowedManager()\n\n    def __unicode__(self):\n        return u'<Sujet \"{0}\" suivi par {1}>'.format(self.topic.title,\n                                                     self.user.username)\n\n# used to fix Django 1.9 Warning\n# https://github.com/zestedesavoir/zds-site/issues/3451\nimport receivers  # noqa\n", "path": "zds/notification/models.py"}]}