From d0fec2d66713f09eee98f894deca3fc3d7873cfc Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 16:17:21 +0100 Subject: [PATCH 01/13] feature: adds "Mark unread" to message actions feature: adds more template tags to count messages --- django_messages/management.py | 1 + .../templates/django_messages/view.html | 3 +- .../messages_mark_unreed/full.txt | 1 + .../messages_mark_unreed/notice.html | 1 + django_messages/templatetags/inbox.py | 118 ++++++++++++++---- django_messages/urls.py | 1 + django_messages/views.py | 23 ++++ 7 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 django_messages/templates/notification/messages_mark_unreed/full.txt create mode 100644 django_messages/templates/notification/messages_mark_unreed/notice.html diff --git a/django_messages/management.py b/django_messages/management.py index 79cd9d2..ae6e318 100644 --- a/django_messages/management.py +++ b/django_messages/management.py @@ -12,6 +12,7 @@ def create_notice_types(app, created_models, verbosity, **kwargs): notification.create_notice_type("messages_reply_received", _("Reply Received"), _("you have received a reply to a message"), default=2) notification.create_notice_type("messages_deleted", _("Message Deleted"), _("you have deleted a message"), default=1) notification.create_notice_type("messages_recovered", _("Message Recovered"), _("you have undeleted a message"), default=1) + notification.create_notice_type("messages_marked_unread", _("Message Marked As Unread"), _("you have marked a message as unread"), default=1) signals.post_syncdb.connect(create_notice_types, sender=notification) else: diff --git a/django_messages/templates/django_messages/view.html b/django_messages/templates/django_messages/view.html index e360177..ebb9b83 100644 --- a/django_messages/templates/django_messages/view.html +++ b/django_messages/templates/django_messages/view.html @@ -20,6 +20,7 @@

{% trans "View Message" %}

{% trans "Reply" %} {% endifequal %} {% trans "Delete" %} +{% trans "Mark Unread" %} {% comment %}Example reply_form integration {% if reply_form %} @@ -33,4 +34,4 @@

{% trans "Compose reply"%}

{% endif %} {% endcomment %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/django_messages/templates/notification/messages_mark_unreed/full.txt b/django_messages/templates/notification/messages_mark_unreed/full.txt new file mode 100644 index 0000000..039dcbc --- /dev/null +++ b/django_messages/templates/notification/messages_mark_unreed/full.txt @@ -0,0 +1 @@ +{% load i18n %}{% blocktrans %}You have marked the message as unread '{{ message }}'.{% endblocktrans %} diff --git a/django_messages/templates/notification/messages_mark_unreed/notice.html b/django_messages/templates/notification/messages_mark_unreed/notice.html new file mode 100644 index 0000000..46cbd11 --- /dev/null +++ b/django_messages/templates/notification/messages_mark_unreed/notice.html @@ -0,0 +1 @@ +{% load i18n %}{% blocktrans with message.get_absolute_url as message_url %}You have marked the message as unread {{ message }}.{% endblocktrans %} diff --git a/django_messages/templatetags/inbox.py b/django_messages/templatetags/inbox.py index 9b65e34..4a60863 100644 --- a/django_messages/templatetags/inbox.py +++ b/django_messages/templatetags/inbox.py @@ -1,13 +1,40 @@ from django.template import Library, Node, TemplateSyntaxError + class InboxOutput(Node): - def __init__(self, varname=None): + + INBOX = 'inbox' + OUTBOX = 'outbox' + TRASH = 'trash' + + def __init__(self, varname=None, box=INBOX, only_new=True, include_deleted=False): + """ + @param varname: If provided, will assign the result into varname variable in template context + @param box: name of the message box to count + @param only_new: If set to True, will only count the new messages + @param include_deleted: if set to True, will count deleted messages in the box too + """ self.varname = varname + self.only_new = only_new + self.include_deleted = include_deleted + self.box = box def render(self, context): try: user = context['user'] - count = user.received_messages.filter(read_at__isnull=True, recipient_deleted_at__isnull=True).count() + messages = { + 'inbox': user.received_messages.all(), + 'outbox': user.sent_messages.all(), + 'trash': user.received_messages.filter(recipient_deleted_at__isnull=False), + }.get(self.box, 'inbox') + + if self.only_new: + messages = messages.filter(read_at__isnull=True) + + if not self.include_deleted: + messages = messages.filter(recipient_deleted_at__isnull=True) + + count = messages.count() except (KeyError, AttributeError): count = '' if self.varname is not None: @@ -16,30 +43,77 @@ def render(self, context): else: return "%s" % (count) -def do_print_inbox_count(parser, token): + +def get_box_count(box=InboxOutput.INBOX, include_deleted=False, only_new=True): """ - A templatetag to show the unread-count for a logged in user. - Returns the number of unread messages in the user's inbox. - Usage:: + Creates and returns a templatetag callable. + @param box: the box name to retreive the objects from + @type box: str + @param include_deleted: if set to True will include deleted messages + @type include_deleted: bool + @param only_new: if set to True will only return the new un-read messages + @type include_deleted: bool + @rtype callable + """ + def do_print_inbox_count(parser, token): + """ + A templatetag to show the message count for a logged in user. + Returns the number of messages in the user's message box. + Usage: - {% load inbox %} - {% inbox_count %} + {% load inbox %} + {% new_inbox_count %} - {# or assign the value to a variable: #} + {# or assign the value to a variable: #} - {% inbox_count as my_var %} - {{ my_var }} + {% new_inbox_count as my_var %} + {{ my_var }} - """ - bits = token.contents.split() - if len(bits) > 1: - if len(bits) != 3: - raise TemplateSyntaxError("inbox_count tag takes either no arguments or exactly two arguments") - if bits[1] != 'as': - raise TemplateSyntaxError("first argument to inbox_count tag must be 'as'") - return InboxOutput(bits[2]) - else: - return InboxOutput() + Tags: + {% new_inbox_count %} {# count of new messages in INBOX #} + {% inbox_count %} {# count of all INBOX #} + {% new_outbox_count %} + {% outbox_count %} + {% trash_count %} + + @type token: django.template.base.Token + """ + bits = token.contents.split() + if len(bits) > 1: + if len(bits) != 3: + raise TemplateSyntaxError("inbox_count tag takes either no arguments or exactly two arguments") + if bits[1] != 'as': + raise TemplateSyntaxError("first argument to inbox_count tag must be 'as'") + return InboxOutput(varname=bits[2], box=box, include_deleted=include_deleted, only_new=only_new) + else: + return InboxOutput(box=box, include_deleted=include_deleted, only_new=only_new) + + return do_print_inbox_count register = Library() -register.tag('inbox_count', do_print_inbox_count) + +setup = { + 'new_inbox_count': { + 'box': InboxOutput.INBOX, + 'include_deleted': False, + }, + 'inbox_count': { + 'box': InboxOutput.INBOX, + 'only_new': False, + }, + 'new_outbox_count': { + 'box': InboxOutput.OUTBOX + }, + 'outbox_count': { + 'box': InboxOutput.OUTBOX, + 'only_new': False, + }, + 'trash_count': { + 'box': InboxOutput.TRASH, + 'include_deleted': True, + 'only_new': False, + }, +} + +for tagname, defaults in setup.items(): + register.tag(tagname, get_box_count(**defaults)) diff --git a/django_messages/urls.py b/django_messages/urls.py index 1effc02..64eb2d4 100644 --- a/django_messages/urls.py +++ b/django_messages/urls.py @@ -13,5 +13,6 @@ url(r'^view/(?P[\d]+)/$', view, name='messages_detail'), url(r'^delete/(?P[\d]+)/$', delete, name='messages_delete'), url(r'^undelete/(?P[\d]+)/$', undelete, name='messages_undelete'), + url(r'^mark-unread/(?P[\d]+)/$', unread, name='messages_mark_unread'), url(r'^trash/$', trash, name='messages_trash'), ) diff --git a/django_messages/views.py b/django_messages/views.py index 601b459..9056a8c 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -222,3 +222,26 @@ def view(request, message_id, form_class=ComposeForm, quote_helper=format_quote, context['reply_form'] = form return render_to_response(template_name, context, context_instance=RequestContext(request)) + + +@login_required +def unread(request, message_id, success_url=None): + """ + Sets a message's state as unread. + @type request: django.http.HttpRequest + @type success_url: mix + """ + user = request.user + message = get_object_or_404(Message, id=message_id) + + if success_url is None: + success_url = reverse('messages_inbox') + if 'next' in request.GET: + success_url = request.GET['next'] + + message.read_at = None + message.save() + messages.info(request, _(u"Message successfully marked as unread.")) + if notification: + notification.send([user], "messages_marked_unread", {'message': message}) + return HttpResponseRedirect(success_url) From 4e377477927e2beb5727fd32a456e84995d2a203 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 17:44:03 +0100 Subject: [PATCH 02/13] Adds migrations bugfix: uses proper manager methods for getting box counts --- django_messages/migrations/0001_initial.py | 37 ++++++++++++++++++++++ django_messages/migrations/__init__.py | 0 django_messages/templatetags/inbox.py | 6 ++-- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 django_messages/migrations/0001_initial.py create mode 100644 django_messages/migrations/__init__.py diff --git a/django_messages/migrations/0001_initial.py b/django_messages/migrations/0001_initial.py new file mode 100644 index 0000000..bae9d8e --- /dev/null +++ b/django_messages/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('subject', models.CharField(max_length=120, verbose_name='Subject')), + ('body', models.TextField(verbose_name='Body')), + ('sent_at', models.DateTimeField(null=True, verbose_name='sent at', blank=True)), + ('read_at', models.DateTimeField(null=True, verbose_name='read at', blank=True)), + ('replied_at', models.DateTimeField(null=True, verbose_name='replied at', blank=True)), + ('sender_deleted_at', models.DateTimeField(null=True, verbose_name='Sender deleted at', blank=True)), + ('recipient_deleted_at', models.DateTimeField(null=True, verbose_name='Recipient deleted at', blank=True)), + ('parent_msg', models.ForeignKey(null=True, to='django_messages.Message', verbose_name='Parent message', blank=True, related_name='next_messages')), + ('recipient', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient', blank=True, related_name='received_messages')), + ('sender', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Sender', related_name='sent_messages')), + ], + options={ + 'ordering': ['-sent_at'], + 'verbose_name_plural': 'Messages', + 'verbose_name': 'Message', + }, + bases=(models.Model,), + ), + ] diff --git a/django_messages/migrations/__init__.py b/django_messages/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_messages/templatetags/inbox.py b/django_messages/templatetags/inbox.py index 4a60863..6784714 100644 --- a/django_messages/templatetags/inbox.py +++ b/django_messages/templatetags/inbox.py @@ -23,9 +23,9 @@ def render(self, context): try: user = context['user'] messages = { - 'inbox': user.received_messages.all(), - 'outbox': user.sent_messages.all(), - 'trash': user.received_messages.filter(recipient_deleted_at__isnull=False), + 'inbox': user.received_messages.inbox_for(user), + 'outbox': user.sent_messages.outbox_for(user), + 'trash': user.received_messages.trash_for(user) | user.sent_messages.trash_for(user), }.get(self.box, 'inbox') if self.only_new: From ea8d3adffdbdeea8fe900f6c48eccb2a1b0d51f2 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 17:51:46 +0100 Subject: [PATCH 03/13] feature: purges deleted messages. --- django_messages/management.py | 1 + .../migrations/0002_auto_20140926_1746.py | 26 ++++++++++++++++ django_messages/models.py | 5 ++++ .../templates/django_messages/trash.html | 17 ++++++----- django_messages/urls.py | 1 + django_messages/views.py | 30 +++++++++++++++++++ 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 django_messages/migrations/0002_auto_20140926_1746.py diff --git a/django_messages/management.py b/django_messages/management.py index ae6e318..1f7fb34 100644 --- a/django_messages/management.py +++ b/django_messages/management.py @@ -13,6 +13,7 @@ def create_notice_types(app, created_models, verbosity, **kwargs): notification.create_notice_type("messages_deleted", _("Message Deleted"), _("you have deleted a message"), default=1) notification.create_notice_type("messages_recovered", _("Message Recovered"), _("you have undeleted a message"), default=1) notification.create_notice_type("messages_marked_unread", _("Message Marked As Unread"), _("you have marked a message as unread"), default=1) + notification.create_notice_type("messages_purged", _("Message Purged"), _("you have purged a message"), default=1) signals.post_syncdb.connect(create_notice_types, sender=notification) else: diff --git a/django_messages/migrations/0002_auto_20140926_1746.py b/django_messages/migrations/0002_auto_20140926_1746.py new file mode 100644 index 0000000..1b013da --- /dev/null +++ b/django_messages/migrations/0002_auto_20140926_1746.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_messages', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='message', + name='purged_for_recipient', + field=models.BooleanField(db_index=True, verbose_name='Purged for recipient', default=False), + preserve_default=True, + ), + migrations.AddField( + model_name='message', + name='purged_for_sender', + field=models.BooleanField(db_index=True, verbose_name='Purged for sender', default=False), + preserve_default=True, + ), + ] diff --git a/django_messages/models.py b/django_messages/models.py index c3fe703..ff52bb2 100644 --- a/django_messages/models.py +++ b/django_messages/models.py @@ -38,14 +38,17 @@ def trash_for(self, user): return self.filter( recipient=user, recipient_deleted_at__isnull=False, + purged_for_recipient=False, ) | self.filter( sender=user, sender_deleted_at__isnull=False, + purged_for_sender=False, ) @python_2_unicode_compatible class Message(models.Model): + """ A private message from user to user """ @@ -59,6 +62,8 @@ class Message(models.Model): replied_at = models.DateTimeField(_("replied at"), null=True, blank=True) sender_deleted_at = models.DateTimeField(_("Sender deleted at"), null=True, blank=True) recipient_deleted_at = models.DateTimeField(_("Recipient deleted at"), null=True, blank=True) + purged_for_sender = models.BooleanField(_("Purged for sender"), default=False, db_index=True) + purged_for_recipient = models.BooleanField(_("Purged for recipient"), default=False, db_index=True) objects = MessageManager() diff --git a/django_messages/templates/django_messages/trash.html b/django_messages/templates/django_messages/trash.html index 02fb11e..a593a4b 100644 --- a/django_messages/templates/django_messages/trash.html +++ b/django_messages/templates/django_messages/trash.html @@ -1,30 +1,31 @@ -{% extends "django_messages/base.html" %} -{% load i18n %} +{% extends "django_messages/base.html" %} +{% load i18n %} {% load url from future %} -{% block content %} +{% block content %}

{% trans "Deleted Messages" %}

-{% if message_list %} +{% if message_list %} -{% for message in message_list %} +{% for message in message_list %} - + {% endfor %}
{% trans "Sender" %}{% trans "Subject" %}{% trans "Date" %}{% trans "Action" %}
{{ message.sender }} + {{ message.subject }} {{ message.sent_at|date:_("DATETIME_FORMAT") }} {% trans "undelete" %}{% trans "purge" %}
{% else %}

{% trans "No messages." %}

-{% endif %} +{% endif %}

{% trans "Deleted Messages are removed from the trash at unregular intervals, don't rely on this feature for long-time storage." %}

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/django_messages/urls.py b/django_messages/urls.py index 64eb2d4..f7194ff 100644 --- a/django_messages/urls.py +++ b/django_messages/urls.py @@ -15,4 +15,5 @@ url(r'^undelete/(?P[\d]+)/$', undelete, name='messages_undelete'), url(r'^mark-unread/(?P[\d]+)/$', unread, name='messages_mark_unread'), url(r'^trash/$', trash, name='messages_trash'), + url(r'^purge/(?P[\d]+)/$', purge, name='messages_purge'), ) diff --git a/django_messages/views.py b/django_messages/views.py index 9056a8c..c3c6f9b 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -245,3 +245,33 @@ def unread(request, message_id, success_url=None): if notification: notification.send([user], "messages_marked_unread", {'message': message}) return HttpResponseRedirect(success_url) + + +@login_required +def purge(request, message_id, success_url=None): + """ + Purges a deleted message from database. + @type request: django.http.HttpRequest + @type success_url: mix + """ + user = request.user + message = get_object_or_404(Message, id=message_id) + + if success_url is None: + success_url = reverse('messages_trash') + if 'next' in request.GET: + success_url = request.GET['next'] + + if message.sender == request.user: + message.purged_for_sender = True + else: + message.purged_for_recipient = True + + if message.purged_for_recipient and message.purged_for_sender: + message.delete() + else: + message.save() + messages.info(request, _(u"Message successfully purged.")) + if notification: + notification.send([user], "messages_purged", {'message': message}) + return HttpResponseRedirect(success_url) From 74f1b4c1a1167215cf21b8b955120575fefc9b24 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 18:18:38 +0100 Subject: [PATCH 04/13] feature: introduces signals Sends signals after each composing, deleting, purging, undeleting, and marking as unread --- django_messages/__init__.py | 3 ++- django_messages/forms.py | 32 +++++++++++++++++++------------- django_messages/signals.py | 9 +++++++++ django_messages/views.py | 25 ++++++++++++++++--------- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/django_messages/__init__.py b/django_messages/__init__.py index 4d068f0..471f7f0 100644 --- a/django_messages/__init__.py +++ b/django_messages/__init__.py @@ -1,3 +1,4 @@ VERSION = (0, 5, 1,) __version__ = '.'.join(map(str, VERSION)) -default_app_config = 'django_messages.apps.DjangoMessagesConfig' \ No newline at end of file +default_app_config = 'django_messages.apps.DjangoMessagesConfig' +from . import signals diff --git a/django_messages/forms.py b/django_messages/forms.py index 8aef642..fb36b0f 100644 --- a/django_messages/forms.py +++ b/django_messages/forms.py @@ -10,24 +10,25 @@ from django_messages.models import Message from django_messages.fields import CommaSeparatedUserField +from . import signals + class ComposeForm(forms.Form): + """ A simple default form for private messages. """ recipient = CommaSeparatedUserField(label=_(u"Recipient")) subject = forms.CharField(label=_(u"Subject"), max_length=120) body = forms.CharField(label=_(u"Body"), - widget=forms.Textarea(attrs={'rows': '12', 'cols':'55'})) - - + widget=forms.Textarea(attrs={'rows': '12', 'cols': '55'})) + def __init__(self, *args, **kwargs): recipient_filter = kwargs.pop('recipient_filter', None) super(ComposeForm, self).__init__(*args, **kwargs) if recipient_filter is not None: self.fields['recipient']._recipient_filter = recipient_filter - - + def save(self, sender, parent_msg=None): recipients = self.cleaned_data['recipient'] subject = self.cleaned_data['subject'] @@ -35,22 +36,27 @@ def save(self, sender, parent_msg=None): message_list = [] for r in recipients: msg = Message( - sender = sender, - recipient = r, - subject = subject, - body = body, + sender=sender, + recipient=r, + subject=subject, + body=body, ) if parent_msg is not None: msg.parent_msg = parent_msg parent_msg.replied_at = timezone.now() parent_msg.save() + signals.message_repled.send(sender=ComposeForm, message=msg, user=sender) msg.save() + + if parent_msg is None: + signals.message_sent.send(sender=ComposeForm, message=msg, user=sender) + message_list.append(msg) if notification: if parent_msg is not None: - notification.send([sender], "messages_replied", {'message': msg,}) - notification.send([r], "messages_reply_received", {'message': msg,}) + notification.send([sender], "messages_replied", {'message': msg}) + notification.send([r], "messages_reply_received", {'message': msg}) else: - notification.send([sender], "messages_sent", {'message': msg,}) - notification.send([r], "messages_received", {'message': msg,}) + notification.send([sender], "messages_sent", {'message': msg}) + notification.send([r], "messages_received", {'message': msg}) return message_list diff --git a/django_messages/signals.py b/django_messages/signals.py index e69de29..96b4875 100644 --- a/django_messages/signals.py +++ b/django_messages/signals.py @@ -0,0 +1,9 @@ + +from django.dispatch import Signal + +message_deleted = Signal(providing_args=["message", "user"]) +message_sent = Signal(providing_args=["message", "user"]) +message_repled = Signal(providing_args=["message", "user"]) +mesage_recovered = Signal(providing_args=["message", "user"]) +message_marked_as_unread = Signal(providing_args=["message", "user"]) +message_purge = Signal(providing_args=["message", "user"]) diff --git a/django_messages/views.py b/django_messages/views.py index c3c6f9b..74010ee 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -1,5 +1,5 @@ from django.http import Http404, HttpResponseRedirect -from django.shortcuts import render_to_response, get_object_or_404 +from django.shortcuts import render_to_response, get_object_or_404, render from django.template import RequestContext from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -11,6 +11,8 @@ from django_messages.models import Message from django_messages.forms import ComposeForm from django_messages.utils import format_quote, get_user_model, get_username_field +from . import signals + User = get_user_model() @@ -72,7 +74,6 @@ def compose(request, recipient=None, form_class=ComposeForm, ``success_url``: where to redirect after successfull submission """ if request.method == "POST": - sender = request.user form = form_class(request.POST, recipient_filter=recipient_filter) if form.is_valid(): form.save(sender=request.user) @@ -127,6 +128,7 @@ def reply(request, message_id, form_class=ComposeForm, 'form': form, }, context_instance=RequestContext(request)) + @login_required def delete(request, message_id, success_url=None): """ @@ -156,12 +158,14 @@ def delete(request, message_id, success_url=None): deleted = True if deleted: message.save() + signals.message_deleted.send(sender=delete, message=message, user=request.user) messages.info(request, _(u"Message successfully deleted.")) if notification: - notification.send([user], "messages_deleted", {'message': message,}) + notification.send([user], "messages_deleted", {'message': message}) return HttpResponseRedirect(success_url) raise Http404 + @login_required def undelete(request, message_id, success_url=None): """ @@ -183,16 +187,18 @@ def undelete(request, message_id, success_url=None): undeleted = True if undeleted: message.save() + signals.mesage_recovered.send(sender=undelete, message=message, user=request.user) messages.info(request, _(u"Message successfully recovered.")) if notification: - notification.send([user], "messages_recovered", {'message': message,}) + notification.send([user], "messages_recovered", {'message': message}) return HttpResponseRedirect(success_url) raise Http404 + @login_required def view(request, message_id, form_class=ComposeForm, quote_helper=format_quote, - subject_template=_(u"Re: %(subject)s"), - template_name='django_messages/view.html'): + subject_template=_(u"Re: %(subject)s"), + template_name='django_messages/view.html'): """ Shows a single message.``message_id`` argument is required. The user is only allowed to see the message, if he is either @@ -217,11 +223,10 @@ def view(request, message_id, form_class=ComposeForm, quote_helper=format_quote, form = form_class(initial={ 'body': quote_helper(message.sender, message.body), 'subject': subject_template % {'subject': message.subject}, - 'recipient': [message.sender,] + 'recipient': [message.sender] }) context['reply_form'] = form - return render_to_response(template_name, context, - context_instance=RequestContext(request)) + return render(request, template_name, context) @login_required @@ -241,6 +246,7 @@ def unread(request, message_id, success_url=None): message.read_at = None message.save() + signals.message_marked_as_unread.send(sender=unread, message=message, user=request.user) messages.info(request, _(u"Message successfully marked as unread.")) if notification: notification.send([user], "messages_marked_unread", {'message': message}) @@ -271,6 +277,7 @@ def purge(request, message_id, success_url=None): message.delete() else: message.save() + signals.message_purge.send(sender=purge, message=message, user=request.user) messages.info(request, _(u"Message successfully purged.")) if notification: notification.send([user], "messages_purged", {'message': message}) From 3c89211d6f410074fad6a41cbea5628e2067e08c Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Mon, 29 Sep 2014 09:27:48 +0100 Subject: [PATCH 05/13] Moves notifications in signals --- django_messages/forms.py | 16 +----------- django_messages/models.py | 6 ----- django_messages/signals.py | 51 +++++++++++++++++++++++++++++++++++++- django_messages/views.py | 24 ++++++------------ 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/django_messages/forms.py b/django_messages/forms.py index fb36b0f..9ed6611 100644 --- a/django_messages/forms.py +++ b/django_messages/forms.py @@ -1,16 +1,9 @@ from django import forms -from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.utils import timezone - -if "notification" in settings.INSTALLED_APPS: - from notification import models as notification -else: - notification = None - from django_messages.models import Message from django_messages.fields import CommaSeparatedUserField -from . import signals +from django_messages import signals class ComposeForm(forms.Form): @@ -52,11 +45,4 @@ def save(self, sender, parent_msg=None): signals.message_sent.send(sender=ComposeForm, message=msg, user=sender) message_list.append(msg) - if notification: - if parent_msg is not None: - notification.send([sender], "messages_replied", {'message': msg}) - notification.send([r], "messages_reply_received", {'message': msg}) - else: - notification.send([sender], "messages_sent", {'message': msg}) - notification.send([r], "messages_received", {'message': msg}) return message_list diff --git a/django_messages/models.py b/django_messages/models.py index ff52bb2..941b248 100644 --- a/django_messages/models.py +++ b/django_messages/models.py @@ -1,6 +1,5 @@ from django.conf import settings from django.db import models -from django.db.models import signals from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -103,8 +102,3 @@ def inbox_count_for(user): mark them seen """ return Message.objects.filter(recipient=user, read_at__isnull=True, recipient_deleted_at__isnull=True).count() - -# fallback for email notification if django-notification could not be found -if "notification" not in settings.INSTALLED_APPS: - from django_messages.utils import new_message_email - signals.post_save.connect(new_message_email, sender=Message) diff --git a/django_messages/signals.py b/django_messages/signals.py index 96b4875..0b79afa 100644 --- a/django_messages/signals.py +++ b/django_messages/signals.py @@ -1,4 +1,4 @@ - +from django.conf import settings from django.dispatch import Signal message_deleted = Signal(providing_args=["message", "user"]) @@ -7,3 +7,52 @@ mesage_recovered = Signal(providing_args=["message", "user"]) message_marked_as_unread = Signal(providing_args=["message", "user"]) message_purge = Signal(providing_args=["message", "user"]) + + +if "notification" in settings.INSTALLED_APPS: + from notification import models as notification + from django_messages.forms import ComposeForm + from django_messages.views import delete, undelete, unread, purge + + def sent_notification(sender, **kwargs): + msg = kwargs['message'] + notification.send([msg.sender], "messages_sent", {'message': msg}) + notification.send([msg.recipient], "messages_received", {'message': msg}) + + def replied_notification(sender, **kwargs): + msg = kwargs['message'] + notification.send([msg.sender], "messages_replied", {'message': msg}) + notification.send([msg.recipient], "messages_reply_received", {'message': msg}) + + def deleted_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_deleted", {'message': msg}) + + def recovered_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_recovered", {'message': msg}) + + def unread_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_marked_unread", {'message': msg}) + + def purge_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_purged", {'message': msg}) + + message_deleted.connect(deleted_notification, sender=delete) + message_sent.connect(sent_notification, sender=ComposeForm) + message_repled.connect(replied_notification, sender=ComposeForm) + mesage_recovered.connect(recovered_notification, sender=undelete) + message_marked_as_unread.connect(unread_notification, sender=unread) + message_purge.connect(purge_notification, sender=purge) + + # fallback for email notification if django-notification could not be found + from django_messages.utils import new_message_email + from django.db.models import signals + from django_messages.models import Message + signals.post_save.connect(new_message_email, sender=Message) diff --git a/django_messages/views.py b/django_messages/views.py index 74010ee..4731cf0 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -6,7 +6,6 @@ from django.utils.translation import ugettext as _ from django.utils import timezone from django.core.urlresolvers import reverse -from django.conf import settings from django_messages.models import Message from django_messages.forms import ComposeForm @@ -16,10 +15,6 @@ User = get_user_model() -if "notification" in settings.INSTALLED_APPS: - from notification import models as notification -else: - notification = None @login_required def inbox(request, template_name='django_messages/inbox.html'): @@ -110,7 +105,6 @@ def reply(request, message_id, form_class=ComposeForm, raise Http404 if request.method == "POST": - sender = request.user form = form_class(request.POST, recipient_filter=recipient_filter) if form.is_valid(): form.save(sender=request.user, parent_msg=parent) @@ -160,8 +154,7 @@ def delete(request, message_id, success_url=None): message.save() signals.message_deleted.send(sender=delete, message=message, user=request.user) messages.info(request, _(u"Message successfully deleted.")) - if notification: - notification.send([user], "messages_deleted", {'message': message}) + return HttpResponseRedirect(success_url) raise Http404 @@ -189,8 +182,7 @@ def undelete(request, message_id, success_url=None): message.save() signals.mesage_recovered.send(sender=undelete, message=message, user=request.user) messages.info(request, _(u"Message successfully recovered.")) - if notification: - notification.send([user], "messages_recovered", {'message': message}) + return HttpResponseRedirect(success_url) raise Http404 @@ -236,7 +228,6 @@ def unread(request, message_id, success_url=None): @type request: django.http.HttpRequest @type success_url: mix """ - user = request.user message = get_object_or_404(Message, id=message_id) if success_url is None: @@ -248,8 +239,7 @@ def unread(request, message_id, success_url=None): message.save() signals.message_marked_as_unread.send(sender=unread, message=message, user=request.user) messages.info(request, _(u"Message successfully marked as unread.")) - if notification: - notification.send([user], "messages_marked_unread", {'message': message}) + return HttpResponseRedirect(success_url) @@ -260,7 +250,6 @@ def purge(request, message_id, success_url=None): @type request: django.http.HttpRequest @type success_url: mix """ - user = request.user message = get_object_or_404(Message, id=message_id) if success_url is None: @@ -273,12 +262,13 @@ def purge(request, message_id, success_url=None): else: message.purged_for_recipient = True - if message.purged_for_recipient and message.purged_for_sender: + if message.sender == message.recipient: + message.delete() + elif message.purged_for_recipient and message.purged_for_sender: message.delete() else: message.save() signals.message_purge.send(sender=purge, message=message, user=request.user) messages.info(request, _(u"Message successfully purged.")) - if notification: - notification.send([user], "messages_purged", {'message': message}) + return HttpResponseRedirect(success_url) From 676245202d357258a594b66903c58b9481793057 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Mon, 29 Sep 2014 10:20:37 +0100 Subject: [PATCH 06/13] bugfix: configures the settings during installation --- django_messages/signals.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_messages/signals.py b/django_messages/signals.py index 0b79afa..d17d60b 100644 --- a/django_messages/signals.py +++ b/django_messages/signals.py @@ -8,6 +8,12 @@ message_marked_as_unread = Signal(providing_args=["message", "user"]) message_purge = Signal(providing_args=["message", "user"]) +try: + #If it's during installation, we should configure the settings otherwise it fails + settings.configure() +except RuntimeError: + # Already configured (installation is complete) + pass if "notification" in settings.INSTALLED_APPS: from notification import models as notification From 9e68d331ce39e3cdcf85011cb29d834c1b116801 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 17:44:03 +0100 Subject: [PATCH 07/13] Adds migrations bugfix: uses proper manager methods for getting box counts --- django_messages/migrations/0001_initial.py | 37 ++++++++++++++++++++++ django_messages/migrations/__init__.py | 0 django_messages/templatetags/inbox.py | 6 ++-- setup.py | 3 +- 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 django_messages/migrations/0001_initial.py create mode 100644 django_messages/migrations/__init__.py diff --git a/django_messages/migrations/0001_initial.py b/django_messages/migrations/0001_initial.py new file mode 100644 index 0000000..bae9d8e --- /dev/null +++ b/django_messages/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), + ('subject', models.CharField(max_length=120, verbose_name='Subject')), + ('body', models.TextField(verbose_name='Body')), + ('sent_at', models.DateTimeField(null=True, verbose_name='sent at', blank=True)), + ('read_at', models.DateTimeField(null=True, verbose_name='read at', blank=True)), + ('replied_at', models.DateTimeField(null=True, verbose_name='replied at', blank=True)), + ('sender_deleted_at', models.DateTimeField(null=True, verbose_name='Sender deleted at', blank=True)), + ('recipient_deleted_at', models.DateTimeField(null=True, verbose_name='Recipient deleted at', blank=True)), + ('parent_msg', models.ForeignKey(null=True, to='django_messages.Message', verbose_name='Parent message', blank=True, related_name='next_messages')), + ('recipient', models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient', blank=True, related_name='received_messages')), + ('sender', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Sender', related_name='sent_messages')), + ], + options={ + 'ordering': ['-sent_at'], + 'verbose_name_plural': 'Messages', + 'verbose_name': 'Message', + }, + bases=(models.Model,), + ), + ] diff --git a/django_messages/migrations/__init__.py b/django_messages/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_messages/templatetags/inbox.py b/django_messages/templatetags/inbox.py index 4a60863..6784714 100644 --- a/django_messages/templatetags/inbox.py +++ b/django_messages/templatetags/inbox.py @@ -23,9 +23,9 @@ def render(self, context): try: user = context['user'] messages = { - 'inbox': user.received_messages.all(), - 'outbox': user.sent_messages.all(), - 'trash': user.received_messages.filter(recipient_deleted_at__isnull=False), + 'inbox': user.received_messages.inbox_for(user), + 'outbox': user.sent_messages.outbox_for(user), + 'trash': user.received_messages.trash_for(user) | user.sent_messages.trash_for(user), }.get(self.box, 'inbox') if self.only_new: diff --git a/setup.py b/setup.py index 5530c06..bc395f6 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ packages=( 'django_messages', 'django_messages.templatetags', + 'django_messages.migrations', ), package_data={ 'django_messages': [ @@ -29,4 +30,4 @@ 'Topic :: Utilities', 'Framework :: Django', ), -) \ No newline at end of file +) From 210200d7c39c080f27240377c40b19045d039fe2 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 17:51:46 +0100 Subject: [PATCH 08/13] feature: purges deleted messages. --- django_messages/management.py | 1 + .../migrations/0002_auto_20140926_1746.py | 26 ++++++++++++++++ django_messages/models.py | 5 ++++ .../templates/django_messages/trash.html | 17 ++++++----- django_messages/urls.py | 1 + django_messages/views.py | 30 +++++++++++++++++++ 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 django_messages/migrations/0002_auto_20140926_1746.py diff --git a/django_messages/management.py b/django_messages/management.py index ae6e318..1f7fb34 100644 --- a/django_messages/management.py +++ b/django_messages/management.py @@ -13,6 +13,7 @@ def create_notice_types(app, created_models, verbosity, **kwargs): notification.create_notice_type("messages_deleted", _("Message Deleted"), _("you have deleted a message"), default=1) notification.create_notice_type("messages_recovered", _("Message Recovered"), _("you have undeleted a message"), default=1) notification.create_notice_type("messages_marked_unread", _("Message Marked As Unread"), _("you have marked a message as unread"), default=1) + notification.create_notice_type("messages_purged", _("Message Purged"), _("you have purged a message"), default=1) signals.post_syncdb.connect(create_notice_types, sender=notification) else: diff --git a/django_messages/migrations/0002_auto_20140926_1746.py b/django_messages/migrations/0002_auto_20140926_1746.py new file mode 100644 index 0000000..1b013da --- /dev/null +++ b/django_messages/migrations/0002_auto_20140926_1746.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_messages', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='message', + name='purged_for_recipient', + field=models.BooleanField(db_index=True, verbose_name='Purged for recipient', default=False), + preserve_default=True, + ), + migrations.AddField( + model_name='message', + name='purged_for_sender', + field=models.BooleanField(db_index=True, verbose_name='Purged for sender', default=False), + preserve_default=True, + ), + ] diff --git a/django_messages/models.py b/django_messages/models.py index c3fe703..ff52bb2 100644 --- a/django_messages/models.py +++ b/django_messages/models.py @@ -38,14 +38,17 @@ def trash_for(self, user): return self.filter( recipient=user, recipient_deleted_at__isnull=False, + purged_for_recipient=False, ) | self.filter( sender=user, sender_deleted_at__isnull=False, + purged_for_sender=False, ) @python_2_unicode_compatible class Message(models.Model): + """ A private message from user to user """ @@ -59,6 +62,8 @@ class Message(models.Model): replied_at = models.DateTimeField(_("replied at"), null=True, blank=True) sender_deleted_at = models.DateTimeField(_("Sender deleted at"), null=True, blank=True) recipient_deleted_at = models.DateTimeField(_("Recipient deleted at"), null=True, blank=True) + purged_for_sender = models.BooleanField(_("Purged for sender"), default=False, db_index=True) + purged_for_recipient = models.BooleanField(_("Purged for recipient"), default=False, db_index=True) objects = MessageManager() diff --git a/django_messages/templates/django_messages/trash.html b/django_messages/templates/django_messages/trash.html index 02fb11e..a593a4b 100644 --- a/django_messages/templates/django_messages/trash.html +++ b/django_messages/templates/django_messages/trash.html @@ -1,30 +1,31 @@ -{% extends "django_messages/base.html" %} -{% load i18n %} +{% extends "django_messages/base.html" %} +{% load i18n %} {% load url from future %} -{% block content %} +{% block content %}

{% trans "Deleted Messages" %}

-{% if message_list %} +{% if message_list %} -{% for message in message_list %} +{% for message in message_list %} - + {% endfor %}
{% trans "Sender" %}{% trans "Subject" %}{% trans "Date" %}{% trans "Action" %}
{{ message.sender }} + {{ message.subject }} {{ message.sent_at|date:_("DATETIME_FORMAT") }} {% trans "undelete" %}{% trans "purge" %}
{% else %}

{% trans "No messages." %}

-{% endif %} +{% endif %}

{% trans "Deleted Messages are removed from the trash at unregular intervals, don't rely on this feature for long-time storage." %}

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/django_messages/urls.py b/django_messages/urls.py index 64eb2d4..f7194ff 100644 --- a/django_messages/urls.py +++ b/django_messages/urls.py @@ -15,4 +15,5 @@ url(r'^undelete/(?P[\d]+)/$', undelete, name='messages_undelete'), url(r'^mark-unread/(?P[\d]+)/$', unread, name='messages_mark_unread'), url(r'^trash/$', trash, name='messages_trash'), + url(r'^purge/(?P[\d]+)/$', purge, name='messages_purge'), ) diff --git a/django_messages/views.py b/django_messages/views.py index 9056a8c..c3c6f9b 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -245,3 +245,33 @@ def unread(request, message_id, success_url=None): if notification: notification.send([user], "messages_marked_unread", {'message': message}) return HttpResponseRedirect(success_url) + + +@login_required +def purge(request, message_id, success_url=None): + """ + Purges a deleted message from database. + @type request: django.http.HttpRequest + @type success_url: mix + """ + user = request.user + message = get_object_or_404(Message, id=message_id) + + if success_url is None: + success_url = reverse('messages_trash') + if 'next' in request.GET: + success_url = request.GET['next'] + + if message.sender == request.user: + message.purged_for_sender = True + else: + message.purged_for_recipient = True + + if message.purged_for_recipient and message.purged_for_sender: + message.delete() + else: + message.save() + messages.info(request, _(u"Message successfully purged.")) + if notification: + notification.send([user], "messages_purged", {'message': message}) + return HttpResponseRedirect(success_url) From 596d70f94acbdbb396e10f22dc5b8da2b973b3f7 Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Fri, 26 Sep 2014 18:18:38 +0100 Subject: [PATCH 09/13] feature: introduces signals Sends signals after each composing, deleting, purging, undeleting, and marking as unread --- django_messages/__init__.py | 3 ++- django_messages/forms.py | 32 +++++++++++++++++++------------- django_messages/signals.py | 9 +++++++++ django_messages/views.py | 25 ++++++++++++++++--------- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/django_messages/__init__.py b/django_messages/__init__.py index 4d068f0..471f7f0 100644 --- a/django_messages/__init__.py +++ b/django_messages/__init__.py @@ -1,3 +1,4 @@ VERSION = (0, 5, 1,) __version__ = '.'.join(map(str, VERSION)) -default_app_config = 'django_messages.apps.DjangoMessagesConfig' \ No newline at end of file +default_app_config = 'django_messages.apps.DjangoMessagesConfig' +from . import signals diff --git a/django_messages/forms.py b/django_messages/forms.py index 8aef642..fb36b0f 100644 --- a/django_messages/forms.py +++ b/django_messages/forms.py @@ -10,24 +10,25 @@ from django_messages.models import Message from django_messages.fields import CommaSeparatedUserField +from . import signals + class ComposeForm(forms.Form): + """ A simple default form for private messages. """ recipient = CommaSeparatedUserField(label=_(u"Recipient")) subject = forms.CharField(label=_(u"Subject"), max_length=120) body = forms.CharField(label=_(u"Body"), - widget=forms.Textarea(attrs={'rows': '12', 'cols':'55'})) - - + widget=forms.Textarea(attrs={'rows': '12', 'cols': '55'})) + def __init__(self, *args, **kwargs): recipient_filter = kwargs.pop('recipient_filter', None) super(ComposeForm, self).__init__(*args, **kwargs) if recipient_filter is not None: self.fields['recipient']._recipient_filter = recipient_filter - - + def save(self, sender, parent_msg=None): recipients = self.cleaned_data['recipient'] subject = self.cleaned_data['subject'] @@ -35,22 +36,27 @@ def save(self, sender, parent_msg=None): message_list = [] for r in recipients: msg = Message( - sender = sender, - recipient = r, - subject = subject, - body = body, + sender=sender, + recipient=r, + subject=subject, + body=body, ) if parent_msg is not None: msg.parent_msg = parent_msg parent_msg.replied_at = timezone.now() parent_msg.save() + signals.message_repled.send(sender=ComposeForm, message=msg, user=sender) msg.save() + + if parent_msg is None: + signals.message_sent.send(sender=ComposeForm, message=msg, user=sender) + message_list.append(msg) if notification: if parent_msg is not None: - notification.send([sender], "messages_replied", {'message': msg,}) - notification.send([r], "messages_reply_received", {'message': msg,}) + notification.send([sender], "messages_replied", {'message': msg}) + notification.send([r], "messages_reply_received", {'message': msg}) else: - notification.send([sender], "messages_sent", {'message': msg,}) - notification.send([r], "messages_received", {'message': msg,}) + notification.send([sender], "messages_sent", {'message': msg}) + notification.send([r], "messages_received", {'message': msg}) return message_list diff --git a/django_messages/signals.py b/django_messages/signals.py index e69de29..96b4875 100644 --- a/django_messages/signals.py +++ b/django_messages/signals.py @@ -0,0 +1,9 @@ + +from django.dispatch import Signal + +message_deleted = Signal(providing_args=["message", "user"]) +message_sent = Signal(providing_args=["message", "user"]) +message_repled = Signal(providing_args=["message", "user"]) +mesage_recovered = Signal(providing_args=["message", "user"]) +message_marked_as_unread = Signal(providing_args=["message", "user"]) +message_purge = Signal(providing_args=["message", "user"]) diff --git a/django_messages/views.py b/django_messages/views.py index c3c6f9b..74010ee 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -1,5 +1,5 @@ from django.http import Http404, HttpResponseRedirect -from django.shortcuts import render_to_response, get_object_or_404 +from django.shortcuts import render_to_response, get_object_or_404, render from django.template import RequestContext from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -11,6 +11,8 @@ from django_messages.models import Message from django_messages.forms import ComposeForm from django_messages.utils import format_quote, get_user_model, get_username_field +from . import signals + User = get_user_model() @@ -72,7 +74,6 @@ def compose(request, recipient=None, form_class=ComposeForm, ``success_url``: where to redirect after successfull submission """ if request.method == "POST": - sender = request.user form = form_class(request.POST, recipient_filter=recipient_filter) if form.is_valid(): form.save(sender=request.user) @@ -127,6 +128,7 @@ def reply(request, message_id, form_class=ComposeForm, 'form': form, }, context_instance=RequestContext(request)) + @login_required def delete(request, message_id, success_url=None): """ @@ -156,12 +158,14 @@ def delete(request, message_id, success_url=None): deleted = True if deleted: message.save() + signals.message_deleted.send(sender=delete, message=message, user=request.user) messages.info(request, _(u"Message successfully deleted.")) if notification: - notification.send([user], "messages_deleted", {'message': message,}) + notification.send([user], "messages_deleted", {'message': message}) return HttpResponseRedirect(success_url) raise Http404 + @login_required def undelete(request, message_id, success_url=None): """ @@ -183,16 +187,18 @@ def undelete(request, message_id, success_url=None): undeleted = True if undeleted: message.save() + signals.mesage_recovered.send(sender=undelete, message=message, user=request.user) messages.info(request, _(u"Message successfully recovered.")) if notification: - notification.send([user], "messages_recovered", {'message': message,}) + notification.send([user], "messages_recovered", {'message': message}) return HttpResponseRedirect(success_url) raise Http404 + @login_required def view(request, message_id, form_class=ComposeForm, quote_helper=format_quote, - subject_template=_(u"Re: %(subject)s"), - template_name='django_messages/view.html'): + subject_template=_(u"Re: %(subject)s"), + template_name='django_messages/view.html'): """ Shows a single message.``message_id`` argument is required. The user is only allowed to see the message, if he is either @@ -217,11 +223,10 @@ def view(request, message_id, form_class=ComposeForm, quote_helper=format_quote, form = form_class(initial={ 'body': quote_helper(message.sender, message.body), 'subject': subject_template % {'subject': message.subject}, - 'recipient': [message.sender,] + 'recipient': [message.sender] }) context['reply_form'] = form - return render_to_response(template_name, context, - context_instance=RequestContext(request)) + return render(request, template_name, context) @login_required @@ -241,6 +246,7 @@ def unread(request, message_id, success_url=None): message.read_at = None message.save() + signals.message_marked_as_unread.send(sender=unread, message=message, user=request.user) messages.info(request, _(u"Message successfully marked as unread.")) if notification: notification.send([user], "messages_marked_unread", {'message': message}) @@ -271,6 +277,7 @@ def purge(request, message_id, success_url=None): message.delete() else: message.save() + signals.message_purge.send(sender=purge, message=message, user=request.user) messages.info(request, _(u"Message successfully purged.")) if notification: notification.send([user], "messages_purged", {'message': message}) From 9c494bb57500b36d86d1bcfeed7a1c25d6bd898c Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Mon, 29 Sep 2014 09:27:48 +0100 Subject: [PATCH 10/13] Moves notifications in signals --- django_messages/forms.py | 16 +----------- django_messages/models.py | 6 ----- django_messages/signals.py | 51 +++++++++++++++++++++++++++++++++++++- django_messages/views.py | 24 ++++++------------ 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/django_messages/forms.py b/django_messages/forms.py index fb36b0f..9ed6611 100644 --- a/django_messages/forms.py +++ b/django_messages/forms.py @@ -1,16 +1,9 @@ from django import forms -from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.utils import timezone - -if "notification" in settings.INSTALLED_APPS: - from notification import models as notification -else: - notification = None - from django_messages.models import Message from django_messages.fields import CommaSeparatedUserField -from . import signals +from django_messages import signals class ComposeForm(forms.Form): @@ -52,11 +45,4 @@ def save(self, sender, parent_msg=None): signals.message_sent.send(sender=ComposeForm, message=msg, user=sender) message_list.append(msg) - if notification: - if parent_msg is not None: - notification.send([sender], "messages_replied", {'message': msg}) - notification.send([r], "messages_reply_received", {'message': msg}) - else: - notification.send([sender], "messages_sent", {'message': msg}) - notification.send([r], "messages_received", {'message': msg}) return message_list diff --git a/django_messages/models.py b/django_messages/models.py index ff52bb2..941b248 100644 --- a/django_messages/models.py +++ b/django_messages/models.py @@ -1,6 +1,5 @@ from django.conf import settings from django.db import models -from django.db.models import signals from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -103,8 +102,3 @@ def inbox_count_for(user): mark them seen """ return Message.objects.filter(recipient=user, read_at__isnull=True, recipient_deleted_at__isnull=True).count() - -# fallback for email notification if django-notification could not be found -if "notification" not in settings.INSTALLED_APPS: - from django_messages.utils import new_message_email - signals.post_save.connect(new_message_email, sender=Message) diff --git a/django_messages/signals.py b/django_messages/signals.py index 96b4875..0b79afa 100644 --- a/django_messages/signals.py +++ b/django_messages/signals.py @@ -1,4 +1,4 @@ - +from django.conf import settings from django.dispatch import Signal message_deleted = Signal(providing_args=["message", "user"]) @@ -7,3 +7,52 @@ mesage_recovered = Signal(providing_args=["message", "user"]) message_marked_as_unread = Signal(providing_args=["message", "user"]) message_purge = Signal(providing_args=["message", "user"]) + + +if "notification" in settings.INSTALLED_APPS: + from notification import models as notification + from django_messages.forms import ComposeForm + from django_messages.views import delete, undelete, unread, purge + + def sent_notification(sender, **kwargs): + msg = kwargs['message'] + notification.send([msg.sender], "messages_sent", {'message': msg}) + notification.send([msg.recipient], "messages_received", {'message': msg}) + + def replied_notification(sender, **kwargs): + msg = kwargs['message'] + notification.send([msg.sender], "messages_replied", {'message': msg}) + notification.send([msg.recipient], "messages_reply_received", {'message': msg}) + + def deleted_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_deleted", {'message': msg}) + + def recovered_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_recovered", {'message': msg}) + + def unread_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_marked_unread", {'message': msg}) + + def purge_notification(sender, **kwargs): + msg = kwargs['message'] + user = kwargs['user'] + notification.send([user], "messages_purged", {'message': msg}) + + message_deleted.connect(deleted_notification, sender=delete) + message_sent.connect(sent_notification, sender=ComposeForm) + message_repled.connect(replied_notification, sender=ComposeForm) + mesage_recovered.connect(recovered_notification, sender=undelete) + message_marked_as_unread.connect(unread_notification, sender=unread) + message_purge.connect(purge_notification, sender=purge) + + # fallback for email notification if django-notification could not be found + from django_messages.utils import new_message_email + from django.db.models import signals + from django_messages.models import Message + signals.post_save.connect(new_message_email, sender=Message) diff --git a/django_messages/views.py b/django_messages/views.py index 74010ee..4731cf0 100644 --- a/django_messages/views.py +++ b/django_messages/views.py @@ -6,7 +6,6 @@ from django.utils.translation import ugettext as _ from django.utils import timezone from django.core.urlresolvers import reverse -from django.conf import settings from django_messages.models import Message from django_messages.forms import ComposeForm @@ -16,10 +15,6 @@ User = get_user_model() -if "notification" in settings.INSTALLED_APPS: - from notification import models as notification -else: - notification = None @login_required def inbox(request, template_name='django_messages/inbox.html'): @@ -110,7 +105,6 @@ def reply(request, message_id, form_class=ComposeForm, raise Http404 if request.method == "POST": - sender = request.user form = form_class(request.POST, recipient_filter=recipient_filter) if form.is_valid(): form.save(sender=request.user, parent_msg=parent) @@ -160,8 +154,7 @@ def delete(request, message_id, success_url=None): message.save() signals.message_deleted.send(sender=delete, message=message, user=request.user) messages.info(request, _(u"Message successfully deleted.")) - if notification: - notification.send([user], "messages_deleted", {'message': message}) + return HttpResponseRedirect(success_url) raise Http404 @@ -189,8 +182,7 @@ def undelete(request, message_id, success_url=None): message.save() signals.mesage_recovered.send(sender=undelete, message=message, user=request.user) messages.info(request, _(u"Message successfully recovered.")) - if notification: - notification.send([user], "messages_recovered", {'message': message}) + return HttpResponseRedirect(success_url) raise Http404 @@ -236,7 +228,6 @@ def unread(request, message_id, success_url=None): @type request: django.http.HttpRequest @type success_url: mix """ - user = request.user message = get_object_or_404(Message, id=message_id) if success_url is None: @@ -248,8 +239,7 @@ def unread(request, message_id, success_url=None): message.save() signals.message_marked_as_unread.send(sender=unread, message=message, user=request.user) messages.info(request, _(u"Message successfully marked as unread.")) - if notification: - notification.send([user], "messages_marked_unread", {'message': message}) + return HttpResponseRedirect(success_url) @@ -260,7 +250,6 @@ def purge(request, message_id, success_url=None): @type request: django.http.HttpRequest @type success_url: mix """ - user = request.user message = get_object_or_404(Message, id=message_id) if success_url is None: @@ -273,12 +262,13 @@ def purge(request, message_id, success_url=None): else: message.purged_for_recipient = True - if message.purged_for_recipient and message.purged_for_sender: + if message.sender == message.recipient: + message.delete() + elif message.purged_for_recipient and message.purged_for_sender: message.delete() else: message.save() signals.message_purge.send(sender=purge, message=message, user=request.user) messages.info(request, _(u"Message successfully purged.")) - if notification: - notification.send([user], "messages_purged", {'message': message}) + return HttpResponseRedirect(success_url) From 8bf5ada56e86087a19608c2f195c120a5ab5287b Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Mon, 29 Sep 2014 10:20:37 +0100 Subject: [PATCH 11/13] bugfix: configures the settings during installation --- django_messages/signals.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_messages/signals.py b/django_messages/signals.py index 0b79afa..d17d60b 100644 --- a/django_messages/signals.py +++ b/django_messages/signals.py @@ -8,6 +8,12 @@ message_marked_as_unread = Signal(providing_args=["message", "user"]) message_purge = Signal(providing_args=["message", "user"]) +try: + #If it's during installation, we should configure the settings otherwise it fails + settings.configure() +except RuntimeError: + # Already configured (installation is complete) + pass if "notification" in settings.INSTALLED_APPS: from notification import models as notification From df047ce2d7adb1f0bb4f0333e9ff7f79cc321b4f Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Tue, 30 Sep 2014 17:20:53 +0100 Subject: [PATCH 12/13] Adds a new property in settings to turn off admin discovery --- django_messages/admin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/django_messages/admin.py b/django_messages/admin.py index 91d004a..77e010d 100644 --- a/django_messages/admin.py +++ b/django_messages/admin.py @@ -11,7 +11,7 @@ from notification import models as notification else: notification = None - + from django_messages.models import Message class MessageAdminForm(forms.ModelForm): @@ -75,7 +75,7 @@ def save_model(self, request, obj, form, change): the message is effectively resent to those users. """ obj.save() - + if notification: # Getting the appropriate notice labels for the sender and recipients. if obj.parent_msg is None: @@ -84,7 +84,7 @@ def save_model(self, request, obj, form, change): else: sender_label = 'messages_replied' recipients_label = 'messages_reply_received' - + # Notification for the sender. notification.send([obj.sender], sender_label, {'message': obj,}) @@ -108,5 +108,6 @@ def save_model(self, request, obj, form, change): if notification: # Notification for the recipient. notification.send([user], recipients_label, {'message' : obj,}) - -admin.site.register(Message, MessageAdmin) + +if getattr(settings, 'DJANGO_MESSAGES_ADMIN_PANEL', True): + admin.site.register(Message, MessageAdmin) From f1092f52b19d26a17cf0cf882a99f03adb1a417a Mon Sep 17 00:00:00 2001 From: Arsham Shirvani Date: Tue, 11 Nov 2014 10:43:19 +0000 Subject: [PATCH 13/13] bugfix: When replying to a message, saves the message before sending the signal. (symptom: you cannot access the pk of the message in the signal) --- django_messages/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django_messages/forms.py b/django_messages/forms.py index 9ed6611..19cb462 100644 --- a/django_messages/forms.py +++ b/django_messages/forms.py @@ -38,10 +38,10 @@ def save(self, sender, parent_msg=None): msg.parent_msg = parent_msg parent_msg.replied_at = timezone.now() parent_msg.save() + msg.save() signals.message_repled.send(sender=ComposeForm, message=msg, user=sender) - msg.save() - - if parent_msg is None: + else: + msg.save() signals.message_sent.send(sender=ComposeForm, message=msg, user=sender) message_list.append(msg)