~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to notification/models.py

  • Committer: franku
  • Date: 2016-12-13 18:28:51 UTC
  • mto: This revision was merged to the branch mainline in revision 443.
  • Revision ID: somal@arcor.de-20161213182851-bo5ebf8pdvw5beua
run the script

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
else:
31
31
    from django.core.mail import send_mail
32
32
 
33
 
QUEUE_ALL = getattr(settings, "NOTIFICATION_QUEUE_ALL", False)
 
33
QUEUE_ALL = getattr(settings, 'NOTIFICATION_QUEUE_ALL', False)
 
34
 
34
35
 
35
36
class LanguageStoreNotAvailable(Exception):
36
37
    pass
37
38
 
 
39
 
38
40
class NoticeType(models.Model):
39
41
 
40
42
    label = models.CharField(_('label'), max_length=40)
41
43
    display = models.CharField(_('display'), max_length=50)
42
44
    description = models.CharField(_('description'), max_length=100)
43
45
 
44
 
    # by default only on for media with sensitivity less than or equal to this number
 
46
    # by default only on for media with sensitivity less than or equal to this
 
47
    # number
45
48
    default = models.IntegerField(_('default'))
46
49
 
47
50
    def __unicode__(self):
48
51
        return self.label
49
52
 
50
53
    class Meta:
51
 
        verbose_name = _("notice type")
52
 
        verbose_name_plural = _("notice types")
 
54
        verbose_name = _('notice type')
 
55
        verbose_name_plural = _('notice types')
53
56
 
54
57
 
55
58
# if this gets updated, the create() method below needs to be as well...
56
59
NOTICE_MEDIA = (
57
 
    ("1", _("Email")),
 
60
    ('1', _('Email')),
58
61
)
59
62
 
60
63
# how spam-sensitive is the medium
61
64
NOTICE_MEDIA_DEFAULTS = {
62
 
    "1": 2 # email
 
65
    '1': 2  # email
63
66
}
64
67
 
 
68
 
65
69
class NoticeSetting(models.Model):
66
 
    """
67
 
    Indicates, for a given user, whether to send notifications
68
 
    of a given type to a given medium.
69
 
    """
 
70
    """Indicates, for a given user, whether to send notifications of a given
 
71
    type to a given medium."""
70
72
 
71
73
    user = models.ForeignKey(User, verbose_name=_('user'))
72
74
    notice_type = models.ForeignKey(NoticeType, verbose_name=_('notice type'))
74
76
    send = models.BooleanField(_('send'))
75
77
 
76
78
    class Meta:
77
 
        verbose_name = _("notice setting")
78
 
        verbose_name_plural = _("notice settings")
79
 
        unique_together = ("user", "notice_type", "medium")
 
79
        verbose_name = _('notice setting')
 
80
        verbose_name_plural = _('notice settings')
 
81
        unique_together = ('user', 'notice_type', 'medium')
 
82
 
80
83
 
81
84
def get_notification_setting(user, notice_type, medium):
82
85
    try:
83
86
        return NoticeSetting.objects.get(user=user, notice_type=notice_type, medium=medium)
84
87
    except NoticeSetting.DoesNotExist:
85
88
        default = (NOTICE_MEDIA_DEFAULTS[medium] <= notice_type.default)
86
 
        setting = NoticeSetting(user=user, notice_type=notice_type, medium=medium, send=default)
 
89
        setting = NoticeSetting(
 
90
            user=user, notice_type=notice_type, medium=medium, send=default)
87
91
        setting.save()
88
92
        return setting
89
93
 
 
94
 
90
95
def should_send(user, notice_type, medium):
91
96
    return get_notification_setting(user, notice_type, medium).send
92
97
 
94
99
class NoticeManager(models.Manager):
95
100
 
96
101
    def notices_for(self, user, archived=False, unseen=None, on_site=None):
97
 
        """
98
 
        returns Notice objects for the given user.
 
102
        """returns Notice objects for the given user.
99
103
 
100
104
        If archived=False, it only include notices not archived.
101
105
        If archived=True, it returns all notices for that user.
103
107
        If unseen=None, it includes all notices.
104
108
        If unseen=True, return only unseen notices.
105
109
        If unseen=False, return only seen notices.
 
110
 
106
111
        """
107
112
        if archived:
108
113
            qs = self.filter(user=user)
115
120
        return qs
116
121
 
117
122
    def unseen_count_for(self, user, **kwargs):
118
 
        """
119
 
        returns the number of unseen notices for the given user but does not
120
 
        mark them seen
121
 
        """
 
123
        """returns the number of unseen notices for the given user but does not
 
124
        mark them seen."""
122
125
        return self.notices_for(user, unseen=True, **kwargs).count()
123
126
 
 
127
 
124
128
class Notice(models.Model):
125
129
 
126
130
    user = models.ForeignKey(User, verbose_name=_('user'))
141
145
        self.save()
142
146
 
143
147
    def is_unseen(self):
144
 
        """
145
 
        returns value of self.unseen but also changes it to false.
146
 
 
147
 
        Use this in a template to mark an unseen notice differently the first
148
 
        time it is shown.
 
148
        """returns value of self.unseen but also changes it to false.
 
149
 
 
150
        Use this in a template to mark an unseen notice differently the
 
151
        first time it is shown.
 
152
 
149
153
        """
150
154
        unseen = self.unseen
151
155
        if unseen:
154
158
        return unseen
155
159
 
156
160
    class Meta:
157
 
        ordering = ["-added"]
158
 
        verbose_name = _("notice")
159
 
        verbose_name_plural = _("notices")
 
161
        ordering = ['-added']
 
162
        verbose_name = _('notice')
 
163
        verbose_name_plural = _('notices')
160
164
 
161
165
    def get_absolute_url(self):
162
 
        return ("notification_notice", [str(self.pk)])
 
166
        return ('notification_notice', [str(self.pk)])
163
167
    get_absolute_url = models.permalink(get_absolute_url)
164
168
 
 
169
 
165
170
class NoticeQueueBatch(models.Model):
166
 
    """
167
 
    A queued notice.
 
171
    """A queued notice.
 
172
 
168
173
    Denormalized data for a notice.
 
174
 
169
175
    """
170
176
    pickled_data = models.TextField()
171
177
 
 
178
 
172
179
def create_notice_type(label, display, description, default=2, verbosity=1):
173
 
    """
174
 
    Creates a new NoticeType.
175
 
 
176
 
    This is intended to be used by other apps as a post_syncdb manangement step.
 
180
    """Creates a new NoticeType.
 
181
 
 
182
    This is intended to be used by other apps as a post_syncdb
 
183
    manangement step.
 
184
 
177
185
    """
178
186
    try:
179
187
        notice_type = NoticeType.objects.get(label=label)
190
198
        if updated:
191
199
            notice_type.save()
192
200
            if verbosity > 1:
193
 
                print "Updated %s NoticeType" % label
 
201
                print 'Updated %s NoticeType' % label
194
202
    except NoticeType.DoesNotExist:
195
 
        NoticeType(label=label, display=display, description=description, default=default).save()
 
203
        NoticeType(label=label, display=display,
 
204
                   description=description, default=default).save()
196
205
        if verbosity > 1:
197
 
            print "Created %s NoticeType" % label
 
206
            print 'Created %s NoticeType' % label
 
207
 
198
208
 
199
209
def get_notification_language(user):
200
210
    """
204
214
    """
205
215
    if getattr(settings, 'NOTIFICATION_LANGUAGE_MODULE', False):
206
216
        try:
207
 
            app_label, model_name = settings.NOTIFICATION_LANGUAGE_MODULE.split('.')
 
217
            app_label, model_name = settings.NOTIFICATION_LANGUAGE_MODULE.split(
 
218
                '.')
208
219
            model = models.get_model(app_label, model_name)
209
 
            language_model = model._default_manager.get(user__id__exact=user.id)
 
220
            language_model = model._default_manager.get(
 
221
                user__id__exact=user.id)
210
222
            if hasattr(language_model, 'language'):
211
223
                return language_model.language
212
224
        except (ImportError, ImproperlyConfigured, model.DoesNotExist):
213
225
            raise LanguageStoreNotAvailable
214
226
    raise LanguageStoreNotAvailable
215
227
 
 
228
 
216
229
def get_formatted_messages(formats, label, context):
217
 
    """
218
 
    Returns a dictionary with the format identifier as the key. The values are
219
 
    are fully rendered templates with the given context.
 
230
    """Returns a dictionary with the format identifier as the key.
 
231
 
 
232
    The values are are fully rendered templates with the given context.
 
233
 
220
234
    """
221
235
    format_templates = {}
222
236
    for format in formats:
223
237
        # conditionally turn off autoescaping for .txt extensions in format
224
 
        if format.endswith(".txt"):
 
238
        if format.endswith('.txt'):
225
239
            context.autoescape = False
226
240
        else:
227
241
            context.autoescape = True
230
244
            'notification/%s' % format), context_instance=context)
231
245
    return format_templates
232
246
 
 
247
 
233
248
def send_now(users, label, extra_context=None, on_site=True):
234
 
    """
235
 
    Creates a new notice.
 
249
    """Creates a new notice.
236
250
 
237
251
    This is intended to be how other apps create new notices.
238
252
 
240
254
        'spam': 'eggs',
241
255
        'foo': 'bar',
242
256
    )
243
 
    
 
257
 
244
258
    You can pass in on_site=False to prevent the notice emitted from being
245
259
    displayed on the site.
 
260
 
246
261
    """
247
262
    if extra_context is None:
248
263
        extra_context = {}
249
 
    
 
264
 
250
265
    notice_type = NoticeType.objects.get(label=label)
251
266
 
252
267
    current_site = Site.objects.get_current()
253
268
    notices_url = u"http://%s%s" % (
254
269
        unicode(current_site),
255
 
        reverse("notification_notices"),
 
270
        reverse('notification_notices'),
256
271
    )
257
272
 
258
273
    current_language = get_language()
262
277
        'full.txt',
263
278
        'notice.html',
264
279
        'full.html',
265
 
    ) # TODO make formats configurable
 
280
    )  # TODO make formats configurable
266
281
 
267
282
    for user in users:
268
283
        recipients = []
279
294
 
280
295
        # update context with user specific translations
281
296
        context = Context({
282
 
            "user": user,
283
 
            "notice": ugettext(notice_type.display),
284
 
            "notices_url": notices_url,
285
 
            "current_site": current_site,
 
297
            'user': user,
 
298
            'notice': ugettext(notice_type.display),
 
299
            'notices_url': notices_url,
 
300
            'current_site': current_site,
286
301
        })
287
302
        context.update(extra_context)
288
303
 
299
314
        }, context)
300
315
 
301
316
        notice = Notice.objects.create(user=user, message=messages['notice.html'],
302
 
            notice_type=notice_type, on_site=on_site)
303
 
        if should_send(user, notice_type, "1") and user.email: # Email
 
317
                                       notice_type=notice_type, on_site=on_site)
 
318
        if should_send(user, notice_type, '1') and user.email:  # Email
304
319
            recipients.append(user.email)
305
320
        send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, recipients)
306
321
 
307
322
    # reset environment to original language
308
323
    activate(current_language)
309
324
 
 
325
 
310
326
def send(*args, **kwargs):
311
 
    """
312
 
    A basic interface around both queue and send_now. This honors a global
313
 
    flag NOTIFICATION_QUEUE_ALL that helps determine whether all calls should
314
 
    be queued or not. A per call ``queue`` or ``now`` keyword argument can be
315
 
    used to always override the default global behavior.
316
 
    """
317
 
    queue_flag = kwargs.pop("queue", False)
318
 
    now_flag = kwargs.pop("now", False)
319
 
    assert not (queue_flag and now_flag), "'queue' and 'now' cannot both be True."
 
327
    """A basic interface around both queue and send_now.
 
328
 
 
329
    This honors a global flag NOTIFICATION_QUEUE_ALL that helps
 
330
    determine whether all calls should be queued or not. A per call
 
331
    ``queue`` or ``now`` keyword argument can be used to always override
 
332
    the default global behavior.
 
333
 
 
334
    """
 
335
    queue_flag = kwargs.pop('queue', False)
 
336
    now_flag = kwargs.pop('now', False)
 
337
    assert not (
 
338
        queue_flag and now_flag), "'queue' and 'now' cannot both be True."
320
339
    if queue_flag:
321
340
        return queue(*args, **kwargs)
322
341
    elif now_flag:
326
345
            return queue(*args, **kwargs)
327
346
        else:
328
347
            return send_now(*args, **kwargs)
329
 
        
 
348
 
 
349
 
330
350
def queue(users, label, extra_context=None, on_site=True):
331
 
    """
332
 
    Queue the notification in NoticeQueueBatch. This allows for large amounts
333
 
    of user notifications to be deferred to a seperate process running outside
334
 
    the webserver.
 
351
    """Queue the notification in NoticeQueueBatch.
 
352
 
 
353
    This allows for large amounts of user notifications to be deferred
 
354
    to a seperate process running outside the webserver.
 
355
 
335
356
    """
336
357
    if extra_context is None:
337
358
        extra_context = {}
338
359
    if isinstance(users, QuerySet):
339
 
        users = [row["pk"] for row in users.values("pk")]
 
360
        users = [row['pk'] for row in users.values('pk')]
340
361
    else:
341
362
        users = [user.pk for user in users]
342
363
    notices = []
343
364
    for user in users:
344
365
        notices.append((user, label, extra_context, on_site))
345
 
    NoticeQueueBatch(pickled_data=pickle.dumps(notices).encode("base64")).save()
 
366
    NoticeQueueBatch(pickled_data=pickle.dumps(
 
367
        notices).encode('base64')).save()
 
368
 
346
369
 
347
370
class ObservedItemManager(models.Manager):
348
371
 
349
372
    def all_for(self, observed, signal):
350
 
        """
351
 
        Returns all ObservedItems for an observed object,
352
 
        to be sent when a signal is emited.
353
 
        """
 
373
        """Returns all ObservedItems for an observed object, to be sent when a
 
374
        signal is emited."""
354
375
        content_type = ContentType.objects.get_for_model(observed)
355
 
        observed_items = self.filter(content_type=content_type, object_id=observed.id, signal=signal)
 
376
        observed_items = self.filter(
 
377
            content_type=content_type, object_id=observed.id, signal=signal)
356
378
        return observed_items
357
379
 
358
380
    def get_for(self, observed, observer, signal):
359
381
        content_type = ContentType.objects.get_for_model(observed)
360
 
        observed_item = self.get(content_type=content_type, object_id=observed.id, user=observer, signal=signal)
 
382
        observed_item = self.get(
 
383
            content_type=content_type, object_id=observed.id, user=observer, signal=signal)
361
384
        return observed_item
362
385
 
363
386
 
389
412
 
390
413
 
391
414
def observe(observed, observer, notice_type_label, signal='post_save'):
392
 
    """
393
 
    Create a new ObservedItem.
394
 
 
395
 
    To be used by applications to register a user as an observer for some object.
 
415
    """Create a new ObservedItem.
 
416
 
 
417
    To be used by applications to register a user as an observer for
 
418
    some object.
 
419
 
396
420
    """
397
421
    notice_type = NoticeType.objects.get(label=notice_type_label)
398
422
    observed_item = ObservedItem(user=observer, observed_object=observed,
400
424
    observed_item.save()
401
425
    return observed_item
402
426
 
 
427
 
403
428
def stop_observing(observed, observer, signal='post_save'):
404
 
    """
405
 
    Remove an observed item.
406
 
    """
 
429
    """Remove an observed item."""
407
430
    observed_item = ObservedItem.objects.get_for(observed, observer, signal)
408
431
    observed_item.delete()
409
432
 
 
433
 
410
434
def send_observation_notices_for(observed, signal='post_save'):
411
 
    """
412
 
    Send a notice for each registered user about an observed object.
413
 
    """
 
435
    """Send a notice for each registered user about an observed object."""
414
436
    observed_items = ObservedItem.objects.all_for(observed, signal)
415
437
    for observed_item in observed_items:
416
438
        observed_item.send_notice()
417
439
    return observed_items
418
440
 
 
441
 
419
442
def is_observing(observed, observer, signal='post_save'):
420
443
    if isinstance(observer, AnonymousUser):
421
444
        return False
422
445
    try:
423
 
        observed_items = ObservedItem.objects.get_for(observed, observer, signal)
 
446
        observed_items = ObservedItem.objects.get_for(
 
447
            observed, observer, signal)
424
448
        return True
425
449
    except ObservedItem.DoesNotExist:
426
450
        return False
427
451
    except ObservedItem.MultipleObjectsReturned:
428
452
        return True
429
453
 
 
454
 
430
455
def handle_observations(sender, instance, *args, **kw):
431
456
    send_observation_notices_for(instance)