Browse Source

Tweaks in notifications implementation.

Rafał Pitoń 10 years ago
parent
commit
47123e0549

+ 5 - 5
docs/developers/notifications.rst

@@ -10,12 +10,12 @@ Misago implements such system and exposes simple as part of it, located in :py:m
 notify_user
 notify_user
 -----------
 -----------
 
 
-.. function:: notify_user(user, message, url, trigger, formats=None, sender=None, update_user=True)
+.. function:: notify_user(user, message, url, type, formats=None, sender=None, update_user=True)
 
 
 * ``user:`` User to notify.
 * ``user:`` User to notify.
 * ``message:`` Notification message.
 * ``message:`` Notification message.
 * ``url:`` Link user should follow to read message.
 * ``url:`` Link user should follow to read message.
-* ``trigger:`` short text used to identify this message for ``read_user_notification`` function.
+* ``type:`` short text used to identify this message for ``read_user_notifications`` function. For example ``see_thread_123`` notification will be read when user sees thread with ID 123 for first time.
 * ``formats:`` Optional. Dict of formats for ``message`` argument that should be boldened.
 * ``formats:`` Optional. Dict of formats for ``message`` argument that should be boldened.
 * ``sender:`` Optional. User that notification origins from.
 * ``sender:`` Optional. User that notification origins from.
 * ``update_user:`` Optional. Boolean controlling if to call ``user.update`` after setting notification, or not. Defaults to ``True``.
 * ``update_user:`` Optional. Boolean controlling if to call ``user.update`` after setting notification, or not. Defaults to ``True``.
@@ -24,10 +24,10 @@ notify_user
 read_user_notifications
 read_user_notifications
 -----------------------
 -----------------------
 
 
-.. function:: read_user_notifications(user, triggers, atomic=True)
+.. function:: read_user_notifications(user, types, atomic=True)
 
 
-Sets user notifications identified by ``triggers`` as read. This function checks internally if user has new notifications before it queries database.
+Sets user notifications identified by ``types`` as read. This function checks internally if user has new notifications before it queries database.
 
 
 * ``user:`` User to whom notification belongs to
 * ``user:`` User to whom notification belongs to
-* ``triggers:`` Short text or list of short texts used to identify notifications that will be set as read.
+* ``types:`` Short text or list of short texts used to identify notifications that will be set as read.
 * ``atomic:`` Lets you control if you should wrap this in dedicated transaction.
 * ``atomic:`` Lets you control if you should wrap this in dedicated transaction.

+ 37 - 0
misago/core/pgutils.py

@@ -69,3 +69,40 @@ def batch_delete(queryset, step=50):
         for obj in queryset[:step]:
         for obj in queryset[:step]:
             yield obj
             yield obj
         queryset_exists = queryset.exists()
         queryset_exists = queryset.exists()
+
+
+class CreatePartialCompositeIndex(CreatePartialIndex):
+    CREATE_SQL = """
+CREATE INDEX %(index_name)s ON %(table)s (%(fields)s)
+WHERE %(condition)s;
+"""
+
+    REMOVE_SQL = """
+DROP INDEX %(index_name)s
+"""
+
+    def __init__(self, model, fields, index_name, condition):
+        self.model = model
+        self.fields = fields
+        self.index_name = index_name
+        self.condition = condition
+
+    def database_forwards(self, app_label, schema_editor,
+                          from_state, to_state):
+        apps = from_state.render()
+        model = apps.get_model(app_label, self.model)
+
+        statement = self.CREATE_SQL % {
+            'index_name': self.index_name,
+            'table': model._meta.db_table,
+            'fields': ', '.join(self.fields),
+            'condition': self.condition,
+        }
+
+        schema_editor.execute(statement)
+
+    def describe(self):
+        message = ("Create PostgreSQL partial composite "
+                   "index on fields %s in %s for %s")
+        formats = (', '.join(self.fields), self.model_name, self.values)
+        return message % formats

+ 11 - 14
misago/notifications/api.py

@@ -3,7 +3,7 @@ from django.db import transaction
 from django.utils.html import escape
 from django.utils.html import escape
 
 
 from misago.notifications.checksums import update_checksum
 from misago.notifications.checksums import update_checksum
-from misago.notifications.utils import hash_trigger
+from misago.notifications.utils import hash_type
 
 
 
 
 __all__ = [
 __all__ = [
@@ -14,7 +14,7 @@ __all__ = [
 ]
 ]
 
 
 
 
-def notify_user(user, message, url, trigger=None, formats=None, sender=None,
+def notify_user(user, message, url, type=None, formats=None, sender=None,
                 update_user=True):
                 update_user=True):
     from misago.notifications.models import Notification
     from misago.notifications.models import Notification
 
 
@@ -26,7 +26,7 @@ def notify_user(user, message, url, trigger=None, formats=None, sender=None,
         message_escaped = message_escaped % final_formats
         message_escaped = message_escaped % final_formats
 
 
     new_notification = Notification(user=user,
     new_notification = Notification(user=user,
-                                    trigger=hash_trigger(trigger or message),
+                                    hash=hash_type(type or message),
                                     url=url,
                                     url=url,
                                     message=message_escaped)
                                     message=message_escaped)
 
 
@@ -46,25 +46,22 @@ def notify_user(user, message, url, trigger=None, formats=None, sender=None,
     return new_notification
     return new_notification
 
 
 
 
-def read_user_notifications(user, triggers, atomic=True):
+def read_user_notifications(user, types, atomic=True):
     if user.is_authenticated() and user.new_notifications:
     if user.is_authenticated() and user.new_notifications:
         if atomic:
         if atomic:
             with transaction.atomic():
             with transaction.atomic():
-                _real_read_user_notifications(user, triggers)
+                _real_read_user_notifications(user, types)
         else:
         else:
-            _real_read_user_notifications(user, triggers)
+            _real_read_user_notifications(user, types)
 
 
 
 
-def _real_read_user_notifications(user, triggers):
-    if isinstance(triggers, basestring):
-        triggers = [triggers]
+def _real_read_user_notifications(user, types):
+    if isinstance(types, basestring):
+        types = [types]
 
 
-    hashes = [hash_trigger(trigger) for trigger in triggers]
+    hashes = [hash_type(type) for type in types]
     update_qs = user.misago_notifications.filter(is_new=True)
     update_qs = user.misago_notifications.filter(is_new=True)
-    if len(hashes) == 1:
-        update_qs = update_qs.filter(trigger=hashes[0])
-    else:
-        update_qs = update_qs.filter(trigger__in=hashes)
+    update_qs = update_qs.filter(hash__in=hashes)
     updated = update_qs.update(is_new=False)
     updated = update_qs.update(is_new=False)
 
 
     if updated:
     if updated:

+ 16 - 2
misago/notifications/migrations/0001_initial.py

@@ -6,6 +6,8 @@ import django.db.models.deletion
 import django.utils.timezone
 import django.utils.timezone
 from django.conf import settings
 from django.conf import settings
 
 
+from misago.core.pgutils import CreatePartialCompositeIndex
+
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
@@ -19,8 +21,8 @@ class Migration(migrations.Migration):
             fields=[
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('is_new', models.BooleanField(default=True)),
                 ('is_new', models.BooleanField(default=True)),
-                ('date', models.DateTimeField(default=django.utils.timezone.now, db_index=True)),
-                ('trigger', models.CharField(max_length=8)),
+                ('date', models.DateTimeField(default=django.utils.timezone.now)),
+                ('hash', models.CharField(max_length=8)),
                 ('message', models.TextField()),
                 ('message', models.TextField()),
                 ('checksum', models.CharField(default=b'-', max_length=64)),
                 ('checksum', models.CharField(default=b'-', max_length=64)),
                 ('url', models.TextField()),
                 ('url', models.TextField()),
@@ -33,4 +35,16 @@ class Migration(migrations.Migration):
             },
             },
             bases=(models.Model,),
             bases=(models.Model,),
         ),
         ),
+        migrations.AlterIndexTogether(
+            name='notification',
+            index_together=set([
+                ('user', 'hash'),
+            ]),
+        ),
+        CreatePartialCompositeIndex(
+            model='Notification',
+            fields=('user_id', 'is_new', 'hash'),
+            index_name='misago_notifications_notification_user_is_new_hash_partial',
+            condition='is_new = TRUE',
+        ),
     ]
     ]

+ 8 - 3
misago/notifications/models.py

@@ -10,8 +10,8 @@ class Notification(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL,
     user = models.ForeignKey(settings.AUTH_USER_MODEL,
                              related_name='misago_notifications')
                              related_name='misago_notifications')
     is_new = models.BooleanField(default=True)
     is_new = models.BooleanField(default=True)
-    date = models.DateTimeField(default=timezone.now, db_index=True)
-    trigger = models.CharField(max_length=8)
+    date = models.DateTimeField(default=timezone.now)
+    hash = models.CharField(max_length=8)
     message = models.TextField()
     message = models.TextField()
     checksum = models.CharField(max_length=64, default='-')
     checksum = models.CharField(max_length=64, default='-')
     url = models.TextField()
     url = models.TextField()
@@ -22,6 +22,11 @@ class Notification(models.Model):
     sender_username = models.CharField(max_length=255, blank=True, null=True)
     sender_username = models.CharField(max_length=255, blank=True, null=True)
     sender_slug = models.CharField(max_length=255, blank=True, null=True)
     sender_slug = models.CharField(max_length=255, blank=True, null=True)
 
 
+    class Meta:
+        index_together = [
+            ['user', 'hash'],
+        ]
+
     @property
     @property
     def is_valid(self):
     def is_valid(self):
         return is_valid(self)
         return is_valid(self)
@@ -30,7 +35,7 @@ class Notification(models.Model):
         if self.is_new:
         if self.is_new:
             return reverse('misago:go_to_notification', kwargs={
             return reverse('misago:go_to_notification', kwargs={
                 'notification_id': self.id,
                 'notification_id': self.id,
-                'trigger': self.trigger
+                'hash': self.hash
             })
             })
         else:
         else:
             return self.url
             return self.url

+ 1 - 1
misago/notifications/urls.py

@@ -3,5 +3,5 @@ from django.conf.urls import include, patterns, url
 
 
 urlpatterns = patterns('misago.notifications.views',
 urlpatterns = patterns('misago.notifications.views',
     url(r'^notifications/$', 'notifications', name='notifications'),
     url(r'^notifications/$', 'notifications', name='notifications'),
-    url(r'^notifications/go-to/(?P<notification_id>\d+)/(?P<trigger>[a-zA-Z0-9]+)/$', 'go_to_notification', name='go_to_notification'),
+    url(r'^notifications/go-to/(?P<notification_id>\d+)/(?P<hash>[a-zA-Z0-9]+)/$', 'go_to_notification', name='go_to_notification'),
 )
 )

+ 2 - 2
misago/notifications/utils.py

@@ -3,8 +3,8 @@ from hashlib import md5
 from django.conf import settings
 from django.conf import settings
 
 
 
 
-def hash_trigger(message):
-    return md5('%s:%s' % (message, settings.SECRET_KEY)).hexdigest()[:8]
+def hash_type(type):
+    return md5('%s:%s' % (type, settings.SECRET_KEY)).hexdigest()[:8]
 
 
 
 
 def variables_dict(plain=None, links=None, users=None, threads=None):
 def variables_dict(plain=None, links=None, users=None, threads=None):

+ 4 - 4
misago/notifications/views.py

@@ -49,14 +49,14 @@ def full_page(request):
 
 
 
 
 @atomic
 @atomic
-def go_to_notification(request, notification_id, trigger):
+def go_to_notification(request, notification_id, hash):
     queryset = request.user.misago_notifications.select_for_update()
     queryset = request.user.misago_notifications.select_for_update()
     notification = get_object_or_404(
     notification = get_object_or_404(
-        queryset, pk=notification_id, trigger=trigger)
+        queryset, pk=notification_id, hash=hash)
 
 
     if notification.is_new:
     if notification.is_new:
-        notification.is_new = False
-        notification.save(update_fields=['is_new'])
+        update_qs = request.user.misago_notifications.filter(hash=hash)
+        update_qs.update(is_new=False)
         assert_real_new_notifications_count(request.user)
         assert_real_new_notifications_count(request.user)
 
 
     return redirect(notification.url)
     return redirect(notification.url)

+ 7 - 1
misago/readtracker/signals.py

@@ -1,7 +1,7 @@
 from django.dispatch import receiver, Signal
 from django.dispatch import receiver, Signal
 
 
 from misago.forums.signals import move_forum_content
 from misago.forums.signals import move_forum_content
-from misago.threads.signals import move_thread
+from misago.threads.signals import move_thread, remove_thread_participant
 
 
 
 
 all_read = Signal()
 all_read = Signal()
@@ -42,3 +42,9 @@ def decrease_unread_count(sender, **kwargs):
 def zero_unread_counters(sender, **kwargs):
 def zero_unread_counters(sender, **kwargs):
     sender.new_threads.set(0)
     sender.new_threads.set(0)
     sender.unread_threads.set(0)
     sender.unread_threads.set(0)
+
+
+@receiver(remove_thread_participant)
+def remove_private_thread_readtrackers(sender, **kwargs):
+    user = kwargs['user']
+    user.threadread_set.filter(thread=sender).delete()

+ 4 - 0
misago/threads/participants.py

@@ -1,6 +1,8 @@
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 from misago.notifications import notify_user
 from misago.notifications import notify_user
+
 from misago.threads.models import ThreadParticipant
 from misago.threads.models import ThreadParticipant
+from misago.threads.signals import remove_thread_participant
 
 
 
 
 def thread_has_participants(thread):
 def thread_has_participants(thread):
@@ -62,3 +64,5 @@ def remove_participant(thread, user):
     """
     """
     thread.threadparticipant_set.filter(user=user).delete()
     thread.threadparticipant_set.filter(user=user).delete()
     set_user_unread_private_threads_sync(user)
     set_user_unread_private_threads_sync(user)
+
+    remove_thread_participant.send(thread, user=user)

+ 1 - 0
misago/threads/signals.py

@@ -13,6 +13,7 @@ merge_post = Signal()
 merge_thread = Signal(providing_args=["other_thread"])
 merge_thread = Signal(providing_args=["other_thread"])
 move_post = Signal()
 move_post = Signal()
 move_thread = Signal()
 move_thread = Signal()
+remove_thread_participant = Signal(providing_args=["user"])
 
 
 
 
 """
 """