Browse Source

Replaced set_flash with djnago-consistent api. #146

Rafał Pitoń 12 years ago
parent
commit
498e3a9516
36 changed files with 2513 additions and 2474 deletions
  1. 87 86
      misago/apps/activation/views.py
  2. 3 2
      misago/apps/admin/forums/views.py
  3. 203 202
      misago/apps/admin/newsletters/views.py
  4. 13 11
      misago/apps/admin/pruneusers/views.py
  5. 39 38
      misago/apps/admin/roles/views.py
  6. 3 2
      misago/apps/admin/settings/views.py
  7. 3 2
      misago/apps/admin/stats/views.py
  8. 9 8
      misago/apps/admin/widgets.py
  9. 116 115
      misago/apps/privatethreads/jumps.py
  10. 90 89
      misago/apps/privatethreads/posting.py
  11. 24 23
      misago/apps/readall.py
  12. 84 84
      misago/apps/register/views.py
  13. 122 121
      misago/apps/reports/list.py
  14. 47 46
      misago/apps/reports/posting.py
  15. 74 73
      misago/apps/reports/thread.py
  16. 87 88
      misago/apps/resetpswd/views.py
  17. 119 118
      misago/apps/signin/views.py
  18. 40 39
      misago/apps/threads/posting.py
  19. 128 127
      misago/apps/threadtype/changelog.py
  20. 233 232
      misago/apps/threadtype/delete.py
  21. 369 368
      misago/apps/threadtype/jumps.py
  22. 24 23
      misago/apps/threadtype/list/moderation.py
  23. 16 15
      misago/apps/threadtype/thread/moderation/posts.py
  24. 12 11
      misago/apps/threadtype/thread/moderation/thread.py
  25. 229 228
      misago/apps/usercp/avatar/views.py
  26. 72 71
      misago/apps/usercp/credentials/views.py
  27. 41 40
      misago/apps/usercp/options/views.py
  28. 46 45
      misago/apps/usercp/signature/views.py
  29. 71 70
      misago/apps/usercp/username/views.py
  30. 77 76
      misago/apps/usercp/views.py
  31. 17 7
      misago/messages.py
  32. 3 2
      misago/utils/views.py
  33. 4 4
      templates/admin/macros.html
  34. 2 2
      templates/admin/signin.html
  35. 4 4
      templates/cranefly/macros.html
  36. 2 2
      templates/cranefly/signin.html

+ 87 - 86
misago/apps/activation/views.py

@@ -1,86 +1,87 @@
-from django.template import RequestContext
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error404, error_banned
-from misago.auth import sign_user_in
-from misago.decorators import block_authenticated, block_banned, block_crawlers, block_jammed
-from misago.messages import Message
-from misago.models import Ban, User
-from misago.shortcuts import redirect_message, render_to_response
-from misago.apps.activation.forms import UserSendActivationMailForm
-
-@block_crawlers
-@block_banned
-@block_authenticated
-@block_jammed
-def form(request):
-    message = None
-    if request.method == 'POST':
-        form = UserSendActivationMailForm(request.POST, request=request)
-        if form.is_valid():
-            user = form.found_user
-            user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
-
-            if user_ban:
-                return error_banned(request, user, user_ban)
-
-            if user.activation == User.ACTIVATION_NONE:
-                return redirect_message(request, Message(_("%(username)s, your account is already active.") % {'username': user.username}), 'info')
-
-            if user.activation == User.ACTIVATION_ADMIN:
-                return redirect_message(request, Message(_("%(username)s, only board administrator can activate your account.") % {'username': user.username}), 'info')
-
-            user.email_user(
-                            request,
-                            'users/activation/resend',
-                            _("Account Activation"),
-                            )
-            return redirect_message(request, Message(_("%(username)s, e-mail containing new activation link has been sent to %(email)s.") % {'username': user.username, 'email': user.email}), 'success')
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = UserSendActivationMailForm(request=request)
-    return render_to_response('resend_activation.html',
-                              {
-                               'message': message,
-                               'form': form,
-                              },
-                              context_instance=RequestContext(request));
-
-
-@block_banned
-@block_authenticated
-@block_jammed
-def activate(request, username="", user="0", token=""):
-    user = int(user)
-
-    try:
-        user = User.objects.get(pk=user)
-        current_activation = user.activation
-
-        # Run checks
-        user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
-        if user_ban:
-            return error_banned(request, user, user_ban)
-
-        if user.activation == User.ACTIVATION_NONE:
-            return redirect_message(request, Message(_("%(username)s, your account is already active.") % {'username': user.username}), 'info')
-
-        if user.activation == User.ACTIVATION_ADMIN:
-            return redirect_message(request, Message(_("%(username)s, only board administrator can activate your account.") % {'username': user.username}), 'info')
-
-        if not token or not user.token or user.token != token:
-            return redirect_message(request, Message(_("%(username)s, your activation link is invalid. Try again or request new activation e-mail.") % {'username': user.username}), 'error')
-
-        # Activate and sign in our member
-        user.activation = User.ACTIVATION_NONE
-        sign_user_in(request, user)
-
-        # Update monitor
-        User.objects.resync_monitor()
-
-        if current_activation == User.ACTIVATION_CREDENTIALS:
-            return redirect_message(request, Message(_("%(username)s, your account has been successfully reactivated after change of sign-in credentials.") % {'username': user.username}), 'success')
-        else:
-            return redirect_message(request, Message(_("%(username)s, your account has been successfully activated. Welcome aboard!") % {'username': user.username}), 'success')
-    except User.DoesNotExist:
-        return error404(request)
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago.apps.errors import error404, error_banned
+from misago.auth import sign_user_in
+from misago.decorators import block_authenticated, block_banned, block_crawlers, block_jammed
+from misago import messages
+from misago.messages import Message
+from misago.models import Ban, User
+from misago.shortcuts import redirect_message, render_to_response
+from misago.apps.activation.forms import UserSendActivationMailForm
+
+@block_crawlers
+@block_banned
+@block_authenticated
+@block_jammed
+def form(request):
+    message = None
+    if request.method == 'POST':
+        form = UserSendActivationMailForm(request.POST, request=request)
+        if form.is_valid():
+            user = form.found_user
+            user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
+
+            if user_ban:
+                return error_banned(request, user, user_ban)
+
+            if user.activation == User.ACTIVATION_NONE:
+                return redirect_message(request, messages.INFO, _("%(username)s, your account is already active.") % {'username': user.username})
+
+            if user.activation == User.ACTIVATION_ADMIN:
+                return redirect_message(request, messages.INFO, _("%(username)s, only board administrator can activate your account.") % {'username': user.username})
+
+            user.email_user(
+                            request,
+                            'users/activation/resend',
+                            _("Account Activation"),
+                            )
+            return redirect_message(request, messages.SUCCESS, _("%(username)s, e-mail containing new activation link has been sent to %(email)s.") % {'username': user.username, 'email': user.email})
+        else:
+            message = Message(form.non_field_errors()[0], messages.ERROR)
+    else:
+        form = UserSendActivationMailForm(request=request)
+    return render_to_response('resend_activation.html',
+                              {
+                               'message': message,
+                               'form': form,
+                              },
+                              context_instance=RequestContext(request));
+
+
+@block_banned
+@block_authenticated
+@block_jammed
+def activate(request, username="", user="0", token=""):
+    user = int(user)
+
+    try:
+        user = User.objects.get(pk=user)
+        current_activation = user.activation
+
+        # Run checks
+        user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
+        if user_ban:
+            return error_banned(request, user, user_ban)
+
+        if user.activation == User.ACTIVATION_NONE:
+            return redirect_message(request, messages.INFO, _("%(username)s, your account is already active.") % {'username': user.username})
+
+        if user.activation == User.ACTIVATION_ADMIN:
+            return redirect_message(request, messages.INFO, Message(_("%(username)s, only board administrator can activate your account.") % {'username': user.username})
+
+        if not token or not user.token or user.token != token:
+            return redirect_message(request, messages.ERROR, Message(_("%(username)s, your activation link is invalid. Try again or request new activation e-mail.") % {'username': user.username})
+
+        # Activate and sign in our member
+        user.activation = User.ACTIVATION_NONE
+        sign_user_in(request, user)
+
+        # Update monitor
+        User.objects.resync_monitor()
+
+        if current_activation == User.ACTIVATION_CREDENTIALS:
+            return redirect_message(request, messages.SUCCESS, _("%(username)s, your account has been successfully reactivated after change of sign-in credentials.") % {'username': user.username})
+        else:
+            return redirect_message(request, messages.SUCCESS, _("%(username)s, your account has been successfully activated. Welcome aboard!") % {'username': user.username})
+    except User.DoesNotExist:
+        return error404(request)

+ 3 - 2
misago/apps/admin/forums/views.py

@@ -7,6 +7,7 @@ from django.shortcuts import redirect
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 import floppyforms as forms
 import floppyforms as forms
 from mptt.forms import TreeNodeChoiceField
 from mptt.forms import TreeNodeChoiceField
+from misago import messages
 from misago.admin import site
 from misago.admin import site
 from misago.apps.admin.widgets import *
 from misago.apps.admin.widgets import *
 from misago.models import Forum
 from misago.models import Forum
@@ -89,7 +90,7 @@ def resync_forums(request, forum=0, progress=0):
     progress = int(progress)
     progress = int(progress)
     forums = request.session.get('sync_forums')
     forums = request.session.get('sync_forums')
     if not forums:
     if not forums:
-        request.messages.set_flash(Message(_('No forums to resynchronize.')), 'info', 'forums')
+        messages.info(request, _('No forums to resynchronize.'), 'forums')
         return redirect(reverse('admin_forums'))
         return redirect(reverse('admin_forums'))
     try:
     try:
         if not forum:
         if not forum:
@@ -97,7 +98,7 @@ def resync_forums(request, forum=0, progress=0):
         forum = Forum.objects.get(id=forum)
         forum = Forum.objects.get(id=forum)
     except Forum.DoesNotExist:
     except Forum.DoesNotExist:
         del request.session['sync_forums']
         del request.session['sync_forums']
-        request.messages.set_flash(Message(_('Forum for resynchronization does not exist.')), 'error', 'forums')
+        messages.error(request, _('Forum for resynchronization does not exist.'), 'forums')
         return redirect(reverse('admin_forums'))
         return redirect(reverse('admin_forums'))
 
 
     # Sync 50 threads
     # Sync 50 threads

+ 203 - 202
misago/apps/admin/newsletters/views.py

@@ -1,202 +1,203 @@
-from django.core.urlresolvers import reverse as django_reverse
-from django.db.models import Q
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils.translation import ugettext as _
-from misago.admin import site
-from misago.apps.admin.widgets import *
-from misago.conf import settings
-from misago.models import Newsletter, User
-from misago.shortcuts import render_to_response
-from misago.apps.admin.newsletters.forms import NewsletterForm, SearchNewslettersForm
-
-def reverse(route, target=None):
-    if target:
-        if route == 'admin_newsletters_send':
-          return django_reverse(route, kwargs={'target': target.pk, 'token': target.token})
-        return django_reverse(route, kwargs={'target': target.pk})
-    return django_reverse(route)
-
-
-"""
-Views
-"""
-class List(ListWidget):
-    admin = site.get_action('newsletters')
-    id = 'list'
-    columns = (
-             ('newsletter', _("Newsletter")),
-             )
-    nothing_checked_message = _('You have to check at least one newsletter.')
-    actions = (
-             ('delete', _("Delete selected newsletters"), _("Are you sure you want to delete selected newsletters?")),
-             )
-    pagination = 20
-    search_form = SearchNewslettersForm
-
-    def sort_items(self, page_items, sorting_method):
-        return page_items.order_by('-id')
-
-    def set_filters(self, model, filters):
-        if 'rank' in filters:
-            model = model.filter(ranks__in=filters['rank']).distinct()
-        if 'type' in filters:
-            model = model.filter(ignore_subscriptions__in=filters['type'])
-        if 'name' in filters:
-            model = model.filter(name__icontains=filters['name'])
-        if 'content' in filters:
-            model = model.filter(Q(content_html__icontains=filters['content']) | Q(content_plain__icontains=filters['content']))
-        return model
-
-    def get_item_actions(self, item):
-        return (
-                self.action('envelope', _("Send Newsletter"), reverse('admin_newsletters_send', item)),
-                self.action('pencil', _("Edit Newsletter"), reverse('admin_newsletters_edit', item)),
-                self.action('remove', _("Delete Newsletter"), reverse('admin_newsletters_delete', item), post=True, prompt=_("Are you sure you want to delete this newsletter?")),
-                )
-
-    def action_delete(self, items, checked):
-        Newsletter.objects.filter(id__in=checked).delete()
-        return Message(_('Selected newsletters have been deleted successfully.'), 'success'), reverse('admin_newsletters')
-
-
-class New(FormWidget):
-    admin = site.get_action('newsletters')
-    id = 'new'
-    fallback = 'admin_newsletters'
-    form = NewsletterForm
-    submit_button = _("Save Newsletter")
-    tabbed = True
-
-    def get_new_link(self, model):
-        return reverse('admin_newsletters_new')
-
-    def get_edit_link(self, model):
-        return reverse('admin_newsletters_edit', model)
-
-    def submit_form(self, form, target):
-        new_newsletter = Newsletter(
-                      name=form.cleaned_data['name'],
-                      step_size=form.cleaned_data['step_size'],
-                      content_html=form.cleaned_data['content_html'],
-                      content_plain=form.cleaned_data['content_plain'],
-                      ignore_subscriptions=form.cleaned_data['ignore_subscriptions'],
-                     )
-        new_newsletter.generate_token()
-        new_newsletter.save(force_insert=True)
-
-        for rank in form.cleaned_data['ranks']:
-            new_newsletter.ranks.add(rank)
-
-        return new_newsletter, Message(_('New Newsletter has been created.'), 'success')
-
-
-class Edit(FormWidget):
-    admin = site.get_action('newsletters')
-    id = 'edit'
-    name = _("Edit Newsletter")
-    fallback = 'admin_newsletters'
-    form = NewsletterForm
-    target_name = 'name'
-    notfound_message = _('Requested Newsletter could not be found.')
-    submit_fallback = True
-    tabbed = True
-
-    def get_link(self, model):
-        return reverse('admin_newsletters_edit', model)
-
-    def get_edit_link(self, model):
-        return self.get_link(model)
-
-    def get_initial_data(self, model):
-        return {
-                'name': model.name,
-                'step_size': model.step_size,
-                'ignore_subscriptions': model.ignore_subscriptions,
-                'content_html': model.content_html,
-                'content_plain': model.content_plain,
-                'ranks': model.ranks.all(),
-                }
-
-    def submit_form(self, form, target):
-        target.name = form.cleaned_data['name']
-        target.step_size = form.cleaned_data['step_size']
-        target.ignore_subscriptions = form.cleaned_data['ignore_subscriptions']
-        target.content_html = form.cleaned_data['content_html']
-        target.content_plain = form.cleaned_data['content_plain']
-        target.generate_token()
-
-        target.ranks.clear()
-        for rank in form.cleaned_data['ranks']:
-            target.ranks.add(rank)
-
-        target.save(force_update=True)
-        return target, Message(_('Changes in newsletter "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
-
-
-class Delete(ButtonWidget):
-    admin = site.get_action('newsletters')
-    id = 'delete'
-    fallback = 'admin_newsletters'
-    notfound_message = _('Requested newsletter could not be found.')
-
-    def action(self, target):
-        target.delete()
-        return Message(_('Newsletter "%(name)s"" has been deleted.') % {'name': target.name}, 'success'), False
-
-
-def send(request, target, token):
-    try:
-        newsletter = Newsletter.objects.get(pk=target, token=token)
-
-        # Build recipients queryset
-        recipients = User.objects
-        if newsletter.ranks.all():
-            recipients = recipients.filter(rank__in=[x.pk for x in newsletter.ranks.all()])
-        if not newsletter.ignore_subscriptions:
-            recipients = recipients.filter(receive_newsletters=1)
-
-        recipients_total = recipients.count()
-        if recipients_total < 1:
-            request.messages.set_flash(Message(_('No recipients for newsletter "%(newsletter)s" could be found.') % {'newsletter': newsletter.name}), 'error', 'newsletters')
-            return redirect(reverse('admin_newsletters'))
-
-        for user in recipients.all()[newsletter.progress:(newsletter.progress + newsletter.step_size)]:
-            tokens = {
-              '{{ board_name }}': settings.board_name,
-              '{{ username }}': user.username,
-              '{{ user_link }}': django_reverse('user', kwargs={'username': user.username_slug, 'user': user.pk}),
-              '{{ board_link }}': settings.BOARD_ADDRESS,
-            }
-            subject = newsletter.parse_name(tokens)
-            user.email_user(request, 'users/newsletter', subject, {
-                                                                'newsletter': newsletter,
-                                                                'subject': subject,
-                                                                'content_html': newsletter.parse_html(tokens),
-                                                                'content_plain': newsletter.parse_plain(tokens),
-                                                                })
-            newsletter.progress += 1
-        newsletter.generate_token()
-        newsletter.save(force_update=True)
-
-        if newsletter.progress >= recipients_total:
-            newsletter.progress = 0
-            newsletter.save(force_update=True)
-            request.messages.set_flash(Message(_('Newsletter "%(newsletter)s" has been sent.') % {'newsletter': newsletter.name}), 'success', 'newsletters')
-            return redirect(reverse('admin_newsletters'))
-
-        # Render Progress
-        response = render_to_response('processing.html',
-                                      {
-                                      'task_name': _('Sending Newsletter'),
-                                      'target_name': newsletter.name,
-                                      'message': _('Sent to %(progress)s from %(total)s users') % {'progress': newsletter.progress, 'total': recipients_total},
-                                      'progress': newsletter.progress * 100 / recipients_total,
-                                      'cancel_link': reverse('admin_newsletters'),
-                                      },
-                                      context_instance=RequestContext(request));
-        response['refresh'] = '2;url=%s' % reverse('admin_newsletters_send', newsletter)
-        return response
-    except Newsletter.DoesNotExist:
-        request.messages.set_flash(Message(_('Requested Newsletter could not be found.')), 'error', 'newsletters')
-        return redirect(reverse('admin_newsletters'))
+from django.core.urlresolvers import reverse as django_reverse
+from django.db.models import Q
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.admin import site
+from misago.apps.admin.widgets import *
+from misago.conf import settings
+from misago.models import Newsletter, User
+from misago.shortcuts import render_to_response
+from misago.apps.admin.newsletters.forms import NewsletterForm, SearchNewslettersForm
+
+def reverse(route, target=None):
+    if target:
+        if route == 'admin_newsletters_send':
+          return django_reverse(route, kwargs={'target': target.pk, 'token': target.token})
+        return django_reverse(route, kwargs={'target': target.pk})
+    return django_reverse(route)
+
+
+"""
+Views
+"""
+class List(ListWidget):
+    admin = site.get_action('newsletters')
+    id = 'list'
+    columns = (
+             ('newsletter', _("Newsletter")),
+             )
+    nothing_checked_message = _('You have to check at least one newsletter.')
+    actions = (
+             ('delete', _("Delete selected newsletters"), _("Are you sure you want to delete selected newsletters?")),
+             )
+    pagination = 20
+    search_form = SearchNewslettersForm
+
+    def sort_items(self, page_items, sorting_method):
+        return page_items.order_by('-id')
+
+    def set_filters(self, model, filters):
+        if 'rank' in filters:
+            model = model.filter(ranks__in=filters['rank']).distinct()
+        if 'type' in filters:
+            model = model.filter(ignore_subscriptions__in=filters['type'])
+        if 'name' in filters:
+            model = model.filter(name__icontains=filters['name'])
+        if 'content' in filters:
+            model = model.filter(Q(content_html__icontains=filters['content']) | Q(content_plain__icontains=filters['content']))
+        return model
+
+    def get_item_actions(self, item):
+        return (
+                self.action('envelope', _("Send Newsletter"), reverse('admin_newsletters_send', item)),
+                self.action('pencil', _("Edit Newsletter"), reverse('admin_newsletters_edit', item)),
+                self.action('remove', _("Delete Newsletter"), reverse('admin_newsletters_delete', item), post=True, prompt=_("Are you sure you want to delete this newsletter?")),
+                )
+
+    def action_delete(self, items, checked):
+        Newsletter.objects.filter(id__in=checked).delete()
+        return Message(_('Selected newsletters have been deleted successfully.'), 'success'), reverse('admin_newsletters')
+
+
+class New(FormWidget):
+    admin = site.get_action('newsletters')
+    id = 'new'
+    fallback = 'admin_newsletters'
+    form = NewsletterForm
+    submit_button = _("Save Newsletter")
+    tabbed = True
+
+    def get_new_link(self, model):
+        return reverse('admin_newsletters_new')
+
+    def get_edit_link(self, model):
+        return reverse('admin_newsletters_edit', model)
+
+    def submit_form(self, form, target):
+        new_newsletter = Newsletter(
+                      name=form.cleaned_data['name'],
+                      step_size=form.cleaned_data['step_size'],
+                      content_html=form.cleaned_data['content_html'],
+                      content_plain=form.cleaned_data['content_plain'],
+                      ignore_subscriptions=form.cleaned_data['ignore_subscriptions'],
+                     )
+        new_newsletter.generate_token()
+        new_newsletter.save(force_insert=True)
+
+        for rank in form.cleaned_data['ranks']:
+            new_newsletter.ranks.add(rank)
+
+        return new_newsletter, Message(_('New Newsletter has been created.'), 'success')
+
+
+class Edit(FormWidget):
+    admin = site.get_action('newsletters')
+    id = 'edit'
+    name = _("Edit Newsletter")
+    fallback = 'admin_newsletters'
+    form = NewsletterForm
+    target_name = 'name'
+    notfound_message = _('Requested Newsletter could not be found.')
+    submit_fallback = True
+    tabbed = True
+
+    def get_link(self, model):
+        return reverse('admin_newsletters_edit', model)
+
+    def get_edit_link(self, model):
+        return self.get_link(model)
+
+    def get_initial_data(self, model):
+        return {
+                'name': model.name,
+                'step_size': model.step_size,
+                'ignore_subscriptions': model.ignore_subscriptions,
+                'content_html': model.content_html,
+                'content_plain': model.content_plain,
+                'ranks': model.ranks.all(),
+                }
+
+    def submit_form(self, form, target):
+        target.name = form.cleaned_data['name']
+        target.step_size = form.cleaned_data['step_size']
+        target.ignore_subscriptions = form.cleaned_data['ignore_subscriptions']
+        target.content_html = form.cleaned_data['content_html']
+        target.content_plain = form.cleaned_data['content_plain']
+        target.generate_token()
+
+        target.ranks.clear()
+        for rank in form.cleaned_data['ranks']:
+            target.ranks.add(rank)
+
+        target.save(force_update=True)
+        return target, Message(_('Changes in newsletter "%(name)s" have been saved.') % {'name': self.original_name}, 'success')
+
+
+class Delete(ButtonWidget):
+    admin = site.get_action('newsletters')
+    id = 'delete'
+    fallback = 'admin_newsletters'
+    notfound_message = _('Requested newsletter could not be found.')
+
+    def action(self, target):
+        target.delete()
+        return Message(_('Newsletter "%(name)s"" has been deleted.') % {'name': target.name}, 'success'), False
+
+
+def send(request, target, token):
+    try:
+        newsletter = Newsletter.objects.get(pk=target, token=token)
+
+        # Build recipients queryset
+        recipients = User.objects
+        if newsletter.ranks.all():
+            recipients = recipients.filter(rank__in=[x.pk for x in newsletter.ranks.all()])
+        if not newsletter.ignore_subscriptions:
+            recipients = recipients.filter(receive_newsletters=1)
+
+        recipients_total = recipients.count()
+        if recipients_total < 1:
+            messages.error(request, _('No recipients for newsletter "%(newsletter)s" could be found.') % {'newsletter': newsletter.name}, 'newsletters')
+            return redirect(reverse('admin_newsletters'))
+
+        for user in recipients.all()[newsletter.progress:(newsletter.progress + newsletter.step_size)]:
+            tokens = {
+              '{{ board_name }}': settings.board_name,
+              '{{ username }}': user.username,
+              '{{ user_link }}': django_reverse('user', kwargs={'username': user.username_slug, 'user': user.pk}),
+              '{{ board_link }}': settings.BOARD_ADDRESS,
+            }
+            subject = newsletter.parse_name(tokens)
+            user.email_user(request, 'users/newsletter', subject, {
+                                                                'newsletter': newsletter,
+                                                                'subject': subject,
+                                                                'content_html': newsletter.parse_html(tokens),
+                                                                'content_plain': newsletter.parse_plain(tokens),
+                                                                })
+            newsletter.progress += 1
+        newsletter.generate_token()
+        newsletter.save(force_update=True)
+
+        if newsletter.progress >= recipients_total:
+            newsletter.progress = 0
+            newsletter.save(force_update=True)
+            messages.success(request, _('Newsletter "%(newsletter)s" has been sent.') % {'newsletter': newsletter.name}, 'newsletters')
+            return redirect(reverse('admin_newsletters'))
+
+        # Render Progress
+        response = render_to_response('processing.html',
+                                      {
+                                      'task_name': _('Sending Newsletter'),
+                                      'target_name': newsletter.name,
+                                      'message': _('Sent to %(progress)s from %(total)s users') % {'progress': newsletter.progress, 'total': recipients_total},
+                                      'progress': newsletter.progress * 100 / recipients_total,
+                                      'cancel_link': reverse('admin_newsletters'),
+                                      },
+                                      context_instance=RequestContext(request));
+        response['refresh'] = '2;url=%s' % reverse('admin_newsletters_send', newsletter)
+        return response
+    except Newsletter.DoesNotExist:
+        messages.error(request, _('Requested Newsletter could not be found.'), 'newsletters')
+        return redirect(reverse('admin_newsletters'))

+ 13 - 11
misago/apps/admin/pruneusers/views.py

@@ -1,9 +1,11 @@
 from django.core.urlresolvers import reverse as django_reverse
 from django.core.urlresolvers import reverse as django_reverse
 from django.utils.translation import ungettext, ugettext as _
 from django.utils.translation import ungettext, ugettext as _
 import floppyforms as forms
 import floppyforms as forms
+from misago import messages
 from misago.admin import site
 from misago.admin import site
 from misago.apps.admin.widgets import *
 from misago.apps.admin.widgets import *
 from misago.forms import Form
 from misago.forms import Form
+from misago.messages import Message
 from misago.models import PruningPolicy, User
 from misago.models import PruningPolicy, User
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response
 from misago.apps.admin.pruneusers.forms import PolicyForm
 from misago.apps.admin.pruneusers.forms import PolicyForm
@@ -74,7 +76,7 @@ class New(FormWidget):
 
 
     def __call__(self, request, *args, **kwargs):
     def __call__(self, request, *args, **kwargs):
         if not request.user.is_god():
         if not request.user.is_god():
-            request.messages.set_flash(Message(_('Only system administrators can set new pruning policies.')), 'error', self.admin.id)
+            messages.error(request, _('Only system administrators can set new pruning policies.'), self.admin.id)
             return redirect(reverse('admin_prune_users'))
             return redirect(reverse('admin_prune_users'))
 
 
         return super(New, self).__call__(request, *args, **kwargs)
         return super(New, self).__call__(request, *args, **kwargs)
@@ -118,7 +120,7 @@ class Edit(FormWidget):
 
 
     def __call__(self, request, *args, **kwargs):
     def __call__(self, request, *args, **kwargs):
         if not request.user.is_god():
         if not request.user.is_god():
-            request.messages.set_flash(Message(_('Only system administrators can edit pruning policies.')), 'error', self.admin.id)
+            messages.error(request, _('Only system administrators can edit pruning policies.'), self.admin.id)
             return redirect(reverse('admin_prune_users'))
             return redirect(reverse('admin_prune_users'))
 
 
         return super(Edit, self).__call__(request, *args, **kwargs)
         return super(Edit, self).__call__(request, *args, **kwargs)
@@ -170,7 +172,7 @@ class Apply(FormWidget):
         total_users = total_users.count()
         total_users = total_users.count()
 
 
         if not total_users:
         if not total_users:
-            request.messages.set_flash(Message(_('Policy "%(name)s" does not apply to any users.') % {'name': model.name}), 'error', self.admin.id)
+            messages.error(request, _('Policy "%(name)s" does not apply to any users.') % {'name': model.name}, self.admin.id)
             return redirect(reverse('admin_prune_users'))
             return redirect(reverse('admin_prune_users'))
 
 
         message = None
         message = None
@@ -179,19 +181,19 @@ class Apply(FormWidget):
             if request.csrf.request_secure(request):
             if request.csrf.request_secure(request):
                 for user in users.iterator():
                 for user in users.iterator():
                     if user.is_protected():
                     if user.is_protected():
-                        request.messages.set_flash(Message(_('User "%(name)s" is protected and was not deleted.') % {'name': user.username}), 'info', self.admin.id)
+                        messages.info(request, _('User "%(name)s" is protected and was not deleted.') % {'name': user.username}, self.admin.id)
                     else:
                     else:
                         user.delete()
                         user.delete()
                         deleted += 1
                         deleted += 1
                 if deleted:
                 if deleted:
-                    request.messages.set_flash(Message(ungettext(
-                                                                 'One user has been deleted.',
-                                                                 '%(deleted)d users have been deleted.',
-                                                                 deleted
-                                                                 ) % {'deleted': deleted}), 'success', self.admin.id)
+                    messages.success(request, ungettext(
+                                                        'One user has been deleted.',
+                                                        '%(deleted)d users have been deleted.',
+                                                        deleted
+                                                        ) % {'deleted': deleted}, self.admin.id)
                     User.objects.resync_monitor()
                     User.objects.resync_monitor()
                 else:
                 else:
-                    request.messages.set_flash(Message(_("No users have been deleted.")), 'info', self.admin.id)
+                    messages.info(request, _("No users have been deleted."), self.admin.id)
                 return redirect(reverse('admin_prune_users'))
                 return redirect(reverse('admin_prune_users'))
             else:
             else:
                 message = Message(_("Request authorization is invalid. Please resubmit your form."), 'error')
                 message = Message(_("Request authorization is invalid. Please resubmit your form."), 'error')
@@ -203,7 +205,7 @@ class Apply(FormWidget):
                                   'request': request,
                                   'request': request,
                                   'url': self.get_link(model),
                                   'url': self.get_link(model),
                                   'fallback': self.get_fallback_link(),
                                   'fallback': self.get_fallback_link(),
-                                  'messages': request.messages.get_messages(self.admin.id),
+                                  'messages': messages.get_messages(request, self.admin.id),
                                   'message': message,
                                   'message': message,
                                   'tabbed': self.tabbed,
                                   'tabbed': self.tabbed,
                                   'total_users': total_users,
                                   'total_users': total_users,

+ 39 - 38
misago/apps/admin/roles/views.py

@@ -2,7 +2,8 @@ import copy
 from django.core.urlresolvers import reverse as django_reverse
 from django.core.urlresolvers import reverse as django_reverse
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
-from misago.acl.builder import build_form 
+from misago import messages
+from misago.acl.builder import build_form
 from misago.admin import site
 from misago.admin import site
 from misago.apps.admin.widgets import *
 from misago.apps.admin.widgets import *
 from misago.forms import Form, YesNoSwitch
 from misago.forms import Form, YesNoSwitch
@@ -30,10 +31,10 @@ class List(ListWidget):
     actions=(
     actions=(
              ('delete', _("Delete selected roles"), _("Are you sure you want to delete selected roles?")),
              ('delete', _("Delete selected roles"), _("Are you sure you want to delete selected roles?")),
              )
              )
-    
+
     def sort_items(self, page_items, sorting_method):
     def sort_items(self, page_items, sorting_method):
         return page_items.order_by('name')
         return page_items.order_by('name')
-    
+
     def get_item_actions(self, item):
     def get_item_actions(self, item):
         return (
         return (
                 self.action('list', _("Forums Permissions"), reverse('admin_roles_masks', item)),
                 self.action('list', _("Forums Permissions"), reverse('admin_roles_masks', item)),
@@ -51,7 +52,7 @@ class List(ListWidget):
                     return Message(_('You cannot delete protected roles.'), 'error'), reverse('admin_roles')
                     return Message(_('You cannot delete protected roles.'), 'error'), reverse('admin_roles')
                 if item.user_set.count() > 0:
                 if item.user_set.count() > 0:
                     return Message(_('You cannot delete roles that are assigned to users.'), 'error'), reverse('admin_roles')
                     return Message(_('You cannot delete roles that are assigned to users.'), 'error'), reverse('admin_roles')
-        
+
         Role.objects.filter(id__in=checked).delete()
         Role.objects.filter(id__in=checked).delete()
         return Message(_('Selected roles have been deleted successfully.'), 'success'), reverse('admin_roles')
         return Message(_('Selected roles have been deleted successfully.'), 'success'), reverse('admin_roles')
 
 
@@ -59,22 +60,22 @@ class List(ListWidget):
 class New(FormWidget):
 class New(FormWidget):
     admin = site.get_action('roles')
     admin = site.get_action('roles')
     id = 'new'
     id = 'new'
-    fallback = 'admin_roles' 
+    fallback = 'admin_roles'
     form = RoleForm
     form = RoleForm
     submit_button = _("Save Role")
     submit_button = _("Save Role")
-        
+
     def get_new_link(self, model):
     def get_new_link(self, model):
         return reverse('admin_roles_new')
         return reverse('admin_roles_new')
-    
+
     def get_edit_link(self, model):
     def get_edit_link(self, model):
         return reverse('admin_roles_edit', model)
         return reverse('admin_roles_edit', model)
-    
+
     def submit_form(self, form, target):
     def submit_form(self, form, target):
         new_role = Role(name=form.cleaned_data['name'])
         new_role = Role(name=form.cleaned_data['name'])
         new_role.save(force_insert=True)
         new_role.save(force_insert=True)
-        return new_role, Message(_('New Role has been created.'), 'success')    
-    
-   
+        return new_role, Message(_('New Role has been created.'), 'success')
+
+
 class Edit(FormWidget):
 class Edit(FormWidget):
     admin = site.get_action('roles')
     admin = site.get_action('roles')
     id = 'edit'
     id = 'edit'
@@ -85,25 +86,25 @@ class Edit(FormWidget):
     translate_target_name = True
     translate_target_name = True
     notfound_message = _('Requested Role could not be found.')
     notfound_message = _('Requested Role could not be found.')
     submit_fallback = True
     submit_fallback = True
-    
+
     def get_link(self, model):
     def get_link(self, model):
         return reverse('admin_roles_edit', model)
         return reverse('admin_roles_edit', model)
-    
+
     def get_edit_link(self, model):
     def get_edit_link(self, model):
         return self.get_link(model)
         return self.get_link(model)
-    
+
     def get_initial_data(self, model):
     def get_initial_data(self, model):
         if self.request.user.is_god():
         if self.request.user.is_god():
             return {'name': model.name, 'protected': model.protected}
             return {'name': model.name, 'protected': model.protected}
         return {'name': model.name}
         return {'name': model.name}
-    
+
     def get_and_validate_target(self, target):
     def get_and_validate_target(self, target):
         result = super(Edit, self).get_and_validate_target(target)
         result = super(Edit, self).get_and_validate_target(target)
         if result and result.protected and not self.request.user.is_god():
         if result and result.protected and not self.request.user.is_god():
-            self.request.messages.set_flash(Message(_('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(result.name)}), 'error', self.admin.id)
+            messages.error(self.request, _('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(result.name)}, self.admin.id)
             return None
             return None
         return result
         return result
-    
+
     def submit_form(self, form, target):
     def submit_form(self, form, target):
         target.name = form.cleaned_data['name']
         target.name = form.cleaned_data['name']
         if self.request.user.is_god():
         if self.request.user.is_god():
@@ -122,20 +123,20 @@ class Forums(ListWidget):
     table_form_button = _('Change Permissions')
     table_form_button = _('Change Permissions')
     empty_message = _('No forums are currently defined.')
     empty_message = _('No forums are currently defined.')
     template = 'forums'
     template = 'forums'
-    
+
     def get_link(self):
     def get_link(self):
-        return reverse('admin_roles_masks', self.role) 
-    
+        return reverse('admin_roles_masks', self.role)
+
     def get_items(self):
     def get_items(self):
         return Forum.objects.get(special='root').get_descendants()
         return Forum.objects.get(special='root').get_descendants()
-    
+
     def sort_items(self, page_items, sorting_method):
     def sort_items(self, page_items, sorting_method):
         return page_items.order_by('lft').all()
         return page_items.order_by('lft').all()
 
 
     def add_template_variables(self, variables):
     def add_template_variables(self, variables):
         variables['target'] = _(self.role.name)
         variables['target'] = _(self.role.name)
         return variables
         return variables
-    
+
     def get_table_form(self, page_items):
     def get_table_form(self, page_items):
         perms = {}
         perms = {}
         try:
         try:
@@ -144,7 +145,7 @@ class Forums(ListWidget):
                perms[str(fid)] = str(forums[fid])
                perms[str(fid)] = str(forums[fid])
         except KeyError:
         except KeyError:
             pass
             pass
-        
+
         perms_form = {}
         perms_form = {}
         roles_select = [("0", _("No Access"))]
         roles_select = [("0", _("No Access"))]
         for role in self.roles:
         for role in self.roles:
@@ -152,10 +153,10 @@ class Forums(ListWidget):
 
 
         for item in page_items:
         for item in page_items:
             perms_form['forum_' + str(item.pk)] = forms.ChoiceField(choices=roles_select,initial=(perms[str(item.pk)] if str(item.pk) in perms else "0"))
             perms_form['forum_' + str(item.pk)] = forms.ChoiceField(choices=roles_select,initial=(perms[str(item.pk)] if str(item.pk) in perms else "0"))
-        
+
         # Turn dict into object
         # Turn dict into object
         return type('ChangeForumRolesForm', (Form,), perms_form)
         return type('ChangeForumRolesForm', (Form,), perms_form)
-    
+
     def table_action(self, page_items, cleaned_data):
     def table_action(self, page_items, cleaned_data):
         perms = {}
         perms = {}
         for item in page_items:
         for item in page_items:
@@ -166,20 +167,20 @@ class Forums(ListWidget):
         self.role.permissions = role_perms
         self.role.permissions = role_perms
         self.role.save(force_update=True)
         self.role.save(force_update=True)
         return Message(_('Forum permissions have been saved.'), 'success'), self.get_link()
         return Message(_('Forum permissions have been saved.'), 'success'), self.get_link()
-        
+
     def __call__(self, request, slug, target):
     def __call__(self, request, slug, target):
         self.request = request
         self.request = request
         try:
         try:
             self.role = Role.objects.get(id=target)
             self.role = Role.objects.get(id=target)
             if self.role and self.role.protected and not request.user.is_god():
             if self.role and self.role.protected and not request.user.is_god():
-                request.messages.set_flash(Message(_('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(self.role.name)}), 'error', self.admin.id)
+                messages.error(request, _('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(self.role.name)}, self.admin.id)
                 return redirect(reverse('admin_roles'))
                 return redirect(reverse('admin_roles'))
         except Role.DoesNotExist:
         except Role.DoesNotExist:
-            request.messages.set_flash(Message(_('Requested Role could not be found.')), 'error', self.admin.id)
+            messages.error(request, _('Requested Role could not be found.'), self.admin.id)
             return redirect(reverse('admin_roles'))
             return redirect(reverse('admin_roles'))
         self.roles = ForumRole.objects.order_by('name').all()
         self.roles = ForumRole.objects.order_by('name').all()
         if not self.roles:
         if not self.roles:
-            request.messages.set_flash(Message(_('No forum roles are currently set.')), 'error', self.admin.id)
+            messages.error(request, _('No forum roles are currently set.'), self.admin.id)
             return redirect(reverse('admin_roles'))
             return redirect(reverse('admin_roles'))
         return super(Forums, self).__call__(request)
         return super(Forums, self).__call__(request)
 
 
@@ -194,17 +195,17 @@ class ACL(FormWidget):
     notfound_message = _('Requested Role could not be found.')
     notfound_message = _('Requested Role could not be found.')
     submit_fallback = True
     submit_fallback = True
     template = 'acl_form'
     template = 'acl_form'
-    
+
     def get_form(self, target):
     def get_form(self, target):
         self.form = build_form(self.request, target)
         self.form = build_form(self.request, target)
         return self.form
         return self.form
-    
+
     def get_link(self, model):
     def get_link(self, model):
         return reverse('admin_roles_acl', model)
         return reverse('admin_roles_acl', model)
-    
+
     def get_edit_link(self, model):
     def get_edit_link(self, model):
         return self.get_link(model)
         return self.get_link(model)
-    
+
     def get_initial_data(self, model):
     def get_initial_data(self, model):
         raw_acl = model.permissions
         raw_acl = model.permissions
         initial = {}
         initial = {}
@@ -212,14 +213,14 @@ class ACL(FormWidget):
             if field in raw_acl:
             if field in raw_acl:
                 initial[field] = raw_acl[field]
                 initial[field] = raw_acl[field]
         return initial
         return initial
-    
+
     def get_and_validate_target(self, target):
     def get_and_validate_target(self, target):
         result = super(ACL, self).get_and_validate_target(target)
         result = super(ACL, self).get_and_validate_target(target)
         if result and result.protected and not self.request.user.is_god():
         if result and result.protected and not self.request.user.is_god():
-            self.request.messages.set_flash(Message(_('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(result.name)}), 'error', self.admin.id)
+            messages.error(self.request, _('Role "%(name)s" is protected, you cannot edit it.') % {'name': _(result.name)}, self.admin.id)
             return None
             return None
         return result
         return result
-    
+
     def submit_form(self, form, target):
     def submit_form(self, form, target):
         raw_acl = target.permissions
         raw_acl = target.permissions
         for perm in form.cleaned_data:
         for perm in form.cleaned_data:
@@ -228,7 +229,7 @@ class ACL(FormWidget):
         target.save(force_update=True)
         target.save(force_update=True)
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
             monitor.increase('acl_version')
             monitor.increase('acl_version')
-        
+
         return target, Message(_('Role "%(name)s" permissions have been changed.') % {'name': self.original_name}, 'success')
         return target, Message(_('Role "%(name)s" permissions have been changed.') % {'name': self.original_name}, 'success')
 
 
 
 
@@ -237,7 +238,7 @@ class Delete(ButtonWidget):
     id = 'delete'
     id = 'delete'
     fallback = 'admin_roles'
     fallback = 'admin_roles'
     notfound_message = _('Requested Role could not be found.')
     notfound_message = _('Requested Role could not be found.')
-    
+
     def action(self, target):
     def action(self, target):
         if target.special:
         if target.special:
             return Message(_('You cannot delete system roles.'), 'error'), reverse('admin_roles')
             return Message(_('You cannot delete system roles.'), 'error'), reverse('admin_roles')

+ 3 - 2
misago/apps/admin/settings/views.py

@@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
 from django.utils.translation import ungettext, ugettext as _
 from django.utils.translation import ungettext, ugettext as _
+from misago import messages
 from misago.conf import settings as misago_settings
 from misago.conf import settings as misago_settings
 from misago.forms import Form, FormIterator
 from misago.forms import Form, FormIterator
 from misago.messages import Message
 from misago.messages import Message
@@ -50,7 +51,7 @@ def settings(request, group_id=None, group_slug=None):
             for setting in form.cleaned_data.keys():
             for setting in form.cleaned_data.keys():
                 misago_settings[setting] = form.cleaned_data[setting]
                 misago_settings[setting] = form.cleaned_data[setting]
             cache.delete('settings')
             cache.delete('settings')
-            request.messages.set_flash(Message(_('Configuration has been changed.')), 'success', 'admin_settings')
+            messages.success(request, _('Configuration has been changed.'), 'admin_settings')
             return redirect(reverse('admin_settings', kwargs={
             return redirect(reverse('admin_settings', kwargs={
                                                        'group_id': active_group.pk,
                                                        'group_id': active_group.pk,
                                                        'group_slug': active_group.key,
                                                        'group_slug': active_group.key,
@@ -60,7 +61,7 @@ def settings(request, group_id=None, group_slug=None):
     else:
     else:
         form = SettingsGroupForm(request=request)
         form = SettingsGroupForm(request=request)
 
 
-    # Display settings group form      
+    # Display settings group form
     return render_to_response('settings/settings.html',
     return render_to_response('settings/settings.html',
                               {
                               {
                               'message': message,
                               'message': message,

+ 3 - 2
misago/apps/admin/stats/views.py

@@ -6,6 +6,7 @@ from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from misago import messages
 from misago.messages import Message
 from misago.messages import Message
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response
 from misago.apps.admin.stats.forms import GenerateStatisticsForm
 from misago.apps.admin.stats.forms import GenerateStatisticsForm
@@ -46,11 +47,11 @@ def form(request):
                 date_start = date_temp
                 date_start = date_temp
             # Assert that dates are correct
             # Assert that dates are correct
             if date_end == date_start:
             if date_end == date_start:
-                message = Message(_('Start and end date are same'), type='error')
+                message = Message(_('Start and end date are same'), level='error')
             elif check_dates(date_start, date_end, form.cleaned_data['stats_precision']):
             elif check_dates(date_start, date_end, form.cleaned_data['stats_precision']):
                 message = check_dates(date_start, date_end, form.cleaned_data['stats_precision'])
                 message = check_dates(date_start, date_end, form.cleaned_data['stats_precision'])
             else:
             else:
-                request.messages.set_flash(Message(_('Statistical report has been created.')), 'success', 'admin_stats')
+                messages.success(request, _('Statistical report has been created.'), 'admin_stats')
                 return redirect(reverse('admin_stats_graph', kwargs={
                 return redirect(reverse('admin_stats_graph', kwargs={
                                                        'model': form.cleaned_data['provider_model'],
                                                        'model': form.cleaned_data['provider_model'],
                                                        'date_start': date_start.strftime('%Y-%m-%d'),
                                                        'date_start': date_start.strftime('%Y-%m-%d'),

+ 9 - 8
misago/apps/admin/widgets.py

@@ -7,6 +7,7 @@ from django.template import RequestContext
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 import floppyforms as forms
 import floppyforms as forms
 from jinja2 import TemplateNotFound
 from jinja2 import TemplateNotFound
+from misago import messages
 from misago.forms import Form
 from misago.forms import Form
 from misago.messages import Message
 from misago.messages import Message
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response
@@ -76,9 +77,9 @@ class BaseWidget(object):
             self.get_target(model)
             self.get_target(model)
             return model
             return model
         except self.admin.model.DoesNotExist:
         except self.admin.model.DoesNotExist:
-            self.request.messages.set_flash(Message(self.notfound_message), 'error', self.admin.id)
+            messages.error(self.request, self.notfound_message, self.admin.id)
         except ValueError as e:
         except ValueError as e:
-            self.request.messages.set_flash(Message(e.args[0]), 'error', self.admin.id)
+            messages.error(self.request, e.args[0], self.admin.id)
         return None
         return None
 
 
 
 
@@ -297,7 +298,7 @@ class ListWidget(BaseWidget):
                 # Kill search
                 # Kill search
                 if request.POST.get('origin') == 'clear' and self.is_filtering and request.csrf.request_secure(request):
                 if request.POST.get('origin') == 'clear' and self.is_filtering and request.csrf.request_secure(request):
                     request.session[self.get_token('filter')] = None
                     request.session[self.get_token('filter')] = None
-                    request.messages.set_flash(Message(_("Search criteria have been cleared.")), 'info', self.admin.id)
+                    messages.info(request, _("Search criteria have been cleared."), self.admin.id)
                     return redirect(self.get_link())
                     return redirect(self.get_link())
             else:
             else:
                 if self.is_filtering:
                 if self.is_filtering:
@@ -314,7 +315,7 @@ class ListWidget(BaseWidget):
                 if table_form.is_valid():
                 if table_form.is_valid():
                     message, redirect_link = self.table_action(items, table_form.cleaned_data)
                     message, redirect_link = self.table_action(items, table_form.cleaned_data)
                     if redirect_link:
                     if redirect_link:
-                        request.messages.set_flash(message, message.type, self.admin.id)
+                        messages.add_message(request, message.type, message, self.admin.id)
                         return redirect(redirect_link)
                         return redirect(redirect_link)
                 else:
                 else:
                     message = Message(table_form.non_field_errors()[0], 'error')
                     message = Message(table_form.non_field_errors()[0], 'error')
@@ -332,7 +333,7 @@ class ListWidget(BaseWidget):
                         form_action = getattr(self, 'action_' + list_form.cleaned_data['list_action'])
                         form_action = getattr(self, 'action_' + list_form.cleaned_data['list_action'])
                         message, redirect_link = form_action(items, [int(x) for x in list_form.cleaned_data['list_items']])
                         message, redirect_link = form_action(items, [int(x) for x in list_form.cleaned_data['list_items']])
                         if redirect_link:
                         if redirect_link:
-                            request.messages.set_flash(message, message.type, self.admin.id)
+                            messages.add_message(request, message, message.type, self.admin.id)
                             return redirect(redirect_link)
                             return redirect(redirect_link)
                     except AttributeError:
                     except AttributeError:
                         message = Message(_("Requested action is incorrect."))
                         message = Message(_("Requested action is incorrect."))
@@ -436,7 +437,7 @@ class FormWidget(BaseWidget):
                 try:
                 try:
                     model, message = self.submit_form(form, model)
                     model, message = self.submit_form(form, model)
                     if message.type != 'error':
                     if message.type != 'error':
-                        request.messages.set_flash(message, message.type, self.admin.id)
+                        messages.add_message(request, message, message.type, self.admin.id)
                         # Redirect back to right page
                         # Redirect back to right page
                         try:
                         try:
                             if 'save_new' in request.POST and self.get_new_link:
                             if 'save_new' in request.POST and self.get_new_link:
@@ -501,12 +502,12 @@ class ButtonWidget(BaseWidget):
 
 
         # Crash if this is invalid request
         # Crash if this is invalid request
         if not request.csrf.request_secure(request):
         if not request.csrf.request_secure(request):
-            request.messages.set_flash(Message(_("Action authorization is invalid.")), 'error', self.admin.id)
+            messages.error(request, _("Action authorization is invalid."), self.admin.id)
             return redirect(self.get_fallback_link())
             return redirect(self.get_fallback_link())
 
 
         # Do something
         # Do something
         message, url = self.action(model)
         message, url = self.action(model)
-        request.messages.set_flash(message, message.type, self.admin.id)
+        messages.add_message(request, message, message.type, self.admin.id)
         if url:
         if url:
             return redirect(url)
             return redirect(url)
         return redirect(self.get_fallback_link())
         return redirect(self.get_fallback_link())

+ 116 - 115
misago/apps/privatethreads/jumps.py

@@ -1,115 +1,116 @@
-from django.utils.translation import ugettext as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.threadtype.jumps import *
-from misago.models import User
-from misago.utils.strings import slugify
-from misago.apps.privatethreads.mixins import TypeMixin
-
-class LastReplyView(LastReplyBaseView, TypeMixin):
-    pass
-
-
-class FindReplyView(FindReplyBaseView, TypeMixin):
-    pass
-
-
-class NewReplyView(NewReplyBaseView, TypeMixin):
-    pass
-
-
-class ShowHiddenRepliesView(ShowHiddenRepliesBaseView, TypeMixin):
-    pass
-
-
-class WatchThreadView(WatchThreadBaseView, TypeMixin):
-    pass
-
-
-class WatchEmailThreadView(WatchEmailThreadBaseView, TypeMixin):
-    pass
-
-
-class UnwatchThreadView(UnwatchThreadBaseView, TypeMixin):
-    pass
-
-
-class UnwatchEmailThreadView(UnwatchEmailThreadBaseView, TypeMixin):
-    pass
-
-
-class FirstReportedView(FirstReportedBaseView, TypeMixin):
-    pass
-
-
-class ReportPostView(ReportPostBaseView, TypeMixin):
-    pass
-
-
-class ShowPostReportView(ShowPostReportBaseView, TypeMixin):
-    pass
-
-
-class InviteUserView(JumpView, TypeMixin):
-    def make_jump(self):
-        username = slugify(self.request.POST.get('username', '').strip())
-        if not username:
-            self.request.messages.set_flash(Message(_('You have to enter name of user you want to invite to thread.')), 'error', 'threads')
-            return self.retreat_redirect()
-        try:
-            user = User.objects.get(username_slug=username)
-            acl = user.acl(self.request)
-            if user in self.thread.participants.all():
-                if user.pk == self.request.user.pk:
-                    self.request.messages.set_flash(Message(_('You cannot add yourself to this thread.')), 'error', 'threads')
-                else:
-                    self.request.messages.set_flash(Message(_('%(user)s is already participating in this thread.') % {'user': user.username}), 'info', 'threads')
-            elif not acl.private_threads.can_participate():
-                    self.request.messages.set_flash(Message(_('%(user)s cannot participate in private threads.') % {'user': user.username}), 'info', 'threads')
-            elif (not self.request.acl.private_threads.can_invite_ignoring() and
-                    not user.allow_pd_invite(self.request.user)):
-                self.request.messages.set_flash(Message(_('%(user)s restricts who can invite him to private threads.') % {'user': user.username}), 'info', 'threads')
-            else:
-                self.thread.participants.add(user)
-                user.sync_pds = True
-                user.save(force_update=True)
-                user.email_user(self.request, 'private_thread_invite', _("You've been invited to private thread \"%(thread)s\" by %(user)s") % {'thread': self.thread.name, 'user': self.request.user.username}, {'author': self.request.user, 'thread': self.thread})
-                self.thread.set_checkpoint(self.request, 'invited', user)
-                self.request.messages.set_flash(Message(_('%(user)s has been added to this thread.') % {'user': user.username}), 'success', 'threads')
-        except User.DoesNotExist:
-            self.request.messages.set_flash(Message(_('User with requested username could not be found.')), 'error', 'threads')
-        return self.retreat_redirect()
-
-
-class RemoveUserView(JumpView, TypeMixin):
-    def make_jump(self):
-        target_user = int(self.request.POST.get('user', 0))
-        if (not (self.request.user.pk == self.thread.start_poster_id or
-                self.request.acl.private_threads.is_mod()) and
-                target_user != self.request.user.pk):
-            raise ACLError403(_("You don't have permission to remove discussion participants."))
-        try:
-            user = self.thread.participants.get(id=target_user)
-            self.thread.participants.remove(user)
-            self.thread.threadread_set.filter(id=user.pk).delete()
-            self.thread.watchedthread_set.filter(id=user.pk).delete()
-            user.sync_pds = True
-            user.save(force_update=True)
-            # If there are no more participants in thread, remove it
-            if self.thread.participants.count() == 0:
-                self.thread.delete()
-                self.request.messages.set_flash(Message(_('Thread has been deleted because last participant left it.')), 'info', 'threads')
-                return self.threads_list_redirect()
-            # Nope, see if we removed ourselves
-            if user.pk == self.request.user.pk:
-                self.thread.set_checkpoint(self.request, 'left')
-                self.request.messages.set_flash(Message(_('You have left the "%(thread)s" thread.') % {'thread': self.thread.name}), 'info', 'threads')
-                return self.threads_list_redirect()
-            # Nope, somebody else removed user
-            user.sync_pds = True
-            user.save(force_update=True)
-            self.thread.set_checkpoint(self.request, 'removed', user)
-            self.request.messages.set_flash(Message(_('Selected participant was removed from thread.')), 'info', 'threads')
-            return self.retreat_redirect()
-        except User.DoesNotExist:
-            self.request.messages.set_flash(Message(_('Requested thread participant does not exist.')), 'error', 'threads')
-            return self.retreat_redirect()
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.threadtype.jumps import *
+from misago.models import User
+from misago.utils.strings import slugify
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class LastReplyView(LastReplyBaseView, TypeMixin):
+    pass
+
+
+class FindReplyView(FindReplyBaseView, TypeMixin):
+    pass
+
+
+class NewReplyView(NewReplyBaseView, TypeMixin):
+    pass
+
+
+class ShowHiddenRepliesView(ShowHiddenRepliesBaseView, TypeMixin):
+    pass
+
+
+class WatchThreadView(WatchThreadBaseView, TypeMixin):
+    pass
+
+
+class WatchEmailThreadView(WatchEmailThreadBaseView, TypeMixin):
+    pass
+
+
+class UnwatchThreadView(UnwatchThreadBaseView, TypeMixin):
+    pass
+
+
+class UnwatchEmailThreadView(UnwatchEmailThreadBaseView, TypeMixin):
+    pass
+
+
+class FirstReportedView(FirstReportedBaseView, TypeMixin):
+    pass
+
+
+class ReportPostView(ReportPostBaseView, TypeMixin):
+    pass
+
+
+class ShowPostReportView(ShowPostReportBaseView, TypeMixin):
+    pass
+
+
+class InviteUserView(JumpView, TypeMixin):
+    def make_jump(self):
+        username = slugify(self.request.POST.get('username', '').strip())
+        if not username:
+            messages.error(self.request, _('You have to enter name of user you want to invite to thread.'), 'threads')
+            return self.retreat_redirect()
+        try:
+            user = User.objects.get(username_slug=username)
+            acl = user.acl(self.request)
+            if user in self.thread.participants.all():
+                if user.pk == self.request.user.pk:
+                    messages.error(self.request, _('You cannot add yourself to this thread.'), 'threads')
+                else:
+                    messages.info(self.request, _('%(user)s is already participating in this thread.') % {'user': user.username}, 'threads')
+            elif not acl.private_threads.can_participate():
+                messages.info(self.request, _('%(user)s cannot participate in private threads.') % {'user': user.username}, 'threads')
+            elif (not self.request.acl.private_threads.can_invite_ignoring() and
+                    not user.allow_pd_invite(self.request.user)):
+                messages.info(self.request, _('%(user)s restricts who can invite him to private threads.') % {'user': user.username}, 'threads')
+            else:
+                self.thread.participants.add(user)
+                user.sync_pds = True
+                user.save(force_update=True)
+                user.email_user(self.request, 'private_thread_invite', _("You've been invited to private thread \"%(thread)s\" by %(user)s") % {'thread': self.thread.name, 'user': self.request.user.username}, {'author': self.request.user, 'thread': self.thread})
+                self.thread.set_checkpoint(self.request, 'invited', user)
+                messages.success(self.request, _('%(user)s has been added to this thread.') % {'user': user.username}, 'threads')
+        except User.DoesNotExist:
+            messages.error(self.request, _('User with requested username could not be found.'), 'threads')
+        return self.retreat_redirect()
+
+
+class RemoveUserView(JumpView, TypeMixin):
+    def make_jump(self):
+        target_user = int(self.request.POST.get('user', 0))
+        if (not (self.request.user.pk == self.thread.start_poster_id or
+                self.request.acl.private_threads.is_mod()) and
+                target_user != self.request.user.pk):
+            raise ACLError403(_("You don't have permission to remove discussion participants."))
+        try:
+            user = self.thread.participants.get(id=target_user)
+            self.thread.participants.remove(user)
+            self.thread.threadread_set.filter(id=user.pk).delete()
+            self.thread.watchedthread_set.filter(id=user.pk).delete()
+            user.sync_pds = True
+            user.save(force_update=True)
+            # If there are no more participants in thread, remove it
+            if self.thread.participants.count() == 0:
+                self.thread.delete()
+                messages.info(self.request, _('Thread has been deleted because last participant left it.'), 'threads')
+                return self.threads_list_redirect()
+            # Nope, see if we removed ourselves
+            if user.pk == self.request.user.pk:
+                self.thread.set_checkpoint(self.request, 'left')
+                messages.info(self.request, _('You have left the "%(thread)s" thread.') % {'thread': self.thread.name}, 'threads')
+                return self.threads_list_redirect()
+            # Nope, somebody else removed user
+            user.sync_pds = True
+            user.save(force_update=True)
+            self.thread.set_checkpoint(self.request, 'removed', user)
+            messages.info(self.request, _('Selected participant was removed from thread.'), 'threads')
+            return self.retreat_redirect()
+        except User.DoesNotExist:
+            messages.error(self.request, _('Requested thread participant does not exist.'), 'threads')
+            return self.retreat_redirect()

+ 90 - 89
misago/apps/privatethreads/posting.py

@@ -1,90 +1,91 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.threadtype.posting import NewThreadBaseView, EditThreadBaseView, NewReplyBaseView, EditReplyBaseView
-from misago.messages import Message
-from misago.models import Forum, Thread, Post, User
-from misago.apps.privatethreads.forms import (NewThreadForm, EditThreadForm,
-                                              NewReplyForm, EditReplyForm)
-from misago.apps.privatethreads.mixins import TypeMixin
-
-class NewThreadView(NewThreadBaseView, TypeMixin):
-    form_type = NewThreadForm
-
-    def set_forum_context(self):
-        self.forum = Forum.objects.get(special='private_threads')
-
-    def form_initial_data(self):
-        if self.kwargs.get('user'):
-            try:
-                user = User.objects.get(id=self.kwargs.get('user'))
-                acl = user.acl(self.request)
-                if not acl.private_threads.can_participate():
-                    raise ACLError403(_("This member can not participate in private threads."))
-                if (not self.request.acl.private_threads.can_invite_ignoring() and
-                        not user.allow_pd_invite(self.request.user)):
-                    raise ACLError403(_('%(user)s restricts who can invite him to private threads.') % {'user': user.username})
-                return {'invite_users': user.username}
-            except User.DoesNotExist:
-                raise ACLError404()
-        return {}
-
-    def after_form(self, form):
-        self.thread.participants.add(self.request.user)
-        self.invite_users(form.invite_users)
-        self.whitelist_mentions()
-        self.force_stats_sync()
-
-    def response(self):
-        if self.post.moderated:
-            self.request.messages.set_flash(Message(_("New thread has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_("New thread has been posted.")), 'success', 'threads')
-        return redirect(reverse('private_thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
-
-
-class EditThreadView(EditThreadBaseView, TypeMixin):
-    form_type = EditThreadForm
-
-    def after_form(self, form):
-        self.whitelist_mentions()
-    
-    def response(self):
-        self.request.messages.set_flash(Message(_("Your thread has been edited.")), 'success', 'threads_%s' % self.post.pk)
-        return redirect(reverse('private_thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
-
-
-class NewReplyView(NewReplyBaseView, TypeMixin):
-    form_type = NewReplyForm
-
-    def set_context(self):
-        super(NewReplyView, self).set_context()
-        if not (self.request.acl.private_threads.is_mod() or self.thread.participants.count() > 1):
-            raise ACLError403(_("This thread needs to have more than one participant to allow new replies."))
-
-    def after_form(self, form):
-        try:
-            self.invite_users(form.invite_users)
-        except AttributeError:
-            pass
-        self.whitelist_mentions()
-        self.force_stats_sync()
-
-    def response(self):
-        if self.post.moderated:
-            self.request.messages.set_flash(Message(_("Your reply has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads_%s' % self.post.pk)
-        else:
-            self.request.messages.set_flash(Message(_("Your reply has been posted.")), 'success', 'threads_%s' % self.post.pk)
-        return self.redirect_to_post(self.post)
-
-
-class EditReplyView(EditReplyBaseView, TypeMixin):
-    form_type = EditReplyForm
-
-    def after_form(self, form):
-        self.whitelist_mentions()
-
-    def response(self):
-        self.request.messages.set_flash(Message(_("Your reply has been changed.")), 'success', 'threads_%s' % self.post.pk)
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.threadtype.posting import NewThreadBaseView, EditThreadBaseView, NewReplyBaseView, EditReplyBaseView
+from misago.messages import Message
+from misago.models import Forum, Thread, Post, User
+from misago.apps.privatethreads.forms import (NewThreadForm, EditThreadForm,
+                                              NewReplyForm, EditReplyForm)
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class NewThreadView(NewThreadBaseView, TypeMixin):
+    form_type = NewThreadForm
+
+    def set_forum_context(self):
+        self.forum = Forum.objects.get(special='private_threads')
+
+    def form_initial_data(self):
+        if self.kwargs.get('user'):
+            try:
+                user = User.objects.get(id=self.kwargs.get('user'))
+                acl = user.acl(self.request)
+                if not acl.private_threads.can_participate():
+                    raise ACLError403(_("This member can not participate in private threads."))
+                if (not self.request.acl.private_threads.can_invite_ignoring() and
+                        not user.allow_pd_invite(self.request.user)):
+                    raise ACLError403(_('%(user)s restricts who can invite him to private threads.') % {'user': user.username})
+                return {'invite_users': user.username}
+            except User.DoesNotExist:
+                raise ACLError404()
+        return {}
+
+    def after_form(self, form):
+        self.thread.participants.add(self.request.user)
+        self.invite_users(form.invite_users)
+        self.whitelist_mentions()
+        self.force_stats_sync()
+
+    def response(self):
+        if self.post.moderated:
+            messages.success(self.request, _("New thread has been posted. It will be hidden from other members until moderator reviews it."), 'threads')
+        else:
+            messages.success(self.request, _("New thread has been posted."), 'threads')
+        return redirect(reverse('private_thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
+
+
+class EditThreadView(EditThreadBaseView, TypeMixin):
+    form_type = EditThreadForm
+
+    def after_form(self, form):
+        self.whitelist_mentions()
+
+    def response(self):
+        messages.success(self.request, _("Your thread has been edited."), 'threads_%s' % self.post.pk)
+        return redirect(reverse('private_thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
+
+
+class NewReplyView(NewReplyBaseView, TypeMixin):
+    form_type = NewReplyForm
+
+    def set_context(self):
+        super(NewReplyView, self).set_context()
+        if not (self.request.acl.private_threads.is_mod() or self.thread.participants.count() > 1):
+            raise ACLError403(_("This thread needs to have more than one participant to allow new replies."))
+
+    def after_form(self, form):
+        try:
+            self.invite_users(form.invite_users)
+        except AttributeError:
+            pass
+        self.whitelist_mentions()
+        self.force_stats_sync()
+
+    def response(self):
+        if self.post.moderated:
+            messages.success(self.request, _("Your reply has been posted. It will be hidden from other members until moderator reviews it."), 'threads_%s' % self.post.pk)
+        else:
+            messages.success(self.request, _("Your reply has been posted."), 'threads_%s' % self.post.pk)
+        return self.redirect_to_post(self.post)
+
+
+class EditReplyView(EditReplyBaseView, TypeMixin):
+    form_type = EditReplyForm
+
+    def after_form(self, form):
+        self.whitelist_mentions()
+
+    def response(self):
+        messages.success(self.request, _("Your reply has been changed."), 'threads_%s' % self.post.pk)
         return self.redirect_to_post(self.post)
         return self.redirect_to_post(self.post)

+ 24 - 23
misago/apps/readall.py

@@ -1,23 +1,24 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.decorators import block_guest, check_csrf
-from misago.messages import Message
-from misago.models import ForumRead, ThreadRead
-
-@block_guest
-@check_csrf
-def read_all(request):
-    ForumRead.objects.filter(user=request.user).delete()
-    ThreadRead.objects.filter(user=request.user).delete()
-    now = timezone.now()
-    bulk = []
-    for forum in request.acl.forums.known_forums():
-        new_record = ForumRead(user=request.user, forum_id=forum, updated=now, cleared=now)
-        bulk.append(new_record)
-    if bulk:
-        ForumRead.objects.bulk_create(bulk)
-    request.messages.set_flash(Message(_("All forums have been marked as read.")), 'success')
-    return redirect(reverse('index'))
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.decorators import block_guest, check_csrf
+from misago.messages import Message
+from misago.models import ForumRead, ThreadRead
+
+@block_guest
+@check_csrf
+def read_all(request):
+    ForumRead.objects.filter(user=request.user).delete()
+    ThreadRead.objects.filter(user=request.user).delete()
+    now = timezone.now()
+    bulk = []
+    for forum in request.acl.forums.known_forums():
+        new_record = ForumRead(user=request.user, forum_id=forum, updated=now, cleared=now)
+        bulk.append(new_record)
+    if bulk:
+        ForumRead.objects.bulk_create(bulk)
+    messages.success(request, _("All forums have been marked as read."))
+    return redirect(reverse('index'))

+ 84 - 84
misago/apps/register/views.py

@@ -1,85 +1,85 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.auth import sign_user_in
-from misago.conf import settings
-from misago.decorators import block_authenticated, block_banned, block_crawlers, block_jammed
-from misago.messages import Message
-from misago.models import SignInAttempt, User
-from misago.shortcuts import render_to_response
-from misago.utils.views import redirect_message
-from misago.apps.register.forms import UserRegisterForm
-
-@block_crawlers
-@block_banned
-@block_authenticated
-@block_jammed
-def form(request):
-    if settings.account_activation == 'block':
-       return redirect_message(request, Message(_("We are sorry but we don't allow new members registrations at this time.")), 'info')
-    
-    message = None
-    if request.method == 'POST':
-        form = UserRegisterForm(request.POST, request=request)
-        if form.is_valid():
-            need_activation = 0
-            if settings.account_activation == 'user':
-                need_activation = User.ACTIVATION_USER
-            if settings.account_activation == 'admin':
-                need_activation = User.ACTIVATION_ADMIN
-                
-            new_user = User.objects.create_user(
-                                                form.cleaned_data['username'],
-                                                form.cleaned_data['email'],
-                                                form.cleaned_data['password'],
-                                                ip=request.session.get_ip(request),
-                                                agent=request.META.get('HTTP_USER_AGENT'),
-                                                activation=need_activation,
-                                                request=request
-                                                )
-                        
-            if need_activation == User.ACTIVATION_NONE:
-                # Sign in user
-                sign_user_in(request, new_user)
-                request.messages.set_flash(Message(_("Welcome aboard, %(username)s! Your account has been registered successfully.") % {'username': new_user.username}), 'success')
-                
-            if need_activation == User.ACTIVATION_USER:
-                # Mail user activation e-mail
-                request.messages.set_flash(Message(_("%(username)s, your account has been registered, but you will have to activate it before you will be able to sign-in. We have sent you an e-mail with activation link.") % {'username': new_user.username}), 'info')
-                new_user.email_user(
-                                    request,
-                                    'users/activation/user',
-                                    _("Welcome aboard, %(username)s!") % {'username': new_user.username},
-                                    )
-                
-            if need_activation == User.ACTIVATION_ADMIN:
-                # Require admin activation
-                request.messages.set_flash(Message(_("%(username)s, Your account has been registered, but you won't be able to sign in until board administrator accepts it. We'll notify when this happens. Thank you for your patience!") % {'username': new_user.username}), 'info')
-                new_user.email_user(
-                                    request,
-                                    'users/activation/admin',
-                                    _("Welcome aboard, %(username)s!") % {'username': new_user.username},
-                                    {'password': form.cleaned_data['password']}
-                                    )
-            
-            User.objects.resync_monitor()
-            return redirect(reverse('index'))
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-            if settings.registrations_jams:
-                SignInAttempt.objects.register_attempt(request.session.get_ip(request))
-            # Have we jammed our account?
-            if SignInAttempt.objects.is_jammed(request.session.get_ip(request)):
-                request.jam.expires = timezone.now()
-                return redirect(reverse('register'))
-    else:
-        form = UserRegisterForm(request=request)
-    return render_to_response('register.html',
-                              {
-                              'message': message,
-                              'form': form,
-                              'hide_signin': True, 
-                              },
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago.auth import sign_user_in
+from misago.conf import settings
+from misago.decorators import block_authenticated, block_banned, block_crawlers, block_jammed
+from misago import messages
+from misago.messages import Message
+from misago.models import SignInAttempt, User
+from misago.shortcuts import redirect_message, render_to_response
+from misago.apps.register.forms import UserRegisterForm
+
+@block_crawlers
+@block_banned
+@block_authenticated
+@block_jammed
+def form(request):
+    if settings.account_activation == 'block':
+       return redirect_message(request, messages.INFO, _("We are sorry but we don't allow new members registrations at this time."))
+
+    message = None
+    if request.method == 'POST':
+        form = UserRegisterForm(request.POST, request=request)
+        if form.is_valid():
+            need_activation = 0
+            if settings.account_activation == 'user':
+                need_activation = User.ACTIVATION_USER
+            if settings.account_activation == 'admin':
+                need_activation = User.ACTIVATION_ADMIN
+
+            new_user = User.objects.create_user(
+                                                form.cleaned_data['username'],
+                                                form.cleaned_data['email'],
+                                                form.cleaned_data['password'],
+                                                ip=request.session.get_ip(request),
+                                                agent=request.META.get('HTTP_USER_AGENT'),
+                                                activation=need_activation,
+                                                request=request
+                                                )
+
+            if need_activation == User.ACTIVATION_NONE:
+                # Sign in user
+                sign_user_in(request, new_user)
+                messages.success(request, _("Welcome aboard, %(username)s! Your account has been registered successfully.") % {'username': new_user.username})
+
+            if need_activation == User.ACTIVATION_USER:
+                # Mail user activation e-mail
+                messages.info(request, _("%(username)s, your account has been registered, but you will have to activate it before you will be able to sign-in. We have sent you an e-mail with activation link.") % {'username': new_user.username})
+                new_user.email_user(
+                                    request,
+                                    'users/activation/user',
+                                    _("Welcome aboard, %(username)s!") % {'username': new_user.username},
+                                    )
+
+            if need_activation == User.ACTIVATION_ADMIN:
+                # Require admin activation
+                messages.info(request, _("%(username)s, Your account has been registered, but you won't be able to sign in until board administrator accepts it. We'll notify when this happens. Thank you for your patience!") % {'username': new_user.username})
+                new_user.email_user(
+                                    request,
+                                    'users/activation/admin',
+                                    _("Welcome aboard, %(username)s!") % {'username': new_user.username},
+                                    {'password': form.cleaned_data['password']}
+                                    )
+
+            User.objects.resync_monitor()
+            return redirect(reverse('index'))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+            if settings.registrations_jams:
+                SignInAttempt.objects.register_attempt(request.session.get_ip(request))
+            # Have we jammed our account?
+            if SignInAttempt.objects.is_jammed(request.session.get_ip(request)):
+                request.jam.expires = timezone.now()
+                return redirect(reverse('register'))
+    else:
+        form = UserRegisterForm(request=request)
+    return render_to_response('register.html',
+                              {
+                              'message': message,
+                              'form': form,
+                              'hide_signin': True,
+                              },
                               context_instance=RequestContext(request));
                               context_instance=RequestContext(request));

+ 122 - 121
misago/apps/reports/list.py

@@ -1,121 +1,122 @@
-from itertools import chain
-from django.core.urlresolvers import reverse
-from django.db.models import F
-from django.http import Http404
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.apps.threadtype.list import ThreadsListBaseView, ThreadsListModeration
-from misago.conf import settings
-from misago.messages import Message
-from misago.models import Forum, Thread, Post
-from misago.monitor import monitor, UpdatingMonitor
-from misago.readstrackers import ThreadsTracker
-from misago.utils.pagination import make_pagination
-from misago.apps.reports.mixins import TypeMixin
-
-class ThreadsListView(ThreadsListBaseView, ThreadsListModeration, TypeMixin):
-    def fetch_forum(self):
-        self.forum = Forum.objects.get(special='reports')
-
-    def threads_queryset(self):
-        announcements = self.forum.thread_set.filter(weight=2).prefetch_related('report_for').order_by('-pk')
-        threads = self.forum.thread_set.filter(weight__lt=2).prefetch_related('report_for').order_by('-weight', '-last')
-
-        # Add in first and last poster
-        if settings.avatars_on_threads_list:
-            announcements = announcements.prefetch_related('start_poster', 'last_poster')
-            threads = threads.prefetch_related('start_poster', 'last_poster')
-
-        return announcements, threads
-
-    def fetch_threads(self):
-        qs_announcements, qs_threads = self.threads_queryset()
-        self.count = qs_threads.count()
-
-        try:
-            self.pagination = make_pagination(self.kwargs.get('page', 0), self.count, settings.threads_per_page)
-        except Http404:
-            return self.threads_list_redirect()
-
-        tracker_forum = ThreadsTracker(self.request, self.forum)
-        unresolved_count = 0
-        for thread in list(chain(qs_announcements, qs_threads[self.pagination['start']:self.pagination['stop']])):
-            thread.original_weight = thread.weight
-            if thread.weight == 2:
-                unresolved_count += 1
-            thread.is_read = tracker_forum.is_read(thread)
-            thread.report_forum = None
-            if thread.report_for_id:
-                thread.report_forum = Forum.objects.forums_tree.get(thread.report_for.forum_id)
-            self.threads.append(thread)
-
-        if monitor['reported_posts'] != unresolved_count:
-            with UpdatingMonitor() as cm:
-                monitor['reported_posts'] = unresolved_count
-
-    def threads_actions(self):
-        acl = self.request.acl.threads.get_role(self.forum)
-        actions = []
-        try:
-            actions.append(('sticky', _('Change to resolved')))
-            actions.append(('normal', _('Change to bogus')))
-            if acl['can_delete_threads']:
-                actions.append(('undelete', _('Restore reports')))
-                actions.append(('soft', _('Hide reports')))
-            if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Delete reports')))
-        except KeyError:
-            pass
-        return actions
-
-    def mass_resolve(self, ids):
-        reported_posts = []
-        reported_threads = []
-        for thread in self.threads:
-            if thread.pk in ids:
-                if thread.original_weight != thread.weight:
-                    if thread.weight == 1:
-                        thread.set_checkpoint(self.request, 'resolved')
-                    if thread.weight == 0:
-                        thread.set_checkpoint(self.request, 'bogus')
-                if thread.original_weight == 2 and thread.report_for_id:
-                    reported_posts.append(thread.report_for.pk)
-                    reported_threads.append(thread.report_for.thread_id)
-        if reported_threads:
-            Thread.objects.filter(id__in=reported_threads).update(replies_reported=F('replies_reported') - 1)
-            Post.objects.filter(id__in=reported_posts).update(reported=False, reports=None)
-            with UpdatingMonitor() as cm:
-                monitor.decrease('reported_posts', len(reported_threads))
-
-    def action_sticky(self, ids):
-        if self._action_sticky(ids):
-            self.mass_resolve(ids)
-            self.request.messages.set_flash(Message(_('Selected reports were set as resolved.')), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_('No reports were set as resolved.')), 'info', 'threads')
-
-    def action_normal(self, ids):
-        if self._action_normal(ids):
-            self.mass_resolve(ids)
-            self.request.messages.set_flash(Message(_('Selected reports were set as bogus.')), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_('No reports were set as bogus.')), 'info', 'threads')
-
-    def action_undelete(self, ids):
-        if self._action_undelete(ids):
-            self.request.messages.set_flash(Message(_('Selected reports have been restored.')), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_('No reports were restored.')), 'info', 'threads')
-
-    def action_soft(self, ids):
-        if self._action_soft(ids):
-            self.mass_resolve(ids)
-            self.request.messages.set_flash(Message(_('Selected reports have been hidden.')), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_('No reports were hidden.')), 'info', 'threads')
-
-    def action_hard(self, ids):
-        if self._action_hard(ids):
-            self.request.messages.set_flash(Message(_('Selected reports have been deleted.')), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_('No reports were deleted.')), 'info', 'threads')
+from itertools import chain
+from django.core.urlresolvers import reverse
+from django.db.models import F
+from django.http import Http404
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.threadtype.list import ThreadsListBaseView, ThreadsListModeration
+from misago.conf import settings
+from misago.messages import Message
+from misago.models import Forum, Thread, Post
+from misago.monitor import monitor, UpdatingMonitor
+from misago.readstrackers import ThreadsTracker
+from misago.utils.pagination import make_pagination
+from misago.apps.reports.mixins import TypeMixin
+
+class ThreadsListView(ThreadsListBaseView, ThreadsListModeration, TypeMixin):
+    def fetch_forum(self):
+        self.forum = Forum.objects.get(special='reports')
+
+    def threads_queryset(self):
+        announcements = self.forum.thread_set.filter(weight=2).prefetch_related('report_for').order_by('-pk')
+        threads = self.forum.thread_set.filter(weight__lt=2).prefetch_related('report_for').order_by('-weight', '-last')
+
+        # Add in first and last poster
+        if settings.avatars_on_threads_list:
+            announcements = announcements.prefetch_related('start_poster', 'last_poster')
+            threads = threads.prefetch_related('start_poster', 'last_poster')
+
+        return announcements, threads
+
+    def fetch_threads(self):
+        qs_announcements, qs_threads = self.threads_queryset()
+        self.count = qs_threads.count()
+
+        try:
+            self.pagination = make_pagination(self.kwargs.get('page', 0), self.count, settings.threads_per_page)
+        except Http404:
+            return self.threads_list_redirect()
+
+        tracker_forum = ThreadsTracker(self.request, self.forum)
+        unresolved_count = 0
+        for thread in list(chain(qs_announcements, qs_threads[self.pagination['start']:self.pagination['stop']])):
+            thread.original_weight = thread.weight
+            if thread.weight == 2:
+                unresolved_count += 1
+            thread.is_read = tracker_forum.is_read(thread)
+            thread.report_forum = None
+            if thread.report_for_id:
+                thread.report_forum = Forum.objects.forums_tree.get(thread.report_for.forum_id)
+            self.threads.append(thread)
+
+        if monitor['reported_posts'] != unresolved_count:
+            with UpdatingMonitor() as cm:
+                monitor['reported_posts'] = unresolved_count
+
+    def threads_actions(self):
+        acl = self.request.acl.threads.get_role(self.forum)
+        actions = []
+        try:
+            actions.append(('sticky', _('Change to resolved')))
+            actions.append(('normal', _('Change to bogus')))
+            if acl['can_delete_threads']:
+                actions.append(('undelete', _('Restore reports')))
+                actions.append(('soft', _('Hide reports')))
+            if acl['can_delete_threads'] == 2:
+                actions.append(('hard', _('Delete reports')))
+        except KeyError:
+            pass
+        return actions
+
+    def mass_resolve(self, ids):
+        reported_posts = []
+        reported_threads = []
+        for thread in self.threads:
+            if thread.pk in ids:
+                if thread.original_weight != thread.weight:
+                    if thread.weight == 1:
+                        thread.set_checkpoint(self.request, 'resolved')
+                    if thread.weight == 0:
+                        thread.set_checkpoint(self.request, 'bogus')
+                if thread.original_weight == 2 and thread.report_for_id:
+                    reported_posts.append(thread.report_for.pk)
+                    reported_threads.append(thread.report_for.thread_id)
+        if reported_threads:
+            Thread.objects.filter(id__in=reported_threads).update(replies_reported=F('replies_reported') - 1)
+            Post.objects.filter(id__in=reported_posts).update(reported=False, reports=None)
+            with UpdatingMonitor() as cm:
+                monitor.decrease('reported_posts', len(reported_threads))
+
+    def action_sticky(self, ids):
+        if self._action_sticky(ids):
+            self.mass_resolve(ids)
+            messages.success(self.request, _('Selected reports were set as resolved.'), 'threads')
+        else:
+            messages.info(self.request, _('No reports were set as resolved.'), 'threads')
+
+    def action_normal(self, ids):
+        if self._action_normal(ids):
+            self.mass_resolve(ids)
+            messages.success(self.request, _('Selected reports were set as bogus.'), 'threads')
+        else:
+            messages.info(self.request, _('No reports were set as bogus.'), 'threads')
+
+    def action_undelete(self, ids):
+        if self._action_undelete(ids):
+            messages.success(self.request, _('Selected reports have been restored.'), 'threads')
+        else:
+            messages.info(self.request, _('No reports were restored.'), 'threads')
+
+    def action_soft(self, ids):
+        if self._action_soft(ids):
+            self.mass_resolve(ids)
+            messages.success(self.request, _('Selected reports have been hidden.'), 'threads')
+        else:
+            messages.info(self.request, _('No reports were hidden.'), 'threads')
+
+    def action_hard(self, ids):
+        if self._action_hard(ids):
+            messages.success(self.request, _('Selected reports have been deleted.'), 'threads')
+        else:
+            messages.info(self.request, _('No reports were deleted.'), 'threads')

+ 47 - 46
misago/apps/reports/posting.py

@@ -1,46 +1,47 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.apps.threadtype.posting import EditThreadBaseView, NewReplyBaseView, EditReplyBaseView
-from misago.messages import Message
-from misago.models import Forum, Thread, Post
-from misago.monitor import monitor, UpdatingMonitor
-from misago.apps.reports.mixins import TypeMixin
-from misago.apps.reports.forms import EditThreadForm, NewReplyForm, EditReplyForm
-
-class SetStateCheckpointMixin(object):
-    def post_form(self, form):
-        self.thread.original_weight = self.thread.weight
-        super(SetStateCheckpointMixin, self).post_form(form)
-        if self.thread.original_weight != self.thread.weight:
-            if self.thread.original_weight == 2:
-                with UpdatingMonitor() as cm:
-                    monitor.decrease('reported_posts')
-            if self.thread.weight == 1:
-                self.thread.set_checkpoint(self.request, 'resolved')
-            if self.thread.weight == 0:
-                self.thread.set_checkpoint(self.request, 'bogus')
-
-
-class EditThreadView(SetStateCheckpointMixin, EditThreadBaseView, TypeMixin):
-    form_type = EditThreadForm
-
-    def response(self):
-        self.request.messages.set_flash(Message(_("Report has been edited.")), 'success', 'threads_%s' % self.post.pk)
-        return redirect(reverse('report', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
-
-
-class NewReplyView(SetStateCheckpointMixin, NewReplyBaseView, TypeMixin):
-    form_type = NewReplyForm
-
-    def response(self):
-        self.request.messages.set_flash(Message(_("Your reply has been posted.")), 'success', 'threads_%s' % self.post.pk)
-        return self.redirect_to_post(self.post)
-
-
-class EditReplyView(SetStateCheckpointMixin, EditReplyBaseView, TypeMixin):
-    form_type = EditReplyForm
-    
-    def response(self):
-        self.request.messages.set_flash(Message(_("Your reply has been changed.")), 'success', 'threads_%s' % self.post.pk)
-        return self.redirect_to_post(self.post)
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.threadtype.posting import EditThreadBaseView, NewReplyBaseView, EditReplyBaseView
+from misago.messages import Message
+from misago.models import Forum, Thread, Post
+from misago.monitor import monitor, UpdatingMonitor
+from misago.apps.reports.mixins import TypeMixin
+from misago.apps.reports.forms import EditThreadForm, NewReplyForm, EditReplyForm
+
+class SetStateCheckpointMixin(object):
+    def post_form(self, form):
+        self.thread.original_weight = self.thread.weight
+        super(SetStateCheckpointMixin, self).post_form(form)
+        if self.thread.original_weight != self.thread.weight:
+            if self.thread.original_weight == 2:
+                with UpdatingMonitor() as cm:
+                    monitor.decrease('reported_posts')
+            if self.thread.weight == 1:
+                self.thread.set_checkpoint(self.request, 'resolved')
+            if self.thread.weight == 0:
+                self.thread.set_checkpoint(self.request, 'bogus')
+
+
+class EditThreadView(SetStateCheckpointMixin, EditThreadBaseView, TypeMixin):
+    form_type = EditThreadForm
+
+    def response(self):
+        messages.success(self.request, _("Report has been edited."), 'threads_%s' % self.post.pk)
+        return redirect(reverse('report', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
+
+
+class NewReplyView(SetStateCheckpointMixin, NewReplyBaseView, TypeMixin):
+    form_type = NewReplyForm
+
+    def response(self):
+        messages.success(self.request, _("Your reply has been posted."), 'threads_%s' % self.post.pk)
+        return self.redirect_to_post(self.post)
+
+
+class EditReplyView(SetStateCheckpointMixin, EditReplyBaseView, TypeMixin):
+    form_type = EditReplyForm
+
+    def response(self):
+        messages.success(self.request, _("Your reply has been changed."), 'threads_%s' % self.post.pk)
+        return self.redirect_to_post(self.post)

+ 74 - 73
misago/apps/reports/thread.py

@@ -1,73 +1,74 @@
-from django.utils.translation import ugettext as _
-from misago.apps.threadtype.thread import ThreadBaseView, ThreadModeration, PostsModeration
-from misago.messages import Message
-from misago.models import Forum, Thread
-from misago.monitor import monitor, UpdatingMonitor
-from misago.apps.reports.mixins import TypeMixin
-
-class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
-    def fetch_thread(self):
-        super(ThreadView, self).fetch_thread()
-        self.thread.original_weight = self.thread.weight
-
-    def posts_actions(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        actions = []
-        try:
-            if acl['can_delete_posts']:
-                if self.thread.replies_deleted > 0:
-                    actions.append(('undelete', _('Restore posts')))
-                actions.append(('soft', _('Hide posts')))
-            if acl['can_delete_posts'] == 2:
-                actions.append(('hard', _('Delete posts')))
-        except KeyError:
-            pass
-        return actions
-
-    def thread_actions(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        actions = []
-        try:
-            if self.thread.weight != 1:
-                actions.append(('sticky', _('Change to resolved')))
-            if self.thread.weight != 0:
-                actions.append(('normal', _('Change to bogus')))
-            if acl['can_delete_threads']:
-                if self.thread.deleted:
-                    actions.append(('undelete', _('Restore this report')))
-                else:
-                    actions.append(('soft', _('Hide this report')))
-            if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Delete this report')))
-        except KeyError:
-            pass
-        return actions
-
-    def after_thread_action_sticky(self):
-        self.thread.set_checkpoint(self.request, 'resolved')
-        if self.thread.original_weight == 2:
-            with UpdatingMonitor() as cm:
-                monitor.decrease('reported_posts')
-        self.request.messages.set_flash(Message(_('Report has been set as resolved.')), 'success', 'threads')
-
-    def after_thread_action_normal(self):
-        self.thread.set_checkpoint(self.request, 'bogus')
-        if self.thread.original_weight == 2:
-            with UpdatingMonitor() as cm:
-                monitor.decrease('reported_posts')
-        self.request.messages.set_flash(Message(_('Report has been set as bogus.')), 'success', 'threads')
-
-    def after_thread_action_undelete(self):
-        if self.thread.original_weight == 2:
-            with UpdatingMonitor() as cm:
-                monitor.increase('reported_posts')
-        self.request.messages.set_flash(Message(_('Report has been restored.')), 'success', 'threads')
-
-    def after_thread_action_soft(self):
-        if self.thread.original_weight == 2:
-            with UpdatingMonitor() as cm:
-                monitor.decrease('reported_posts')
-        self.request.messages.set_flash(Message(_('Report has been hidden.')), 'success', 'threads')
-
-    def after_thread_action_hard(self):
-        self.request.messages.set_flash(Message(_('Report "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.threadtype.thread import ThreadBaseView, ThreadModeration, PostsModeration
+from misago.messages import Message
+from misago.models import Forum, Thread
+from misago.monitor import monitor, UpdatingMonitor
+from misago.apps.reports.mixins import TypeMixin
+
+class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
+    def fetch_thread(self):
+        super(ThreadView, self).fetch_thread()
+        self.thread.original_weight = self.thread.weight
+
+    def posts_actions(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        actions = []
+        try:
+            if acl['can_delete_posts']:
+                if self.thread.replies_deleted > 0:
+                    actions.append(('undelete', _('Restore posts')))
+                actions.append(('soft', _('Hide posts')))
+            if acl['can_delete_posts'] == 2:
+                actions.append(('hard', _('Delete posts')))
+        except KeyError:
+            pass
+        return actions
+
+    def thread_actions(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        actions = []
+        try:
+            if self.thread.weight != 1:
+                actions.append(('sticky', _('Change to resolved')))
+            if self.thread.weight != 0:
+                actions.append(('normal', _('Change to bogus')))
+            if acl['can_delete_threads']:
+                if self.thread.deleted:
+                    actions.append(('undelete', _('Restore this report')))
+                else:
+                    actions.append(('soft', _('Hide this report')))
+            if acl['can_delete_threads'] == 2:
+                actions.append(('hard', _('Delete this report')))
+        except KeyError:
+            pass
+        return actions
+
+    def after_thread_action_sticky(self):
+        self.thread.set_checkpoint(self.request, 'resolved')
+        if self.thread.original_weight == 2:
+            with UpdatingMonitor() as cm:
+                monitor.decrease('reported_posts')
+        messages.success(self.request, _('Report has been set as resolved.'), 'threads')
+
+    def after_thread_action_normal(self):
+        self.thread.set_checkpoint(self.request, 'bogus')
+        if self.thread.original_weight == 2:
+            with UpdatingMonitor() as cm:
+                monitor.decrease('reported_posts')
+        messages.success(self.request, _('Report has been set as bogus.'), 'threads')
+
+    def after_thread_action_undelete(self):
+        if self.thread.original_weight == 2:
+            with UpdatingMonitor() as cm:
+                monitor.increase('reported_posts')
+        messages.success(self.request, _('Report has been restored.'), 'threads')
+
+    def after_thread_action_soft(self):
+        if self.thread.original_weight == 2:
+            with UpdatingMonitor() as cm:
+                monitor.decrease('reported_posts')
+        messages.success(self.request, _('Report has been hidden.'), 'threads')
+
+    def after_thread_action_hard(self):
+        messages.success(self.request, _('Report "%(thread)s" has been deleted.') % {'thread': self.thread.name}, 'threads')

+ 87 - 88
misago/apps/resetpswd/views.py

@@ -1,89 +1,88 @@
-from django.template import RequestContext
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error404, error_banned
-from misago.decorators import block_authenticated, block_banned, block_crawlers, block_jammed
-from misago.messages import Message
-from misago.models import Ban, Session, Token, User
-from misago.shortcuts import render_to_response
-from misago.utils.strings import random_string
-from misago.utils.views import redirect_message
-from misago.apps.resetpswd.forms import UserResetPasswordForm
-
-@block_crawlers
-@block_banned
-@block_authenticated
-@block_jammed   
-def form(request):
-    message = None
-    
-    if request.method == 'POST':
-        form = UserResetPasswordForm(request.POST, request=request)
-        
-        if form.is_valid():
-            user = form.found_user
-            user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
-            
-            if user_ban:
-                return error_banned(request, user, user_ban)
-            elif user.activation != User.ACTIVATION_NONE:
-                return redirect_message(request, Message(_("%(username)s, your account has to be activated in order for you to be able to request new password.") % {'username': user.username}), 'info')
-            
-            user.token = random_string(12)
-            user.save(force_update=True)
-            user.email_user(
-                            request,
-                            'users/password/confirm',
-                            _("Confirm New Password Request")
-                            )
-            
-            return redirect_message(request, Message(_("%(username)s, new password request confirmation has been sent to %(email)s.") % {'username': user.username, 'email': user.email}), 'info')
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = UserResetPasswordForm(request=request)
-    return render_to_response('reset_password.html',
-                              {
-                              'message': message,
-                              'form': form,
-                              },
-                              context_instance=RequestContext(request));
-
-
-@block_banned
-@block_authenticated
-@block_jammed
-def reset(request, username="", user="0", token=""):
-    user = int(user)
-    try:
-        user = User.objects.get(pk=user)
-        user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
-        
-        if user_ban:
-            return error_banned(request, user, user_ban)
-        
-        if user.activation != User.ACTIVATION_NONE:
-            return redirect_message(request, Message(_("%(username)s, your account has to be activated in order for you to be able to request new password.") % {'username': user.username}), 'info')
-        
-        if not token or not user.token or user.token != token:
-            return redirect_message(request, Message(_("%(username)s, request confirmation link is invalid. Please request new confirmation link.") % {'username': user.username}), 'error')
-        
-        new_password = random_string(6)
-        user.token = None
-        user.set_password(new_password)
-        user.save(force_update=True)
-        
-        # Logout signed in and kill remember me tokens
-        Session.objects.filter(user=user).update(user=None)
-        Token.objects.filter(user=user).delete()
-        
-        # Set flash and mail new password
-        user.email_user(
-                        request,
-                        'users/password/new',
-                        _("Your New Password"),
-                        {'password': new_password}
-                        )
-        
-        return redirect_message(request, Message(_("%(username)s, your password has been changed with new one that was sent to %(email)s.") % {'username': user.username, 'email': user.email}), 'success')
-    except User.DoesNotExist:
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago.apps.errors import error404, error_banned
+from misago.decorators import block_authenticated, block_banned, block_crawlers, block_jammed
+from misago import messages
+from misago.models import Ban, Session, Token, User
+from misago.shortcuts import redirect_message, render_to_response
+from misago.utils.strings import random_string
+from misago.apps.resetpswd.forms import UserResetPasswordForm
+
+@block_crawlers
+@block_banned
+@block_authenticated
+@block_jammed
+def form(request):
+    message = None
+
+    if request.method == 'POST':
+        form = UserResetPasswordForm(request.POST, request=request)
+
+        if form.is_valid():
+            user = form.found_user
+            user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
+
+            if user_ban:
+                return error_banned(request, user, user_ban)
+            elif user.activation != User.ACTIVATION_NONE:
+                return redirect_message(request, messages.INFO, _("%(username)s, your account has to be activated in order for you to be able to request new password.") % {'username': user.username})
+
+            user.token = random_string(12)
+            user.save(force_update=True)
+            user.email_user(
+                            request,
+                            'users/password/confirm',
+                            _("Confirm New Password Request")
+                            )
+
+            return redirect_message(request, messages.INFO, _("%(username)s, new password request confirmation has been sent to %(email)s.") % {'username': user.username, 'email': user.email})
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = UserResetPasswordForm(request=request)
+    return render_to_response('reset_password.html',
+                              {
+                              'message': message,
+                              'form': form,
+                              },
+                              context_instance=RequestContext(request));
+
+
+@block_banned
+@block_authenticated
+@block_jammed
+def reset(request, username="", user="0", token=""):
+    user = int(user)
+    try:
+        user = User.objects.get(pk=user)
+        user_ban = Ban.objects.check_ban(username=user.username, email=user.email)
+
+        if user_ban:
+            return error_banned(request, user, user_ban)
+
+        if user.activation != User.ACTIVATION_NONE:
+            return redirect_message(request, messages.INFO, _("%(username)s, your account has to be activated in order for you to be able to request new password.") % {'username': user.username})
+
+        if not token or not user.token or user.token != token:
+            return redirect_message(request, messages.ERROR, _("%(username)s, request confirmation link is invalid. Please request new confirmation link.") % {'username': user.username})
+
+        new_password = random_string(6)
+        user.token = None
+        user.set_password(new_password)
+        user.save(force_update=True)
+
+        # Logout signed in and kill remember me tokens
+        Session.objects.filter(user=user).update(user=None)
+        Token.objects.filter(user=user).delete()
+
+        # Set flash and mail new password
+        user.email_user(
+                        request,
+                        'users/password/new',
+                        _("Your New Password"),
+                        {'password': new_password}
+                        )
+
+        return redirect_message(request, messages.SUCCESS, _("%(username)s, your password has been changed with new one that was sent to %(email)s.") % {'username': user.username, 'email': user.email})
+    except User.DoesNotExist:
         return error404(request)
         return error404(request)

+ 119 - 118
misago/apps/signin/views.py

@@ -1,118 +1,119 @@
-from django.core.cache import cache
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.admin import site
-from misago.messages import Message
-import misago.auth as auth
-from misago.auth import AuthException, auth_admin, auth_forum, sign_user_in
-from misago.conf import settings
-from misago.decorators import (block_authenticated, block_banned, block_crawlers,
-                            block_guest, block_jammed, check_csrf)
-from misago.models import SignInAttempt, Token
-from misago.shortcuts import render_to_response
-from misago.utils.strings import random_string
-from misago.apps.signin.forms import SignInForm
-
-@block_crawlers
-@block_banned
-@block_authenticated
-@block_jammed
-def signin(request):
-    message = request.messages.get_message('security')
-    bad_password = False
-    not_active = False
-    banned_account = False
-
-    if request.method == 'POST':
-        form = SignInForm(
-                          request.POST,
-                          show_remember_me=not request.firewall.admin and settings.remember_me_allow,
-                          request=request
-                          )
-
-        if form.is_valid():
-            try:
-                # Configure correct auth and redirect links
-                if request.firewall.admin:
-                    auth_method = auth_admin
-                    success_redirect = reverse(site.get_admin_index())
-                else:
-                    auth_method = auth_forum
-                    success_redirect = reverse('index')
-
-                # Authenticate user
-                user = auth_method(
-                                  request,
-                                  form.cleaned_data['user_email'],
-                                  form.cleaned_data['user_password'],
-                                  )
-
-                sign_user_in(request, user)
-                remember_me_token = False
-
-                if not request.firewall.admin and settings.remember_me_allow and form.cleaned_data['user_remember_me']:
-                    remember_me_token = random_string(42)
-                    remember_me = Token(
-                                        id=remember_me_token,
-                                        user=user,
-                                        created=timezone.now(),
-                                        accessed=timezone.now(),
-                                        )
-                    remember_me.save()
-                if remember_me_token:
-                    request.cookiejar.set('TOKEN', remember_me_token, True)
-                request.messages.set_flash(Message(_("Welcome back, %(username)s!") % {'username': user.username}), 'success', 'security')
-                return redirect(success_redirect)
-            except AuthException as e:
-                message = Message(e.error, 'error')
-                bad_password = e.password
-                banned_account = e.ban
-                not_active = e.activation
-
-                # If not in Admin, register failed attempt
-                if not request.firewall.admin and e.type == auth.CREDENTIALS:
-                    SignInAttempt.objects.register_attempt(request.session.get_ip(request))
-
-                    # Have we jammed our account?
-                    if SignInAttempt.objects.is_jammed(request.session.get_ip(request)):
-                        request.jam.expires = timezone.now()
-                        return redirect(reverse('sign_in'))
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = SignInForm(
-                          show_remember_me=not request.firewall.admin and settings.remember_me_allow,
-                          request=request
-                          )
-    return render_to_response('signin.html',
-                              {
-                              'message': message,
-                              'bad_password': bad_password,
-                              'banned_account': banned_account,
-                              'not_active': not_active,
-                              'form': form,
-                              'hide_signin': True,
-                              },
-                              context_instance=RequestContext(request));
-
-
-@block_crawlers
-@block_guest
-@check_csrf
-def signout(request):
-    user = request.user
-    request.session.sign_out(request)
-    request.messages.set_flash(Message(_("You have been signed out.")), 'info', 'security')
-    if request.firewall.admin:
-        return redirect(reverse(site.get_admin_index()))
-    else:
-        ranks_online = cache.get('ranks_online', 'nada')
-        if ranks_online != 'nada':
-            for rank in ranks_online:
-                if rank['id'] == user.rank_id:
-                    cache.delete('ranks_online')
-                    break
-    return redirect(reverse('index'))
+from django.core.cache import cache
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.admin import site
+from misago.messages import Message
+import misago.auth as auth
+from misago.auth import AuthException, auth_admin, auth_forum, sign_user_in
+from misago.conf import settings
+from misago.decorators import (block_authenticated, block_banned, block_crawlers,
+                            block_guest, block_jammed, check_csrf)
+from misago.models import SignInAttempt, Token
+from misago.shortcuts import render_to_response
+from misago.utils.strings import random_string
+from misago.apps.signin.forms import SignInForm
+
+@block_crawlers
+@block_banned
+@block_authenticated
+@block_jammed
+def signin(request):
+    message = request.messages.get_message('security')
+    bad_password = False
+    not_active = False
+    banned_account = False
+
+    if request.method == 'POST':
+        form = SignInForm(
+                          request.POST,
+                          show_remember_me=not request.firewall.admin and settings.remember_me_allow,
+                          request=request
+                          )
+
+        if form.is_valid():
+            try:
+                # Configure correct auth and redirect links
+                if request.firewall.admin:
+                    auth_method = auth_admin
+                    success_redirect = reverse(site.get_admin_index())
+                else:
+                    auth_method = auth_forum
+                    success_redirect = reverse('index')
+
+                # Authenticate user
+                user = auth_method(
+                                  request,
+                                  form.cleaned_data['user_email'],
+                                  form.cleaned_data['user_password'],
+                                  )
+
+                sign_user_in(request, user)
+                remember_me_token = False
+
+                if not request.firewall.admin and settings.remember_me_allow and form.cleaned_data['user_remember_me']:
+                    remember_me_token = random_string(42)
+                    remember_me = Token(
+                                        id=remember_me_token,
+                                        user=user,
+                                        created=timezone.now(),
+                                        accessed=timezone.now(),
+                                        )
+                    remember_me.save()
+                if remember_me_token:
+                    request.cookiejar.set('TOKEN', remember_me_token, True)
+                messages.success(request, _("Welcome back, %(username)s!") % {'username': user.username}, 'security')
+                return redirect(success_redirect)
+            except AuthException as e:
+                message = Message(e.error, 'error')
+                bad_password = e.password
+                banned_account = e.ban
+                not_active = e.activation
+
+                # If not in Admin, register failed attempt
+                if not request.firewall.admin and e.type == auth.CREDENTIALS:
+                    SignInAttempt.objects.register_attempt(request.session.get_ip(request))
+
+                    # Have we jammed our account?
+                    if SignInAttempt.objects.is_jammed(request.session.get_ip(request)):
+                        request.jam.expires = timezone.now()
+                        return redirect(reverse('sign_in'))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = SignInForm(
+                          show_remember_me=not request.firewall.admin and settings.remember_me_allow,
+                          request=request
+                          )
+    return render_to_response('signin.html',
+                              {
+                              'message': message,
+                              'bad_password': bad_password,
+                              'banned_account': banned_account,
+                              'not_active': not_active,
+                              'form': form,
+                              'hide_signin': True,
+                              },
+                              context_instance=RequestContext(request));
+
+
+@block_crawlers
+@block_guest
+@check_csrf
+def signout(request):
+    user = request.user
+    request.session.sign_out(request)
+    messages.info(request, _("You have been signed out."), 'security')
+    if request.firewall.admin:
+        return redirect(reverse(site.get_admin_index()))
+    else:
+        ranks_online = cache.get('ranks_online', 'nada')
+        if ranks_online != 'nada':
+            for rank in ranks_online:
+                if rank['id'] == user.rank_id:
+                    cache.delete('ranks_online')
+                    break
+    return redirect(reverse('index'))

+ 40 - 39
misago/apps/threads/posting.py

@@ -1,39 +1,40 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.apps.threadtype.posting import NewThreadBaseView, EditThreadBaseView, NewReplyBaseView, EditReplyBaseView
-from misago.messages import Message
-from misago.models import Forum, Thread, Post
-from misago.apps.threads.mixins import TypeMixin
-
-class NewThreadView(NewThreadBaseView, TypeMixin):
-    def set_forum_context(self):
-        self.forum = Forum.objects.get(pk=self.kwargs.get('forum'), type='forum')
-
-    def response(self):
-        if self.post.moderated:
-            self.request.messages.set_flash(Message(_("New thread has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads')
-        else:
-            self.request.messages.set_flash(Message(_("New thread has been posted.")), 'success', 'threads')
-        return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
-
-
-class EditThreadView(EditThreadBaseView, TypeMixin):
-    def response(self):
-        self.request.messages.set_flash(Message(_("Your thread has been edited.")), 'success', 'threads_%s' % self.post.pk)
-        return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
-
-
-class NewReplyView(NewReplyBaseView, TypeMixin):
-    def response(self):
-        if self.post.moderated:
-            self.request.messages.set_flash(Message(_("Your reply has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads_%s' % self.post.pk)
-        else:
-            self.request.messages.set_flash(Message(_("Your reply has been posted.")), 'success', 'threads_%s' % self.post.pk)
-        return self.redirect_to_post(self.post)
-
-
-class EditReplyView(EditReplyBaseView, TypeMixin):
-    def response(self):
-        self.request.messages.set_flash(Message(_("Your reply has been changed.")), 'success', 'threads_%s' % self.post.pk)
-        return self.redirect_to_post(self.post)
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.threadtype.posting import NewThreadBaseView, EditThreadBaseView, NewReplyBaseView, EditReplyBaseView
+from misago.messages import Message
+from misago.models import Forum, Thread, Post
+from misago.apps.threads.mixins import TypeMixin
+
+class NewThreadView(NewThreadBaseView, TypeMixin):
+    def set_forum_context(self):
+        self.forum = Forum.objects.get(pk=self.kwargs.get('forum'), type='forum')
+
+    def response(self):
+        if self.post.moderated:
+            messages.success(self.request, _("New thread has been posted. It will be hidden from other members until moderator reviews it."), 'threads')
+        else:
+            messages.success(self.request, _("New thread has been posted."), 'threads')
+        return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
+
+
+class EditThreadView(EditThreadBaseView, TypeMixin):
+    def response(self):
+        messages.success(self.request, _("Your thread has been edited."), 'threads_%s' % self.post.pk)
+        return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
+
+
+class NewReplyView(NewReplyBaseView, TypeMixin):
+    def response(self):
+        if self.post.moderated:
+            messages.success(self.request, _("Your reply has been posted. It will be hidden from other members until moderator reviews it.")), 'threads_%s' % self.post.pk)
+        else:
+            messages.success(self.request, _("Your reply has been posted."), 'threads_%s' % self.post.pk)
+        return self.redirect_to_post(self.post)
+
+
+class EditReplyView(EditReplyBaseView, TypeMixin):
+    def response(self):
+        messages.success(self.request, _("Your reply has been changed."), 'threads_%s' % self.post.pk)
+        return self.redirect_to_post(self.post)

+ 128 - 127
misago/apps/threadtype/changelog.py

@@ -1,127 +1,128 @@
-import difflib
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils.translation import ugettext as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.markdown import post_markdown
-from misago.messages import Message
-from misago.models import Forum, Thread, Post, Change
-from misago.shortcuts import render_to_response
-from misago.utils.datesformats import reldate
-from misago.utils.strings import slugify
-from misago.apps.threadtype.base import ViewBase
-
-class ChangelogBaseView(ViewBase):
-    def fetch_target(self):
-        self.thread = Thread.objects.get(pk=self.kwargs.get('thread'))
-        self.forum = self.thread.forum
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        if self.forum.level:
-            self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-        self.check_forum_type()
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-        self.post = Post.objects.select_related('user').get(pk=self.kwargs.get('post'), thread=self.thread.pk)
-        self.post.thread = self.thread
-        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
-        self.request.acl.threads.allow_changelog_view(self.request.user, self.forum, self.post)
-
-    def __call__(self, request, **kwargs):
-        self.request = request
-        self.kwargs = kwargs
-        self.forum = None
-        self.thread = None
-        self.post = None
-        self.parents = []
-        try:
-            self._type_available()
-            self.fetch_target()
-            self._check_permissions()
-            if not request.user.is_authenticated():
-                raise ACLError403(_("Guest, you have to sign-in in order to see posts changelogs."))
-        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist, Change.DoesNotExist):
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e)
-        except ACLError404 as e:
-            return error404(request, e)
-        return self.dispatch(request)
-
-
-class ChangelogChangesBaseView(ChangelogBaseView):
-    def dispatch(self, request, **kwargs):
-        return render_to_response('%ss/changelog.html' % self.type_prefix,
-                                  self.template_vars({
-                                      'type_prefix': self.type_prefix,
-                                      'forum': self.forum,
-                                      'parents': self.parents,
-                                      'thread': self.thread,
-                                      'post': self.post,
-                                      'edits': self.post.change_set.prefetch_related('user').order_by('-id')
-                                      }),
-                                  context_instance=RequestContext(request))
-
-
-class ChangelogDiffBaseView(ChangelogBaseView):
-    def fetch_target(self):
-        super(ChangelogDiffBaseView, self).fetch_target()
-        self.change = self.post.change_set.get(pk=self.kwargs.get('change'))
-
-    def dispatch(self, request, **kwargs):
-        try:
-            next = self.post.change_set.filter(id__gt=self.change.pk)[:1][0]
-        except IndexError:
-            next = None
-        try:
-            prev = self.post.change_set.filter(id__lt=self.change.pk).order_by('-id')[:1][0]
-        except IndexError:
-            prev = None
-        self.forum.closed = self.proxy.closed
-        return render_to_response('%ss/changelog_diff.html' % self.type_prefix,
-                                  self.template_vars({
-                                      'type_prefix': self.type_prefix,
-                                      'forum': self.forum,
-                                      'parents': self.parents,
-                                      'thread': self.thread,
-                                      'post': self.post,
-                                      'change': self.change,
-                                      'next': next,
-                                      'prev': prev,
-                                      'message': request.messages.get_message('changelog'),
-                                      'l': 1,
-                                      'diff': difflib.ndiff(self.change.post_content.splitlines(), self.post.post.splitlines()),
-                                      }),
-                                  context_instance=RequestContext(request))
-
-
-class ChangelogRevertBaseView(ChangelogDiffBaseView):
-    def fetch_target(self):
-        super(ChangelogRevertBaseView, self).fetch_target()
-        self.change = self.post.change_set.get(pk=self.kwargs.get('change'))
-        self.request.acl.threads.allow_revert(self.proxy, self.thread)
-
-    def dispatch(self, request, **kwargs):
-        if ((not self.change.thread_name_old or self.thread.name == self.change.thread_name_old)
-            and (self.change.post_content == self.post.post)):
-            request.messages.set_flash(Message(_("No changes to revert.")), 'error', 'changelog')
-            return redirect(reverse('%s_changelog_diff' % self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'post': self.post.pk, 'change': self.change.pk}))
-
-        if self.change.thread_name_old and self.change.thread_name_old != self.thread.name:
-            self.thread.name = self.change.thread_name_old
-            self.thread.slug = slugify(self.change.thread_name_old)
-            self.thread.save(force_update=True)
-
-            if self.forum.last_thread_id == self.thread.pk:
-                self.forum.last_thread_name = self.change.thread_name_old
-                self.forum.last_thread_slug = slugify(self.change.thread_name_old)
-                self.forum.save(force_update=True)
-
-        if self.change.post_content != self.post.post:
-            self.post.post = self.change.post_content
-            md, self.post.post_preparsed = post_markdown(self.change.post_content)
-            self.post.save(force_update=True)
-
-        request.messages.set_flash(Message(_("Post has been reverted to state from %(date)s.") % {'date': reldate(self.change.date).lower()}), 'success', 'threads_%s' % self.post.pk)
-        return self.redirect_to_post(self.post)
+import difflib
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.errors import error403, error404
+from misago.markdown import post_markdown
+from misago.messages import Message
+from misago.models import Forum, Thread, Post, Change
+from misago.shortcuts import render_to_response
+from misago.utils.datesformats import reldate
+from misago.utils.strings import slugify
+from misago.apps.threadtype.base import ViewBase
+
+class ChangelogBaseView(ViewBase):
+    def fetch_target(self):
+        self.thread = Thread.objects.get(pk=self.kwargs.get('thread'))
+        self.forum = self.thread.forum
+        self.request.acl.forums.allow_forum_view(self.forum)
+        self.proxy = Forum.objects.parents_aware_forum(self.forum)
+        if self.forum.level:
+            self.parents = Forum.objects.forum_parents(self.forum.pk, True)
+        self.check_forum_type()
+        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
+        self.post = Post.objects.select_related('user').get(pk=self.kwargs.get('post'), thread=self.thread.pk)
+        self.post.thread = self.thread
+        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
+        self.request.acl.threads.allow_changelog_view(self.request.user, self.forum, self.post)
+
+    def __call__(self, request, **kwargs):
+        self.request = request
+        self.kwargs = kwargs
+        self.forum = None
+        self.thread = None
+        self.post = None
+        self.parents = []
+        try:
+            self._type_available()
+            self.fetch_target()
+            self._check_permissions()
+            if not request.user.is_authenticated():
+                raise ACLError403(_("Guest, you have to sign-in in order to see posts changelogs."))
+        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist, Change.DoesNotExist):
+            return error404(self.request)
+        except ACLError403 as e:
+            return error403(request, e)
+        except ACLError404 as e:
+            return error404(request, e)
+        return self.dispatch(request)
+
+
+class ChangelogChangesBaseView(ChangelogBaseView):
+    def dispatch(self, request, **kwargs):
+        return render_to_response('%ss/changelog.html' % self.type_prefix,
+                                  self.template_vars({
+                                      'type_prefix': self.type_prefix,
+                                      'forum': self.forum,
+                                      'parents': self.parents,
+                                      'thread': self.thread,
+                                      'post': self.post,
+                                      'edits': self.post.change_set.prefetch_related('user').order_by('-id')
+                                      }),
+                                  context_instance=RequestContext(request))
+
+
+class ChangelogDiffBaseView(ChangelogBaseView):
+    def fetch_target(self):
+        super(ChangelogDiffBaseView, self).fetch_target()
+        self.change = self.post.change_set.get(pk=self.kwargs.get('change'))
+
+    def dispatch(self, request, **kwargs):
+        try:
+            next = self.post.change_set.filter(id__gt=self.change.pk)[:1][0]
+        except IndexError:
+            next = None
+        try:
+            prev = self.post.change_set.filter(id__lt=self.change.pk).order_by('-id')[:1][0]
+        except IndexError:
+            prev = None
+        self.forum.closed = self.proxy.closed
+        return render_to_response('%ss/changelog_diff.html' % self.type_prefix,
+                                  self.template_vars({
+                                      'type_prefix': self.type_prefix,
+                                      'forum': self.forum,
+                                      'parents': self.parents,
+                                      'thread': self.thread,
+                                      'post': self.post,
+                                      'change': self.change,
+                                      'next': next,
+                                      'prev': prev,
+                                      'message': request.messages.get_message('changelog'),
+                                      'l': 1,
+                                      'diff': difflib.ndiff(self.change.post_content.splitlines(), self.post.post.splitlines()),
+                                      }),
+                                  context_instance=RequestContext(request))
+
+
+class ChangelogRevertBaseView(ChangelogDiffBaseView):
+    def fetch_target(self):
+        super(ChangelogRevertBaseView, self).fetch_target()
+        self.change = self.post.change_set.get(pk=self.kwargs.get('change'))
+        self.request.acl.threads.allow_revert(self.proxy, self.thread)
+
+    def dispatch(self, request, **kwargs):
+        if ((not self.change.thread_name_old or self.thread.name == self.change.thread_name_old)
+            and (self.change.post_content == self.post.post)):
+            messages.error(request, _("No changes to revert."), 'changelog')
+            return redirect(reverse('%s_changelog_diff' % self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'post': self.post.pk, 'change': self.change.pk}))
+
+        if self.change.thread_name_old and self.change.thread_name_old != self.thread.name:
+            self.thread.name = self.change.thread_name_old
+            self.thread.slug = slugify(self.change.thread_name_old)
+            self.thread.save(force_update=True)
+
+            if self.forum.last_thread_id == self.thread.pk:
+                self.forum.last_thread_name = self.change.thread_name_old
+                self.forum.last_thread_slug = slugify(self.change.thread_name_old)
+                self.forum.save(force_update=True)
+
+        if self.change.post_content != self.post.post:
+            self.post.post = self.change.post_content
+            md, self.post.post_preparsed = post_markdown(self.change.post_content)
+            self.post.save(force_update=True)
+
+        messages.success(request, _("Post has been reverted to state from %(date)s.") % {'date': reldate(self.change.date).lower()}, 'threads_%s' % self.post.pk)
+        return self.redirect_to_post(self.post)

+ 233 - 232
misago/apps/threadtype/delete.py

@@ -1,232 +1,233 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.messages import Message
-from misago.models import Forum, Thread, Post, Checkpoint
-from misago.apps.threadtype.base import ViewBase
-
-class DeleteHideBaseView(ViewBase):
-    def set_context(self):
-        pass
-
-    def _set_context(self):
-        self.thread = Thread.objects.get(pk=self.kwargs.get('thread'))
-        self.forum = self.thread.forum
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        if self.forum.level:
-            self.parents = Forum.objects.forum_parents(self.forum.pk)
-        self.check_forum_type()
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-
-        if self.kwargs.get('post'):
-            self.post = self.thread.post_set.get(id=self.kwargs.get('post'))
-            self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
-
-        if self.kwargs.get('checkpoint'):
-            self.checkpoint = self.thread.checkpoint_set.get(id=self.kwargs.get('checkpoint'))
-            self.request.acl.threads.allow_checkpoint_view(self.forum, self.checkpoint)
-
-        self.set_context()
-
-    def __call__(self, request, **kwargs):
-        self.request = request
-        self.kwargs = kwargs
-        self.parents = []
-        try:
-            self._type_available()
-            self._set_context()
-            self._check_permissions()
-            self.delete()
-            self.message()
-            return self.response()
-        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist, Checkpoint.DoesNotExist):
-            return error404(request)
-        except ACLError403 as e:
-            return error403(request, unicode(e))
-        except ACLError404 as e:
-            return error404(request, unicode(e))
-
-
-class DeleteThreadBaseView(DeleteHideBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_delete_thread(self.request.user, self.proxy,
-                                                     self.thread, self.thread.start_post, True)
-
-    def delete(self):
-        self.thread.delete()
-        self.forum.sync()
-        self.forum.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
-
-    def response(self):
-        return self.threads_list_redirect()
-
-
-class HideThreadBaseView(DeleteHideBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_delete_thread(self.request.user, self.proxy,
-                                                     self.thread, self.thread.start_post)
-        # Assert we are not user trying to delete thread with replies
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        if not acl['can_delete_threads']:
-            if self.thread.post_set.exclude(user_id=self.request.user.id).count() > 0:
-                raise ACLError403(_("Somebody has already replied to this thread. You cannot delete it."))
-
-    def delete(self):
-        self.thread.start_post.deleted = True
-        self.thread.start_post.save(force_update=True)
-        self.thread.set_checkpoint(self.request, 'deleted')
-        self.thread.sync()
-        self.thread.save(force_update=True)
-        self.forum.sync()
-        self.forum.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
-
-    def response(self):
-        if self.request.acl.threads.can_see_deleted_threads(self.thread.forum):
-            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-        return self.threads_list_redirect()
-
-
-class ShowThreadBaseView(DeleteHideBaseView):
-    def set_context(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        if not acl['can_delete_threads']:
-            raise ACLError403(_("You cannot undelete this thread."))
-        if not self.thread.start_post.deleted:
-            raise ACLError403(_('This thread is already visible!'))
-
-    def delete(self):
-        self.thread.start_post.deleted = False
-        self.thread.start_post.save(force_update=True)
-        self.thread.set_checkpoint(self.request, 'undeleted')
-        self.thread.sync()
-        self.thread.save(force_update=True)
-        self.forum.sync()
-        self.forum.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been restored.') % {'thread': self.thread.name}), 'success', 'threads')
-
-    def response(self):
-        if self.request.acl.threads.can_see_deleted_threads(self.thread.forum):
-            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-        return self.threads_list_redirect()
-
-
-class DeleteReplyBaseView(DeleteHideBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_delete_post(self.request.user, self.forum,
-                                                   self.thread, self.post, True)
-
-    def delete(self):
-        self.post.delete()
-        self.thread.sync()
-        self.thread.save(force_update=True)
-        self.forum.sync()
-        self.forum.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_("Selected reply has been deleted.")), 'success', 'threads')
-
-    def response(self):
-        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-
-
-class HideReplyBaseView(DeleteHideBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_delete_post(self.request.user, self.forum,
-                                                   self.thread, self.post)
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        if not acl['can_delete_posts'] and self.thread.post_set.filter(id__gt=self.post.pk).count() > 0:
-            raise ACLError403(_("Somebody has already replied to this post, you cannot delete it."))
-
-    def delete(self):
-        self.post.delete_date = timezone.now()
-        self.post.deleted = True
-        self.post.save(force_update=True)
-        self.thread.sync()
-        self.thread.save(force_update=True)
-        self.forum.sync()
-        self.forum.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_("Selected reply has been deleted.")), 'success', 'threads_%s' % self.post.pk)
-
-    def response(self):
-        return self.redirect_to_post(self.post)
-
-
-class ShowReplyBaseView(DeleteHideBaseView):
-    def set_context(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        if not acl['can_delete_posts']:
-            raise ACLError403(_("You cannot undelete this reply."))
-        if not self.post.deleted:
-            raise ACLError403(_('This reply is already visible!'))
-
-    def delete(self):
-        self.post.deleted = False
-        self.post.save(force_update=True)
-        self.thread.sync()
-        self.thread.save(force_update=True)
-        self.forum.sync()
-        self.forum.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_("Selected reply has been restored.")), 'success', 'threads_%s' % self.post.pk)
-
-    def response(self):
-        return self.redirect_to_post(self.post)
-
-
-class DeleteCheckpointBaseView(DeleteHideBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_checkpoint_delete(self.forum)
-
-    def delete(self):
-        self.checkpoint.delete()
-
-    def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been deleted.")), 'success', 'threads')
-
-    def response(self):
-        if 'retreat' in self.request.POST:
-            return redirect(self.request.POST.get('retreat'))
-        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-
-
-class HideCheckpointBaseView(DeleteCheckpointBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_checkpoint_hide(self.forum)
-        if self.checkpoint.deleted:
-            raise ACLError403(_('This checkpoint is already hidden!'))
-
-    def delete(self):
-        self.checkpoint.deleted = True
-        self.checkpoint.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been hidden.")), 'success', 'threads')
-
-
-class ShowCheckpointBaseView(DeleteCheckpointBaseView):
-    def set_context(self):
-        self.request.acl.threads.allow_checkpoint_show(self.forum)
-        if not self.checkpoint.deleted:
-            raise ACLError403(_('This checkpoint is already visible!'))
-
-    def delete(self):
-        self.checkpoint.deleted = False
-        self.checkpoint.save(force_update=True)
-
-    def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been restored.")), 'success', 'threads')
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.errors import error403, error404
+from misago.messages import Message
+from misago.models import Forum, Thread, Post, Checkpoint
+from misago.apps.threadtype.base import ViewBase
+
+class DeleteHideBaseView(ViewBase):
+    def set_context(self):
+        pass
+
+    def _set_context(self):
+        self.thread = Thread.objects.get(pk=self.kwargs.get('thread'))
+        self.forum = self.thread.forum
+        self.proxy = Forum.objects.parents_aware_forum(self.forum)
+        if self.forum.level:
+            self.parents = Forum.objects.forum_parents(self.forum.pk)
+        self.check_forum_type()
+        self.request.acl.forums.allow_forum_view(self.forum)
+        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
+
+        if self.kwargs.get('post'):
+            self.post = self.thread.post_set.get(id=self.kwargs.get('post'))
+            self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
+
+        if self.kwargs.get('checkpoint'):
+            self.checkpoint = self.thread.checkpoint_set.get(id=self.kwargs.get('checkpoint'))
+            self.request.acl.threads.allow_checkpoint_view(self.forum, self.checkpoint)
+
+        self.set_context()
+
+    def __call__(self, request, **kwargs):
+        self.request = request
+        self.kwargs = kwargs
+        self.parents = []
+        try:
+            self._type_available()
+            self._set_context()
+            self._check_permissions()
+            self.delete()
+            self.message()
+            return self.response()
+        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist, Checkpoint.DoesNotExist):
+            return error404(request)
+        except ACLError403 as e:
+            return error403(request, unicode(e))
+        except ACLError404 as e:
+            return error404(request, unicode(e))
+
+
+class DeleteThreadBaseView(DeleteHideBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_delete_thread(self.request.user, self.proxy,
+                                                     self.thread, self.thread.start_post, True)
+
+    def delete(self):
+        self.thread.delete()
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}, 'threads')
+
+    def response(self):
+        return self.threads_list_redirect()
+
+
+class HideThreadBaseView(DeleteHideBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_delete_thread(self.request.user, self.proxy,
+                                                     self.thread, self.thread.start_post)
+        # Assert we are not user trying to delete thread with replies
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        if not acl['can_delete_threads']:
+            if self.thread.post_set.exclude(user_id=self.request.user.id).count() > 0:
+                raise ACLError403(_("Somebody has already replied to this thread. You cannot delete it."))
+
+    def delete(self):
+        self.thread.start_post.deleted = True
+        self.thread.start_post.save(force_update=True)
+        self.thread.set_checkpoint(self.request, 'deleted')
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}, 'threads')
+
+    def response(self):
+        if self.request.acl.threads.can_see_deleted_threads(self.thread.forum):
+            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+        return self.threads_list_redirect()
+
+
+class ShowThreadBaseView(DeleteHideBaseView):
+    def set_context(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        if not acl['can_delete_threads']:
+            raise ACLError403(_("You cannot undelete this thread."))
+        if not self.thread.start_post.deleted:
+            raise ACLError403(_('This thread is already visible!'))
+
+    def delete(self):
+        self.thread.start_post.deleted = False
+        self.thread.start_post.save(force_update=True)
+        self.thread.set_checkpoint(self.request, 'undeleted')
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _('Thread "%(thread)s" has been restored.') % {'thread': self.thread.name}, 'threads')
+
+    def response(self):
+        if self.request.acl.threads.can_see_deleted_threads(self.thread.forum):
+            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+        return self.threads_list_redirect()
+
+
+class DeleteReplyBaseView(DeleteHideBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_delete_post(self.request.user, self.forum,
+                                                   self.thread, self.post, True)
+
+    def delete(self):
+        self.post.delete()
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _("Selected reply has been deleted."), 'threads')
+
+    def response(self):
+        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+
+
+class HideReplyBaseView(DeleteHideBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_delete_post(self.request.user, self.forum,
+                                                   self.thread, self.post)
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        if not acl['can_delete_posts'] and self.thread.post_set.filter(id__gt=self.post.pk).count() > 0:
+            raise ACLError403(_("Somebody has already replied to this post, you cannot delete it."))
+
+    def delete(self):
+        self.post.delete_date = timezone.now()
+        self.post.deleted = True
+        self.post.save(force_update=True)
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _("Selected reply has been deleted."), 'threads_%s' % self.post.pk)
+
+    def response(self):
+        return self.redirect_to_post(self.post)
+
+
+class ShowReplyBaseView(DeleteHideBaseView):
+    def set_context(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        if not acl['can_delete_posts']:
+            raise ACLError403(_("You cannot undelete this reply."))
+        if not self.post.deleted:
+            raise ACLError403(_('This reply is already visible!'))
+
+    def delete(self):
+        self.post.deleted = False
+        self.post.save(force_update=True)
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _("Selected reply has been restored."), 'threads_%s' % self.post.pk)
+
+    def response(self):
+        return self.redirect_to_post(self.post)
+
+
+class DeleteCheckpointBaseView(DeleteHideBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_checkpoint_delete(self.forum)
+
+    def delete(self):
+        self.checkpoint.delete()
+
+    def message(self):
+        messages.success(self.request, _("Selected checkpoint has been deleted."), 'threads')
+
+    def response(self):
+        if 'retreat' in self.request.POST:
+            return redirect(self.request.POST.get('retreat'))
+        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+
+
+class HideCheckpointBaseView(DeleteCheckpointBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_checkpoint_hide(self.forum)
+        if self.checkpoint.deleted:
+            raise ACLError403(_('This checkpoint is already hidden!'))
+
+    def delete(self):
+        self.checkpoint.deleted = True
+        self.checkpoint.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _("Selected checkpoint has been hidden."), 'threads')
+
+
+class ShowCheckpointBaseView(DeleteCheckpointBaseView):
+    def set_context(self):
+        self.request.acl.threads.allow_checkpoint_show(self.forum)
+        if not self.checkpoint.deleted:
+            raise ACLError403(_('This checkpoint is already visible!'))
+
+    def delete(self):
+        self.checkpoint.deleted = False
+        self.checkpoint.save(force_update=True)
+
+    def message(self):
+        messages.success(self.request, _("Selected checkpoint has been restored."), 'threads')

+ 369 - 368
misago/apps/threadtype/jumps.py

@@ -1,369 +1,370 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.conf import settings
-from misago.decorators import block_guest, check_csrf
-from misago.markdown import post_markdown
-from misago.messages import Message
-from misago.models import Forum, Checkpoint, Thread, Post, Karma, WatchedThread
-from misago.monitor import monitor, UpdatingMonitor
-from misago.readstrackers import ThreadsTracker
-from misago.utils.strings import short_string, slugify
-from misago.utils.views import json_response
-from misago.apps.threadtype.base import ViewBase
-
-class JumpView(ViewBase):
-    def fetch_thread(self, thread):
-        self.thread = Thread.objects.get(pk=thread)
-        self.forum = self.thread.forum
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-
-    def fetch_post(self, post):
-        self.post = self.thread.post_set.get(pk=post)
-        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
-
-    def make_jump(self):
-        raise NotImplementedError('JumpView cannot be called directly.')
-
-    def __call__(self, request, slug=None, thread=None, post=None):
-        self.request = request
-        self.parents = []
-        try:
-            self._type_available()
-            self.fetch_thread(thread)
-            if self.forum.level:
-                self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-            self.check_forum_type()
-            self._check_permissions()
-            if post:
-                self.fetch_post(post)
-            return self.make_jump()
-        except (Thread.DoesNotExist, Post.DoesNotExist):
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e)
-        except ACLError404 as e:
-            return error404(request, e)
-
-
-class LastReplyBaseView(JumpView):
-    def make_jump(self):
-        return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
-
-
-class FindReplyBaseView(JumpView):
-    def make_jump(self):
-        return self.redirect_to_post(self.post)
-
-
-class NewReplyBaseView(JumpView):
-    def make_jump(self):
-        if not self.request.user.is_authenticated():
-            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
-        tracker = ThreadsTracker(self.request, self.forum)
-        read_date = tracker.read_date(self.thread)
-        post = self.thread.post_set.filter(date__gt=read_date).order_by('id')[:1]
-        if not post:
-            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
-        return self.redirect_to_post(post[0])
-
-
-class FirstModeratedBaseView(JumpView):
-    def make_jump(self):
-        if not self.request.acl.threads.can_approve(self.forum):
-            raise ACLError404()
-        try:
-            return self.redirect_to_post(
-                self.thread.post_set.get(moderated=True))
-        except Post.DoesNotExist:
-            return error404(self.request)
-
-
-class FirstReportedBaseView(JumpView):
-    def make_jump(self):
-        if not self.request.acl.threads.can_mod_posts(self.forum):
-            raise ACLError404()
-        try:
-            return self.redirect_to_post(
-                self.thread.post_set.get(reported=True))
-        except Post.DoesNotExist:
-            return error404(self.request)
-
-
-class ShowHiddenRepliesBaseView(JumpView):
-    def make_jump(self):
-        @block_guest
-        @check_csrf
-        def view(request):
-            ignored_exclusions = request.session.get('unignore_threads', [])
-            ignored_exclusions.append(self.thread.pk)
-            request.session['unignore_threads'] = ignored_exclusions
-            request.messages.set_flash(Message(_('Replies made to this thread by members on your ignore list have been revealed.')), 'success', 'threads')
-            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-        return view(self.request)
-
-
-class WatchThreadBaseView(JumpView):
-    def get_retreat(self):
-        return redirect(self.request.POST.get('retreat', reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug})))
-
-    def update_watcher(self, request, watcher):
-        request.messages.set_flash(Message(_('This thread has been added to your watched threads list.')), 'success', 'threads')
-
-    def make_jump(self):
-        @block_guest
-        @check_csrf
-        def view(request):
-            try:
-                watcher = WatchedThread.objects.get(user=request.user, thread=self.thread)
-            except WatchedThread.DoesNotExist:
-                watcher = WatchedThread()
-                watcher.user = request.user
-                watcher.forum = self.forum
-                watcher.thread = self.thread
-                watcher.starter_id = self.thread.start_poster_id
-                watcher.last_read = timezone.now()
-            self.update_watcher(request, watcher)
-            if watcher.pk:
-                watcher.save(force_update=True)
-            else:
-                watcher.save(force_insert=True)
-            return self.get_retreat()
-        return view(self.request)
-
-
-class WatchEmailThreadBaseView(WatchThreadBaseView):
-    def update_watcher(self, request, watcher):
-        watcher.email = True
-        if watcher.pk:
-            request.messages.set_flash(Message(_('You will now receive e-mail with notification when somebody replies to this thread.')), 'success', 'threads')
-        else:
-            request.messages.set_flash(Message(_('This thread has been added to your watched threads list. You will also receive e-mail with notification when somebody replies to it.')), 'success', 'threads')
-
-
-class UnwatchThreadBaseView(WatchThreadBaseView):
-    def update_watcher(self, request, watcher):
-        watcher.deleted = True
-        watcher.delete()
-        if watcher.email:
-            request.messages.set_flash(Message(_('This thread has been removed from your watched threads list. You will no longer receive e-mails with notifications when somebody replies to it.')), 'success', 'threads')
-        else:
-            request.messages.set_flash(Message(_('This thread has been removed from your watched threads list.')), 'success', 'threads')
-
-
-class UnwatchEmailThreadBaseView(WatchThreadBaseView):
-    def update_watcher(self, request, watcher):
-        watcher.email = False
-        request.messages.set_flash(Message(_('You will no longer receive e-mails with notifications when somebody replies to this thread.')), 'success', 'threads')
-
-
-class UpvotePostBaseView(JumpView):
-    def make_jump(self):
-        @block_guest
-        @check_csrf
-        def view(request):
-            if self.post.user_id == request.user.id:
-                return error404(request)
-            self.check_acl(request)
-            try:
-                vote = Karma.objects.get(user=request.user, post=self.post)
-                if self.thread.start_post_id == self.post.pk:
-                    if vote.score > 0:
-                        self.thread.upvotes -= 1
-                    else:
-                        self.thread.downvotes -= 1
-                if vote.score > 0:
-                    self.post.upvotes -= 1
-                    request.user.karma_given_p -= 1
-                    if self.post.user_id:
-                        self.post.user.karma_p -= 1
-                else:
-                    self.post.downvotes -= 1
-                    request.user.karma_given_n -= 1
-                    if self.post.user_id:
-                        self.post.user.karma_n -= 1
-            except Karma.DoesNotExist:
-                vote = Karma()
-            vote.forum = self.forum
-            vote.thread = self.thread
-            vote.post = self.post
-            vote.user = request.user
-            vote.user_name = request.user.username
-            vote.user_slug = request.user.username_slug
-            vote.date = timezone.now()
-            vote.ip = request.session.get_ip(request)
-            vote.agent = request.META.get('HTTP_USER_AGENT')
-            self.make_vote(request, vote)
-            if vote.pk:
-                vote.save(force_update=True)
-            else:
-                vote.save(force_insert=True)
-            if self.thread.start_post_id == self.post.pk:
-                if vote.score > 0:
-                    self.thread.upvotes += 1
-                else:
-                    self.thread.downvotes += 1
-                self.thread.save(force_update=True)
-            if vote.score > 0:
-                self.post.upvotes += 1
-                request.user.karma_given_p += 1
-                if self.post.user_id:
-                    self.post.user.karma_p += 1
-                    self.post.user.score += settings.score_reward_karma_positive
-            else:
-                self.post.downvotes += 1
-                request.user.karma_given_n += 1
-                if self.post.user_id:
-                    self.post.user.karma_n += 1
-                    self.post.user.score -= settings.score_reward_karma_negative
-            self.post.save(force_update=True)
-            request.user.save(force_update=True)
-            if self.post.user_id:
-                self.post.user.save(force_update=True)
-            if request.is_ajax():
-                return json_response(request, {
-                                               'score_total': self.post.upvotes - self.post.downvotes,
-                                               'score_upvotes': self.post.upvotes,
-                                               'score_downvotes': self.post.downvotes,
-                                               'user_vote': vote.score,
-                                              })
-            request.messages.set_flash(Message(_('Your vote has been saved.')), 'success', 'threads_%s' % self.post.pk)
-            return self.redirect_to_post(self.post)
-        return view(self.request)
-    
-    def check_acl(self, request):
-        request.acl.threads.allow_post_upvote(self.forum)
-    
-    def make_vote(self, request, vote):
-        vote.score = 1
-
-
-class DownvotePostBaseView(UpvotePostBaseView):
-    def check_acl(self, request):
-        request.acl.threads.allow_post_downvote(self.forum)
-    
-    def make_vote(self, request, vote):
-        vote.score = -1
-
-
-class ReportPostBaseView(JumpView):
-    def make_jump(self):
-        self.request.acl.reports.allow_report()
-
-        @block_guest
-        @check_csrf
-        def view(request):
-            report = None
-            made_report = False
-            if self.post.reported:
-                report = self.post.live_report()
-
-                if report and report.start_poster_id != request.user.pk:
-                    # Append our q.q to existing report?
-                    try:
-                        report.checkpoint_set.get(user=request.user, action="reported")
-                    except Checkpoint.DoesNotExist:
-                        report.set_checkpoint(self.request, 'reported', user)
-                        self.post.add_reporter(self.request.user)
-                        self.post.save(force_update=True)
-                    made_report = True
-
-            if not report:
-                # File up new report
-                now = timezone.now()
-
-                reason_post = _('''
-Member @%(reporter)s has reported following post by @%(reported)s:
-
-%(quote)s
-**Post link:** <%(post)s>
-''')
-
-                reason_post = reason_post.strip() % {
-                                             'reporter': request.user.username,
-                                             'reported': self.post.user_name,
-                                             'post': settings.BOARD_ADDRESS + self.redirect_to_post(self.post)['Location'],
-                                             'quote': self.post.quote(),
-                                            }
-
-                md, reason_post_preparsed = post_markdown(reason_post)
-
-                reports = Forum.objects.special_model('reports')
-                report = Thread.objects.create(
-                                               forum=reports,
-                                               weight=2,
-                                               name=self.thread.name,
-                                               slug=slugify(self.thread.slug),
-                                               start=now,
-                                               start_poster=request.user,
-                                               start_poster_name=request.user.username,
-                                               start_poster_slug=request.user.username_slug,
-                                               start_poster_style=request.user.rank.style,
-                                               last=now,
-                                               last_poster=request.user,
-                                               last_poster_name=request.user.username,
-                                               last_poster_slug=request.user.username_slug,
-                                               last_poster_style=request.user.rank.style,
-                                               report_for=self.post,
-                                               )
-
-                reason = Post.objects.create(
-                                             forum=reports,
-                                             thread=report,
-                                             user=request.user,
-                                             user_name=request.user.username,
-                                             ip=request.session.get_ip(self.request),
-                                             agent=request.META.get('HTTP_USER_AGENT'),
-                                             post=reason_post,
-                                             post_preparsed=reason_post_preparsed,
-                                             date=now,
-                                             )
-
-                report.start_post = reason
-                report.last_post = reason
-                report.save(force_update=True)
-
-                for m in self.post.mentions.all():
-                    reason.mentions.add(m)
-
-                self.post.reported = True
-                self.post.add_reporter(self.request.user)
-                self.post.save(force_update=True)
-                self.thread.replies_reported += 1
-                self.thread.save(force_update=True)
-                with UpdatingMonitor() as cm:
-                    monitor.increase('reported_posts')
-                made_report = True
-
-            if made_report:
-                if request.is_ajax():
-                    return json_response(request, message=_("Selected post has been reported and will receive moderator attention. Thank you."))
-                self.request.messages.set_flash(Message(_("Selected post has been reported and will receive moderator attention. Thank you.")), 'info', 'threads_%s' % self.post.pk)
-            else:
-                if request.is_ajax():
-                    return json_response(request, message=_("You have already reported this post. One of moderators will handle it as soon as it is possible. Thank you for your patience."))
-                self.request.messages.set_flash(Message(_("You have already reported this post. One of moderators will handle it as soon as it is possible. Thank you for your patience.")), 'info', 'threads_%s' % self.post.pk)
-
-            return self.redirect_to_post(self.post)
-        return view(self.request)
-
-
-class ShowPostReportBaseView(JumpView):
-    def make_jump(self):
-        self.request.acl.reports.allow_report()
-
-        @block_guest
-        def view(request):
-            if not self.post.reported:
-                return error404(request)
-            reports = Forum.objects.special_model('reports')
-            self.request.acl.forums.allow_forum_view(reports)
-            report = self.post.live_report()
-            if not report:
-                return error404(request)
-            return redirect(reverse('report', kwargs={'thread': report.pk, 'slug': report.slug}))
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.errors import error403, error404
+from misago.conf import settings
+from misago.decorators import block_guest, check_csrf
+from misago.markdown import post_markdown
+from misago.messages import Message
+from misago.models import Forum, Checkpoint, Thread, Post, Karma, WatchedThread
+from misago.monitor import monitor, UpdatingMonitor
+from misago.readstrackers import ThreadsTracker
+from misago.utils.strings import short_string, slugify
+from misago.utils.views import json_response
+from misago.apps.threadtype.base import ViewBase
+
+class JumpView(ViewBase):
+    def fetch_thread(self, thread):
+        self.thread = Thread.objects.get(pk=thread)
+        self.forum = self.thread.forum
+        self.request.acl.forums.allow_forum_view(self.forum)
+        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
+
+    def fetch_post(self, post):
+        self.post = self.thread.post_set.get(pk=post)
+        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
+
+    def make_jump(self):
+        raise NotImplementedError('JumpView cannot be called directly.')
+
+    def __call__(self, request, slug=None, thread=None, post=None):
+        self.request = request
+        self.parents = []
+        try:
+            self._type_available()
+            self.fetch_thread(thread)
+            if self.forum.level:
+                self.parents = Forum.objects.forum_parents(self.forum.pk, True)
+            self.check_forum_type()
+            self._check_permissions()
+            if post:
+                self.fetch_post(post)
+            return self.make_jump()
+        except (Thread.DoesNotExist, Post.DoesNotExist):
+            return error404(self.request)
+        except ACLError403 as e:
+            return error403(request, e)
+        except ACLError404 as e:
+            return error404(request, e)
+
+
+class LastReplyBaseView(JumpView):
+    def make_jump(self):
+        return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+
+
+class FindReplyBaseView(JumpView):
+    def make_jump(self):
+        return self.redirect_to_post(self.post)
+
+
+class NewReplyBaseView(JumpView):
+    def make_jump(self):
+        if not self.request.user.is_authenticated():
+            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+        tracker = ThreadsTracker(self.request, self.forum)
+        read_date = tracker.read_date(self.thread)
+        post = self.thread.post_set.filter(date__gt=read_date).order_by('id')[:1]
+        if not post:
+            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+        return self.redirect_to_post(post[0])
+
+
+class FirstModeratedBaseView(JumpView):
+    def make_jump(self):
+        if not self.request.acl.threads.can_approve(self.forum):
+            raise ACLError404()
+        try:
+            return self.redirect_to_post(
+                self.thread.post_set.get(moderated=True))
+        except Post.DoesNotExist:
+            return error404(self.request)
+
+
+class FirstReportedBaseView(JumpView):
+    def make_jump(self):
+        if not self.request.acl.threads.can_mod_posts(self.forum):
+            raise ACLError404()
+        try:
+            return self.redirect_to_post(
+                self.thread.post_set.get(reported=True))
+        except Post.DoesNotExist:
+            return error404(self.request)
+
+
+class ShowHiddenRepliesBaseView(JumpView):
+    def make_jump(self):
+        @block_guest
+        @check_csrf
+        def view(request):
+            ignored_exclusions = request.session.get('unignore_threads', [])
+            ignored_exclusions.append(self.thread.pk)
+            request.session['unignore_threads'] = ignored_exclusions
+            messages.success(request, _('Replies made to this thread by members on your ignore list have been revealed.'), 'threads')
+            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+        return view(self.request)
+
+
+class WatchThreadBaseView(JumpView):
+    def get_retreat(self):
+        return redirect(self.request.POST.get('retreat', reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug})))
+
+    def update_watcher(self, request, watcher):
+        messages.success(request, _('This thread has been added to your watched threads list.'), 'threads')
+
+    def make_jump(self):
+        @block_guest
+        @check_csrf
+        def view(request):
+            try:
+                watcher = WatchedThread.objects.get(user=request.user, thread=self.thread)
+            except WatchedThread.DoesNotExist:
+                watcher = WatchedThread()
+                watcher.user = request.user
+                watcher.forum = self.forum
+                watcher.thread = self.thread
+                watcher.starter_id = self.thread.start_poster_id
+                watcher.last_read = timezone.now()
+            self.update_watcher(request, watcher)
+            if watcher.pk:
+                watcher.save(force_update=True)
+            else:
+                watcher.save(force_insert=True)
+            return self.get_retreat()
+        return view(self.request)
+
+
+class WatchEmailThreadBaseView(WatchThreadBaseView):
+    def update_watcher(self, request, watcher):
+        watcher.email = True
+        if watcher.pk:
+            messages.success(request, _('You will now receive e-mail with notification when somebody replies to this thread.'), 'threads')
+        else:
+            messages.success(request, _('This thread has been added to your watched threads list. You will also receive e-mail with notification when somebody replies to it.'), 'threads')
+
+
+class UnwatchThreadBaseView(WatchThreadBaseView):
+    def update_watcher(self, request, watcher):
+        watcher.deleted = True
+        watcher.delete()
+        if watcher.email:
+            messages.success(request, _('This thread has been removed from your watched threads list. You will no longer receive e-mails with notifications when somebody replies to it.'), 'threads')
+        else:
+            messages.success(request, _('This thread has been removed from your watched threads list.'), 'threads')
+
+
+class UnwatchEmailThreadBaseView(WatchThreadBaseView):
+    def update_watcher(self, request, watcher):
+        watcher.email = False
+        messages.success(request, _('You will no longer receive e-mails with notifications when somebody replies to this thread.'), 'threads')
+
+
+class UpvotePostBaseView(JumpView):
+    def make_jump(self):
+        @block_guest
+        @check_csrf
+        def view(request):
+            if self.post.user_id == request.user.id:
+                return error404(request)
+            self.check_acl(request)
+            try:
+                vote = Karma.objects.get(user=request.user, post=self.post)
+                if self.thread.start_post_id == self.post.pk:
+                    if vote.score > 0:
+                        self.thread.upvotes -= 1
+                    else:
+                        self.thread.downvotes -= 1
+                if vote.score > 0:
+                    self.post.upvotes -= 1
+                    request.user.karma_given_p -= 1
+                    if self.post.user_id:
+                        self.post.user.karma_p -= 1
+                else:
+                    self.post.downvotes -= 1
+                    request.user.karma_given_n -= 1
+                    if self.post.user_id:
+                        self.post.user.karma_n -= 1
+            except Karma.DoesNotExist:
+                vote = Karma()
+            vote.forum = self.forum
+            vote.thread = self.thread
+            vote.post = self.post
+            vote.user = request.user
+            vote.user_name = request.user.username
+            vote.user_slug = request.user.username_slug
+            vote.date = timezone.now()
+            vote.ip = request.session.get_ip(request)
+            vote.agent = request.META.get('HTTP_USER_AGENT')
+            self.make_vote(request, vote)
+            if vote.pk:
+                vote.save(force_update=True)
+            else:
+                vote.save(force_insert=True)
+            if self.thread.start_post_id == self.post.pk:
+                if vote.score > 0:
+                    self.thread.upvotes += 1
+                else:
+                    self.thread.downvotes += 1
+                self.thread.save(force_update=True)
+            if vote.score > 0:
+                self.post.upvotes += 1
+                request.user.karma_given_p += 1
+                if self.post.user_id:
+                    self.post.user.karma_p += 1
+                    self.post.user.score += settings.score_reward_karma_positive
+            else:
+                self.post.downvotes += 1
+                request.user.karma_given_n += 1
+                if self.post.user_id:
+                    self.post.user.karma_n += 1
+                    self.post.user.score -= settings.score_reward_karma_negative
+            self.post.save(force_update=True)
+            request.user.save(force_update=True)
+            if self.post.user_id:
+                self.post.user.save(force_update=True)
+            if request.is_ajax():
+                return json_response(request, {
+                                               'score_total': self.post.upvotes - self.post.downvotes,
+                                               'score_upvotes': self.post.upvotes,
+                                               'score_downvotes': self.post.downvotes,
+                                               'user_vote': vote.score,
+                                              })
+            messages.success(request, _('Your vote has been saved.'), 'threads_%s' % self.post.pk)
+            return self.redirect_to_post(self.post)
+        return view(self.request)
+
+    def check_acl(self, request):
+        request.acl.threads.allow_post_upvote(self.forum)
+
+    def make_vote(self, request, vote):
+        vote.score = 1
+
+
+class DownvotePostBaseView(UpvotePostBaseView):
+    def check_acl(self, request):
+        request.acl.threads.allow_post_downvote(self.forum)
+
+    def make_vote(self, request, vote):
+        vote.score = -1
+
+
+class ReportPostBaseView(JumpView):
+    def make_jump(self):
+        self.request.acl.reports.allow_report()
+
+        @block_guest
+        @check_csrf
+        def view(request):
+            report = None
+            made_report = False
+            if self.post.reported:
+                report = self.post.live_report()
+
+                if report and report.start_poster_id != request.user.pk:
+                    # Append our q.q to existing report?
+                    try:
+                        report.checkpoint_set.get(user=request.user, action="reported")
+                    except Checkpoint.DoesNotExist:
+                        report.set_checkpoint(self.request, 'reported', user)
+                        self.post.add_reporter(self.request.user)
+                        self.post.save(force_update=True)
+                    made_report = True
+
+            if not report:
+                # File up new report
+                now = timezone.now()
+
+                reason_post = _('''
+Member @%(reporter)s has reported following post by @%(reported)s:
+
+%(quote)s
+**Post link:** <%(post)s>
+''')
+
+                reason_post = reason_post.strip() % {
+                                             'reporter': request.user.username,
+                                             'reported': self.post.user_name,
+                                             'post': settings.BOARD_ADDRESS + self.redirect_to_post(self.post)['Location'],
+                                             'quote': self.post.quote(),
+                                            }
+
+                md, reason_post_preparsed = post_markdown(reason_post)
+
+                reports = Forum.objects.special_model('reports')
+                report = Thread.objects.create(
+                                               forum=reports,
+                                               weight=2,
+                                               name=self.thread.name,
+                                               slug=slugify(self.thread.slug),
+                                               start=now,
+                                               start_poster=request.user,
+                                               start_poster_name=request.user.username,
+                                               start_poster_slug=request.user.username_slug,
+                                               start_poster_style=request.user.rank.style,
+                                               last=now,
+                                               last_poster=request.user,
+                                               last_poster_name=request.user.username,
+                                               last_poster_slug=request.user.username_slug,
+                                               last_poster_style=request.user.rank.style,
+                                               report_for=self.post,
+                                               )
+
+                reason = Post.objects.create(
+                                             forum=reports,
+                                             thread=report,
+                                             user=request.user,
+                                             user_name=request.user.username,
+                                             ip=request.session.get_ip(self.request),
+                                             agent=request.META.get('HTTP_USER_AGENT'),
+                                             post=reason_post,
+                                             post_preparsed=reason_post_preparsed,
+                                             date=now,
+                                             )
+
+                report.start_post = reason
+                report.last_post = reason
+                report.save(force_update=True)
+
+                for m in self.post.mentions.all():
+                    reason.mentions.add(m)
+
+                self.post.reported = True
+                self.post.add_reporter(self.request.user)
+                self.post.save(force_update=True)
+                self.thread.replies_reported += 1
+                self.thread.save(force_update=True)
+                with UpdatingMonitor() as cm:
+                    monitor.increase('reported_posts')
+                made_report = True
+
+            if made_report:
+                if request.is_ajax():
+                    return json_response(request, message=_("Selected post has been reported and will receive moderator attention. Thank you."))
+                messages.info(request, _("Selected post has been reported and will receive moderator attention. Thank you."), 'threads_%s' % self.post.pk)
+            else:
+                if request.is_ajax():
+                    return json_response(request, message=_("You have already reported this post. One of moderators will handle it as soon as it is possible. Thank you for your patience."))
+                messages.info(request, _("You have already reported this post. One of moderators will handle it as soon as it is possible. Thank you for your patience."), 'threads_%s' % self.post.pk)
+
+            return self.redirect_to_post(self.post)
+        return view(self.request)
+
+
+class ShowPostReportBaseView(JumpView):
+    def make_jump(self):
+        self.request.acl.reports.allow_report()
+
+        @block_guest
+        def view(request):
+            if not self.post.reported:
+                return error404(request)
+            reports = Forum.objects.special_model('reports')
+            self.request.acl.forums.allow_forum_view(reports)
+            report = self.post.live_report()
+            if not report:
+                return error404(request)
+            return redirect(reverse('report', kwargs={'thread': report.pk, 'slug': report.slug}))
         return view(self.request)
         return view(self.request)

+ 24 - 23
misago/apps/threadtype/list/moderation.py

@@ -2,6 +2,7 @@ from django.forms import ValidationError
 from django.template import RequestContext
 from django.template import RequestContext
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from misago import messages
 from misago.messages import Message
 from misago.messages import Message
 from misago.models import Forum, Thread, Post
 from misago.models import Forum, Thread, Post
 from misago.monitor import monitor, UpdatingMonitor
 from misago.monitor import monitor, UpdatingMonitor
@@ -12,9 +13,9 @@ from misago.utils.strings import slugify
 class ThreadsListModeration(object):
 class ThreadsListModeration(object):
     def action_accept(self, ids):
     def action_accept(self, ids):
         if self._action_accept(ids):
         if self._action_accept(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been marked as reviewed and made visible to other members.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been marked as reviewed and made visible to other members.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were marked as reviewed.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were marked as reviewed.'), 'threads')
 
 
     def _action_accept(self, ids):
     def _action_accept(self, ids):
         accepted = 0
         accepted = 0
@@ -47,9 +48,9 @@ class ThreadsListModeration(object):
 
 
     def action_annouce(self, ids):
     def action_annouce(self, ids):
         if self._action_annouce(ids):
         if self._action_annouce(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been turned into announcements.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been turned into announcements.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were turned into announcements.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were turned into announcements.'), 'threads')
 
 
     def _action_annouce(self, ids):
     def _action_annouce(self, ids):
         acl = self.request.acl.threads.get_role(self.forum)
         acl = self.request.acl.threads.get_role(self.forum)
@@ -63,9 +64,9 @@ class ThreadsListModeration(object):
 
 
     def action_sticky(self, ids):
     def action_sticky(self, ids):
         if self._action_sticky(ids):
         if self._action_sticky(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been sticked to the top of list.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been sticked to the top of list.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were turned into stickies.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were turned into stickies.'), 'threads')
 
 
     def _action_sticky(self, ids):
     def _action_sticky(self, ids):
         acl = self.request.acl.threads.get_role(self.forum)
         acl = self.request.acl.threads.get_role(self.forum)
@@ -79,9 +80,9 @@ class ThreadsListModeration(object):
 
 
     def action_normal(self, ids):
     def action_normal(self, ids):
         if self._action_normal(ids):
         if self._action_normal(ids):
-            self.request.messages.set_flash(Message(_('Selected threads weight has been removed.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads weight has been removed.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads have had their weight removed.')), 'info', 'threads')
+            messages.info(self.request, _('No threads have had their weight removed.'), 'threads')
 
 
     def _action_normal(self, ids):
     def _action_normal(self, ids):
         normalised = []
         normalised = []
@@ -110,7 +111,7 @@ class ThreadsListModeration(object):
                 new_forum.save(force_update=True)
                 new_forum.save(force_update=True)
                 self.forum.sync()
                 self.forum.sync()
                 self.forum.save(force_update=True)
                 self.forum.save(force_update=True)
-                self.request.messages.set_flash(Message(_('Selected threads have been moved to "%(forum)s".') % {'forum': new_forum.name}), 'success', 'threads')
+                messages.success(self.request, _('Selected threads have been moved to "%(forum)s".') % {'forum': new_forum.name}, 'threads')
                 return None
                 return None
             self.message = Message(form.non_field_errors()[0], 'error')
             self.message = Message(form.non_field_errors()[0], 'error')
         else:
         else:
@@ -156,7 +157,7 @@ class ThreadsListModeration(object):
                 if form.cleaned_data['new_forum'].pk != self.forum.pk:
                 if form.cleaned_data['new_forum'].pk != self.forum.pk:
                     form.cleaned_data['new_forum'].sync()
                     form.cleaned_data['new_forum'].sync()
                     form.cleaned_data['new_forum'].save(force_update=True)
                     form.cleaned_data['new_forum'].save(force_update=True)
-                self.request.messages.set_flash(Message(_('Selected threads have been merged into new one.')), 'success', 'threads')
+                messages.success(self.request, _('Selected threads have been merged into new one.'), 'threads')
                 return None
                 return None
             self.message = Message(form.non_field_errors()[0], 'error')
             self.message = Message(form.non_field_errors()[0], 'error')
         else:
         else:
@@ -185,11 +186,11 @@ class ThreadsListModeration(object):
 
 
     def action_open(self, ids):
     def action_open(self, ids):
         if self._action_open(ids):
         if self._action_open(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been opened.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been opened.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were opened.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were opened.'), 'threads')
 
 
-    def _action_open(self, ids):        
+    def _action_open(self, ids):
         opened = []
         opened = []
         for thread in self.threads:
         for thread in self.threads:
             if thread.pk in ids and thread.closed:
             if thread.pk in ids and thread.closed:
@@ -201,9 +202,9 @@ class ThreadsListModeration(object):
 
 
     def action_close(self, ids):
     def action_close(self, ids):
         if self._action_close(ids):
         if self._action_close(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been closed.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been closed.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were closed.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were closed.'), 'threads')
 
 
     def _action_close(self, ids):
     def _action_close(self, ids):
         closed = []
         closed = []
@@ -217,9 +218,9 @@ class ThreadsListModeration(object):
 
 
     def action_undelete(self, ids):
     def action_undelete(self, ids):
         if self._action_undelete(ids):
         if self._action_undelete(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been restored.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been restored.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were restored.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were restored.'), 'threads')
 
 
     def _action_undelete(self, ids):
     def _action_undelete(self, ids):
         undeleted = []
         undeleted = []
@@ -239,9 +240,9 @@ class ThreadsListModeration(object):
 
 
     def action_soft(self, ids):
     def action_soft(self, ids):
         if self._action_soft(ids):
         if self._action_soft(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been hidden.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been hidden.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were hidden.')), 'info', 'threads')
+            messages.info(self.request, _('No threads were hidden.'), 'threads')
 
 
     def _action_soft(self, ids):
     def _action_soft(self, ids):
         deleted = []
         deleted = []
@@ -261,11 +262,11 @@ class ThreadsListModeration(object):
 
 
     def action_hard(self, ids):
     def action_hard(self, ids):
         if self._action_hard(ids):
         if self._action_hard(ids):
-            self.request.messages.set_flash(Message(_('Selected threads have been deleted.')), 'success', 'threads')
+            messages.success(self.request, _('Selected threads have been deleted.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No threads were deleted.')), 'info', 'threads')
-    
-    def _action_hard(self, ids):        
+            messages.info(self.request, _('No threads were deleted.'), 'threads')
+
+    def _action_hard(self, ids):
         deleted = []
         deleted = []
         for thread in self.threads:
         for thread in self.threads:
             if thread.pk in ids:
             if thread.pk in ids:

+ 16 - 15
misago/apps/threadtype/thread/moderation/posts.py

@@ -4,6 +4,7 @@ from django.template import RequestContext
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 import floppyforms as forms
 import floppyforms as forms
+from misago import messages
 from misago.markdown import post_markdown
 from misago.markdown import post_markdown
 from misago.messages import Message
 from misago.messages import Message
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response
@@ -20,9 +21,9 @@ class PostsModeration(object):
             self.thread.post_set.filter(id__in=ids).update(moderated=False)
             self.thread.post_set.filter(id__in=ids).update(moderated=False)
             self.thread.sync()
             self.thread.sync()
             self.thread.save(force_update=True)
             self.thread.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been accepted and made visible to other members.')), 'success', 'threads')
+            messages.success(self.request, _('Selected posts have been accepted and made visible to other members.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No posts were accepted.')), 'info', 'threads')
+            messages.info(self.request, _('No posts were accepted.'), 'threads')
 
 
     def post_action_merge(self, ids):
     def post_action_merge(self, ids):
         users = []
         users = []
@@ -46,7 +47,7 @@ class PostsModeration(object):
         self.thread.save(force_update=True)
         self.thread.save(force_update=True)
         self.forum.sync()
         self.forum.sync()
         self.forum.save(force_update=True)
         self.forum.save(force_update=True)
-        self.request.messages.set_flash(Message(_('Selected posts have been merged into one message.')), 'success', 'threads')
+        messages.success(self.request, _('Selected posts have been merged into one message.'), 'threads')
 
 
     def post_action_split(self, ids):
     def post_action_split(self, ids):
         for id in ids:
         for id in ids:
@@ -80,7 +81,7 @@ class PostsModeration(object):
                 if new_thread.forum != self.forum:
                 if new_thread.forum != self.forum:
                     new_thread.forum.sync()
                     new_thread.forum.sync()
                     new_thread.forum.save(force_update=True)
                     new_thread.forum.save(force_update=True)
-                self.request.messages.set_flash(Message(_("Selected posts have been split to new thread.")), 'success', 'threads')
+                messages.success(self.request, _("Selected posts have been split to new thread."), 'threads')
                 return redirect(reverse(self.type_prefix, kwargs={'thread': new_thread.pk, 'slug': new_thread.slug}))
                 return redirect(reverse(self.type_prefix, kwargs={'thread': new_thread.pk, 'slug': new_thread.slug}))
             message = Message(form.non_field_errors()[0], 'error')
             message = Message(form.non_field_errors()[0], 'error')
         else:
         else:
@@ -122,7 +123,7 @@ class PostsModeration(object):
                 if self.forum.pk != thread.forum.pk:
                 if self.forum.pk != thread.forum.pk:
                     self.forum.sync()
                     self.forum.sync()
                     self.forum.save(force_update=True)
                     self.forum.save(force_update=True)
-                self.request.messages.set_flash(Message(_("Selected posts have been moved to new thread.")), 'success', 'threads')
+                messages.success(self.request, _("Selected posts have been moved to new thread."), 'threads')
                 return redirect(reverse(self.type_prefix, kwargs={'thread': thread.pk, 'slug': thread.slug}))
                 return redirect(reverse(self.type_prefix, kwargs={'thread': thread.pk, 'slug': thread.slug}))
             message = Message(form.non_field_errors()[0], 'error')
             message = Message(form.non_field_errors()[0], 'error')
         else:
         else:
@@ -146,9 +147,9 @@ class PostsModeration(object):
                 protected += 1
                 protected += 1
         if protected:
         if protected:
             self.thread.post_set.filter(id__in=ids).update(protected=True)
             self.thread.post_set.filter(id__in=ids).update(protected=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been protected from edition.')), 'success', 'threads')
+            messages.success(self.request, _('Selected posts have been protected from edition.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No posts were protected.')), 'info', 'threads')
+            messages.info(self.request, _('No posts were protected.'), 'threads')
 
 
     def post_action_unprotect(self, ids):
     def post_action_unprotect(self, ids):
         unprotected = 0
         unprotected = 0
@@ -157,9 +158,9 @@ class PostsModeration(object):
                 unprotected += 1
                 unprotected += 1
         if unprotected:
         if unprotected:
             self.thread.post_set.filter(id__in=ids).update(protected=False)
             self.thread.post_set.filter(id__in=ids).update(protected=False)
-            self.request.messages.set_flash(Message(_('Protection from editions has been removed from selected posts.')), 'success', 'threads')
+            messages.success(self.request, _('Protection from editions has been removed from selected posts.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No posts were unprotected.')), 'info', 'threads')
+            messages.info(self.request, _('No posts were unprotected.'), 'threads')
 
 
     def post_action_undelete(self, ids):
     def post_action_undelete(self, ids):
         undeleted = []
         undeleted = []
@@ -172,9 +173,9 @@ class PostsModeration(object):
             self.thread.save(force_update=True)
             self.thread.save(force_update=True)
             self.forum.sync()
             self.forum.sync()
             self.forum.save(force_update=True)
             self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been restored.')), 'success', 'threads')
+            messages.success(self.request, _('Selected posts have been restored.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No posts were restored.')), 'info', 'threads')
+            messages.info(self.request, _('No posts were restored.'), 'threads')
 
 
     def post_action_soft(self, ids):
     def post_action_soft(self, ids):
         deleted = []
         deleted = []
@@ -189,9 +190,9 @@ class PostsModeration(object):
             self.thread.save(force_update=True)
             self.thread.save(force_update=True)
             self.forum.sync()
             self.forum.sync()
             self.forum.save(force_update=True)
             self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been hidden.')), 'success', 'threads')
+            messages.success(self.request, _('Selected posts have been hidden.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No posts were hidden.')), 'info', 'threads')
+            messages.info(self.request, _('No posts were hidden.'), 'threads')
 
 
     def post_action_hard(self, ids):
     def post_action_hard(self, ids):
         deleted = []
         deleted = []
@@ -208,6 +209,6 @@ class PostsModeration(object):
             self.thread.save(force_update=True)
             self.thread.save(force_update=True)
             self.forum.sync()
             self.forum.sync()
             self.forum.save(force_update=True)
             self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been deleted.')), 'success', 'threads')
+            messages.success(self.request, _('Selected posts have been deleted.'), 'threads')
         else:
         else:
-            self.request.messages.set_flash(Message(_('No posts were deleted.')), 'info', 'threads')
+            messages.info(self.request, _('No posts were deleted.'), 'threads')

+ 12 - 11
misago/apps/threadtype/thread/moderation/thread.py

@@ -1,5 +1,6 @@
 from django.template import RequestContext
 from django.template import RequestContext
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from misago import messages
 from misago.forms import Form
 from misago.forms import Form
 from misago.messages import Message
 from misago.messages import Message
 from misago.monitor import monitor, UpdatingMonitor
 from misago.monitor import monitor, UpdatingMonitor
@@ -31,7 +32,7 @@ class ThreadModeration(object):
         self.after_thread_action_accept()
         self.after_thread_action_accept()
 
 
     def after_thread_action_accept(self):
     def after_thread_action_accept(self):
-        self.request.messages.set_flash(Message(_('Thread has been marked as reviewed and made visible to other members.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been marked as reviewed and made visible to other members.'), 'threads')
 
 
     def thread_action_annouce(self):
     def thread_action_annouce(self):
         self.thread.weight = 2
         self.thread.weight = 2
@@ -39,15 +40,15 @@ class ThreadModeration(object):
         self.after_thread_action_annouce()
         self.after_thread_action_annouce()
 
 
     def after_thread_action_annouce(self):
     def after_thread_action_annouce(self):
-        self.request.messages.set_flash(Message(_('Thread has been turned into announcement.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been turned into announcement.'), 'threads')
 
 
     def thread_action_sticky(self):
     def thread_action_sticky(self):
         self.thread.weight = 1
         self.thread.weight = 1
         self.thread.save(force_update=True)
         self.thread.save(force_update=True)
         self.after_thread_action_sticky()
         self.after_thread_action_sticky()
-    
+
     def after_thread_action_sticky(self):
     def after_thread_action_sticky(self):
-        self.request.messages.set_flash(Message(_('Thread has been turned into sticky.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been turned into sticky.'), 'threads')
 
 
     def thread_action_normal(self):
     def thread_action_normal(self):
         self.thread.weight = 0
         self.thread.weight = 0
@@ -55,7 +56,7 @@ class ThreadModeration(object):
         self.after_thread_action_normal()
         self.after_thread_action_normal()
 
 
     def after_thread_action_normal(self):
     def after_thread_action_normal(self):
-        self.request.messages.set_flash(Message(_('Thread weight has been changed to normal.')), 'success', 'threads')
+        messages.success(self.request, _('Thread weight has been changed to normal.'), 'threads')
 
 
     def thread_action_move(self):
     def thread_action_move(self):
         message = None
         message = None
@@ -70,7 +71,7 @@ class ThreadModeration(object):
                 self.forum.save(force_update=True)
                 self.forum.save(force_update=True)
                 new_forum.sync()
                 new_forum.sync()
                 new_forum.save(force_update=True)
                 new_forum.save(force_update=True)
-                self.request.messages.set_flash(Message(_('Thread has been moved to "%(forum)s".') % {'forum': new_forum.name}), 'success', 'threads')
+                messages.success(self.request, _('Thread has been moved to "%(forum)s".') % {'forum': new_forum.name}, 'threads')
                 return None
                 return None
             message = Message(form.non_field_errors()[0], 'error')
             message = Message(form.non_field_errors()[0], 'error')
         else:
         else:
@@ -93,7 +94,7 @@ class ThreadModeration(object):
         self.after_thread_action_open()
         self.after_thread_action_open()
 
 
     def after_thread_action_open(self):
     def after_thread_action_open(self):
-        self.request.messages.set_flash(Message(_('Thread has been opened.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been opened.'), 'threads')
 
 
     def thread_action_close(self):
     def thread_action_close(self):
         self.thread.closed = True
         self.thread.closed = True
@@ -102,7 +103,7 @@ class ThreadModeration(object):
         self.after_thread_action_close()
         self.after_thread_action_close()
 
 
     def after_thread_action_close(self):
     def after_thread_action_close(self):
-        self.request.messages.set_flash(Message(_('Thread has been closed.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been closed.'), 'threads')
 
 
     def thread_action_undelete(self):
     def thread_action_undelete(self):
         # Update first post in thread
         # Update first post in thread
@@ -123,7 +124,7 @@ class ThreadModeration(object):
         self.after_thread_action_undelete()
         self.after_thread_action_undelete()
 
 
     def after_thread_action_undelete(self):
     def after_thread_action_undelete(self):
-        self.request.messages.set_flash(Message(_('Thread has been restored.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been restored.'), 'threads')
 
 
     def thread_action_soft(self):
     def thread_action_soft(self):
         # Update first post in thread
         # Update first post in thread
@@ -144,7 +145,7 @@ class ThreadModeration(object):
         self.after_thread_action_soft()
         self.after_thread_action_soft()
 
 
     def after_thread_action_soft(self):
     def after_thread_action_soft(self):
-        self.request.messages.set_flash(Message(_('Thread has been hidden.')), 'success', 'threads')
+        messages.success(self.request, _('Thread has been hidden.'), 'threads')
 
 
     def thread_action_hard(self):
     def thread_action_hard(self):
         # Delete thread
         # Delete thread
@@ -160,4 +161,4 @@ class ThreadModeration(object):
         return self.threads_list_redirect()
         return self.threads_list_redirect()
 
 
     def after_thread_action_hard(self):
     def after_thread_action_hard(self):
-        self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
+        messages.success(self.request, _('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}, 'threads')

+ 229 - 228
misago/apps/usercp/avatar/views.py

@@ -1,228 +1,229 @@
-from path import path
-from PIL import Image
-from zipfile import is_zipfile
-from django.core.exceptions import ValidationError
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.encoding import smart_str
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error404
-from misago.conf import settings
-from misago.decorators import block_guest
-from misago.messages import Message
-from misago.shortcuts import render_to_response
-from misago.utils.strings import random_string
-from misago.utils.avatars import resizeimage
-from misago.apps.usercp.template import RequestContext
-from misago.apps.usercp.avatar.forms import UploadAvatarForm
-
-def avatar_view(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if request.user.avatar_ban:
-            return render_to_response('usercp/avatar_banned.html',
-                                      context_instance=RequestContext(request, {
-                                          'tab': 'avatar'}));
-        return f(*args, **kwargs)
-    return decorator
-
-
-@block_guest
-@avatar_view
-def avatar(request):
-    message = request.messages.get_message('usercp_avatar')
-    return render_to_response('usercp/avatar.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'tab': 'avatar'}));
-
-
-@block_guest
-@avatar_view
-def gravatar(request):
-    if not 'gravatar' in settings.avatars_types:
-        return error404(request)
-    if request.user.avatar_type != 'gravatar':
-        if request.csrf.request_secure(request):
-            request.user.delete_avatar()
-            request.user.avatar_type = 'gravatar'
-            request.user.save(force_update=True)
-            request.messages.set_flash(Message(_("Your avatar has been changed to Gravatar.")), 'success', 'usercp_avatar')
-        else:
-            request.messages.set_flash(Message(_("Request authorisation is invalid.")), 'error', 'usercp_avatar')
-    return redirect(reverse('usercp_avatar'))
-
-
-@block_guest
-@avatar_view
-def gallery(request):
-    if not 'gallery' in settings.avatars_types:
-        return error404(request)
-
-    allowed_avatars = []
-    galleries = []
-    for directory in path(settings.STATICFILES_DIRS[0]).joinpath('avatars').dirs():
-        if directory[-7:] != '_locked' and directory[-8:] != '_default':
-            gallery = {'name': directory[-7:], 'avatars': []}
-            avatars = directory.files('*.gif')
-            avatars += directory.files('*.jpg')
-            avatars += directory.files('*.jpeg')
-            avatars += directory.files('*.png')
-            for item in avatars:
-                gallery['avatars'].append('/'.join(path(item).splitall()[-2:]))
-            galleries.append(gallery)
-            allowed_avatars += gallery['avatars']
-
-    if not allowed_avatars:
-        request.messages.set_flash(Message(_("No avatar galleries are available at the moment.")), 'info', 'usercp_avatar')
-        return redirect(reverse('usercp_avatar'))
-
-    message = request.messages.get_message('usercp_avatar')
-    if request.method == 'POST':
-        if request.csrf.request_secure(request):
-            new_avatar = request.POST.get('avatar_image')
-            if new_avatar in allowed_avatars:
-                request.user.delete_avatar()
-                request.user.avatar_type = 'gallery'
-                request.user.avatar_image = new_avatar
-                request.user.save(force_update=True)
-                request.messages.set_flash(Message(_("Your avatar has been changed to one from gallery.")), 'success', 'usercp_avatar')
-                return redirect(reverse('usercp_avatar'))
-            message = Message(_("Selected Avatar is incorrect."), 'error')
-        else:
-            message = Message(_("Request authorisation is invalid."), 'error')
-
-    return render_to_response('usercp/avatar_gallery.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'galleries': galleries,
-                                  'tab': 'avatar'}));
-
-
-@block_guest
-@avatar_view
-def upload(request):
-    if not 'upload' in settings.avatars_types:
-        return error404(request)
-    message = request.messages.get_message('usercp_avatar')
-    if request.method == 'POST':
-        form = UploadAvatarForm(request.POST, request.FILES, request=request)
-        if form.is_valid():
-            request.user.delete_avatar_temp()
-            image = form.cleaned_data['avatar_upload']
-            image_name, image_extension = path(smart_str(image.name.lower())).splitext()
-            image_name = '%s_tmp_%s%s' % (request.user.pk, random_string(8), image_extension)
-            image_path = settings.MEDIA_ROOT + 'avatars/' + image_name
-            request.user.avatar_temp = image_name
-
-            with open(image_path, 'wb+') as destination:
-                for chunk in image.chunks():
-                    destination.write(chunk)
-            request.user.save()
-            try:
-                if is_zipfile(image_path):
-                    # Composite file upload
-                    raise ValidationError()                 
-                image = Image.open(image_path)
-                if not image.format in ['GIF', 'PNG', 'JPEG']:
-                    raise ValidationError()
-                image.seek(0)
-                image.save(image_path)
-                if request.POST.get('js_check'):
-                    return redirect(reverse('usercp_avatar_upload_crop'))
-                # Redirect to crop page didnt happen, handle avatar with old school hollywood way
-                image_path = settings.MEDIA_ROOT + 'avatars/'
-                source = Image.open(image_path + request.user.avatar_temp)
-                image_name, image_extension = path(request.user.avatar_temp).splitext()
-                image_name = '%s_%s%s' % (request.user.pk, random_string(8), image_extension)
-                resizeimage(source, settings.AVATAR_SIZES[0], image_path + image_name, info=source.info, format=source.format)
-                for size in settings.AVATAR_SIZES[1:]:
-                    resizeimage(source, size, image_path + str(size) + '_' + image_name, info=source.info, format=source.format)
-                # Update user model one more time
-                request.user.delete_avatar_image()
-                request.user.delete_avatar_original()
-                request.user.avatar_type = 'upload'
-                request.user.avatar_original = '%s_org_%s%s' % (request.user.pk, random_string(8), image_extension)
-                source.save(image_path + request.user.avatar_original)
-                request.user.delete_avatar_temp()
-                request.user.avatar_image = image_name
-                request.user.save(force_update=True)
-                # Set message and adios!
-                request.messages.set_flash(Message(_("Your avatar has changed.")), 'success', 'usercp_avatar')
-                return redirect(reverse('usercp_avatar'))
-            except ValidationError:
-                request.user.delete_avatar()
-                request.user.default_avatar()
-                message = Message(_("Only gif, jpeg and png files are allowed for member avatars."), 'error')
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = UploadAvatarForm(request=request)
-
-    return render_to_response('usercp/avatar_upload.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'form': form,
-                                  'tab': 'avatar'}));
-
-
-@block_guest
-@avatar_view
-def crop(request, upload=False):
-    if upload and (not request.user.avatar_temp or not 'upload' in settings.avatars_types):
-        return error404(request)
-
-    if not upload and request.user.avatar_type != 'upload':
-        request.messages.set_flash(Message(_("Crop Avatar option is avaiable only when you use uploaded image as your avatar.")), 'error', 'usercp_avatar')
-        return redirect(reverse('usercp_avatar'))
-
-    message = request.messages.get_message('usercp_avatar')
-    if request.method == 'POST':
-        if request.csrf.request_secure(request):
-            try:
-                image_path = settings.MEDIA_ROOT + 'avatars/'
-                if upload:
-                    source = Image.open(image_path + request.user.avatar_temp)
-                else:
-                    source = Image.open(image_path + request.user.avatar_original)
-                width, height = source.size
-
-                aspect = float(width) / float(request.POST['crop_b'])
-                crop_x = int(aspect * float(request.POST['crop_x']))
-                crop_y = int(aspect * float(request.POST['crop_y']))
-                crop_w = int(aspect * float(request.POST['crop_w']))
-                crop = source.crop((crop_x, crop_y, crop_x + crop_w, crop_y + crop_w))
-
-                if upload:
-                    image_name, image_extension = path(request.user.avatar_temp).splitext()
-                else:
-                    image_name, image_extension = path(request.user.avatar_original).splitext()
-                image_name = '%s_%s%s' % (request.user.pk, random_string(8), image_extension)
-                resizeimage(crop, settings.AVATAR_SIZES[0], image_path + image_name, info=source.info, format=source.format)
-                for size in settings.AVATAR_SIZES[1:]:
-                    resizeimage(crop, size, image_path + str(size) + '_' + image_name, info=source.info, format=source.format)
-
-                request.user.delete_avatar_image()
-                if upload:
-                    request.user.delete_avatar_original()
-                    request.user.avatar_type = 'upload'
-                    request.user.avatar_original = '%s_org_%s%s' % (request.user.pk, random_string(8), image_extension)
-                    source.save(image_path + request.user.avatar_original)
-                request.user.delete_avatar_temp()
-                request.user.avatar_image = image_name
-                request.user.save(force_update=True)
-                request.messages.set_flash(Message(_("Your avatar has been cropped.")), 'success', 'usercp_avatar')
-                return redirect(reverse('usercp_avatar'))
-            except Exception:
-                message = Message(_("Form contains errors."), 'error')
-        else:
-            message = Message(_("Request authorisation is invalid."), 'error')
-
-
-    return render_to_response('usercp/avatar_crop.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'after_upload': upload,
-                                  'avatar_size': settings.AVATAR_SIZES[0],
-                                  'source': 'avatars/%s' % (request.user.avatar_temp if upload else request.user.avatar_original),
-                                  'tab': 'avatar'}));
+from path import path
+from PIL import Image
+from zipfile import is_zipfile
+from django.core.exceptions import ValidationError
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.encoding import smart_str
+from django.utils.translation import ugettext as _
+form misago import messages
+from misago.apps.errors import error404
+from misago.conf import settings
+from misago.decorators import block_guest
+from misago.messages import Message
+from misago.shortcuts import render_to_response
+from misago.utils.strings import random_string
+from misago.utils.avatars import resizeimage
+from misago.apps.usercp.template import RequestContext
+from misago.apps.usercp.avatar.forms import UploadAvatarForm
+
+def avatar_view(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if request.user.avatar_ban:
+            return render_to_response('usercp/avatar_banned.html',
+                                      context_instance=RequestContext(request, {
+                                          'tab': 'avatar'}));
+        return f(*args, **kwargs)
+    return decorator
+
+
+@block_guest
+@avatar_view
+def avatar(request):
+    message = request.messages.get_message('usercp_avatar')
+    return render_to_response('usercp/avatar.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'tab': 'avatar'}));
+
+
+@block_guest
+@avatar_view
+def gravatar(request):
+    if not 'gravatar' in settings.avatars_types:
+        return error404(request)
+    if request.user.avatar_type != 'gravatar':
+        if request.csrf.request_secure(request):
+            request.user.delete_avatar()
+            request.user.avatar_type = 'gravatar'
+            request.user.save(force_update=True)
+            messages.success(request, _("Your avatar has been changed to Gravatar."), 'usercp_avatar')
+        else:
+            messages.error(request, _("Request authorisation is invalid."), 'usercp_avatar')
+    return redirect(reverse('usercp_avatar'))
+
+
+@block_guest
+@avatar_view
+def gallery(request):
+    if not 'gallery' in settings.avatars_types:
+        return error404(request)
+
+    allowed_avatars = []
+    galleries = []
+    for directory in path(settings.STATICFILES_DIRS[0]).joinpath('avatars').dirs():
+        if directory[-7:] != '_locked' and directory[-8:] != '_default':
+            gallery = {'name': directory[-7:], 'avatars': []}
+            avatars = directory.files('*.gif')
+            avatars += directory.files('*.jpg')
+            avatars += directory.files('*.jpeg')
+            avatars += directory.files('*.png')
+            for item in avatars:
+                gallery['avatars'].append('/'.join(path(item).splitall()[-2:]))
+            galleries.append(gallery)
+            allowed_avatars += gallery['avatars']
+
+    if not allowed_avatars:
+        messages.info(request, _("No avatar galleries are available at the moment."), 'usercp_avatar')
+        return redirect(reverse('usercp_avatar'))
+
+    message = request.messages.get_message('usercp_avatar')
+    if request.method == 'POST':
+        if request.csrf.request_secure(request):
+            new_avatar = request.POST.get('avatar_image')
+            if new_avatar in allowed_avatars:
+                request.user.delete_avatar()
+                request.user.avatar_type = 'gallery'
+                request.user.avatar_image = new_avatar
+                request.user.save(force_update=True)
+                messages.success(request, _("Your avatar has been changed to one from gallery."), 'usercp_avatar')
+                return redirect(reverse('usercp_avatar'))
+            message = Message(_("Selected Avatar is incorrect."), 'error')
+        else:
+            message = Message(_("Request authorisation is invalid."), 'error')
+
+    return render_to_response('usercp/avatar_gallery.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'galleries': galleries,
+                                  'tab': 'avatar'}));
+
+
+@block_guest
+@avatar_view
+def upload(request):
+    if not 'upload' in settings.avatars_types:
+        return error404(request)
+    message = request.messages.get_message('usercp_avatar')
+    if request.method == 'POST':
+        form = UploadAvatarForm(request.POST, request.FILES, request=request)
+        if form.is_valid():
+            request.user.delete_avatar_temp()
+            image = form.cleaned_data['avatar_upload']
+            image_name, image_extension = path(smart_str(image.name.lower())).splitext()
+            image_name = '%s_tmp_%s%s' % (request.user.pk, random_string(8), image_extension)
+            image_path = settings.MEDIA_ROOT + 'avatars/' + image_name
+            request.user.avatar_temp = image_name
+
+            with open(image_path, 'wb+') as destination:
+                for chunk in image.chunks():
+                    destination.write(chunk)
+            request.user.save()
+            try:
+                if is_zipfile(image_path):
+                    # Composite file upload
+                    raise ValidationError()
+                image = Image.open(image_path)
+                if not image.format in ['GIF', 'PNG', 'JPEG']:
+                    raise ValidationError()
+                image.seek(0)
+                image.save(image_path)
+                if request.POST.get('js_check'):
+                    return redirect(reverse('usercp_avatar_upload_crop'))
+                # Redirect to crop page didnt happen, handle avatar with old school hollywood way
+                image_path = settings.MEDIA_ROOT + 'avatars/'
+                source = Image.open(image_path + request.user.avatar_temp)
+                image_name, image_extension = path(request.user.avatar_temp).splitext()
+                image_name = '%s_%s%s' % (request.user.pk, random_string(8), image_extension)
+                resizeimage(source, settings.AVATAR_SIZES[0], image_path + image_name, info=source.info, format=source.format)
+                for size in settings.AVATAR_SIZES[1:]:
+                    resizeimage(source, size, image_path + str(size) + '_' + image_name, info=source.info, format=source.format)
+                # Update user model one more time
+                request.user.delete_avatar_image()
+                request.user.delete_avatar_original()
+                request.user.avatar_type = 'upload'
+                request.user.avatar_original = '%s_org_%s%s' % (request.user.pk, random_string(8), image_extension)
+                source.save(image_path + request.user.avatar_original)
+                request.user.delete_avatar_temp()
+                request.user.avatar_image = image_name
+                request.user.save(force_update=True)
+                # Set message and adios!
+                messages.success(request, _("Your avatar has changed."), 'usercp_avatar')
+                return redirect(reverse('usercp_avatar'))
+            except ValidationError:
+                request.user.delete_avatar()
+                request.user.default_avatar()
+                message = Message(_("Only gif, jpeg and png files are allowed for member avatars."), 'error')
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = UploadAvatarForm(request=request)
+
+    return render_to_response('usercp/avatar_upload.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'form': form,
+                                  'tab': 'avatar'}));
+
+
+@block_guest
+@avatar_view
+def crop(request, upload=False):
+    if upload and (not request.user.avatar_temp or not 'upload' in settings.avatars_types):
+        return error404(request)
+
+    if not upload and request.user.avatar_type != 'upload':
+        messages.error(request, _("Crop Avatar option is avaiable only when you use uploaded image as your avatar."), 'usercp_avatar')
+        return redirect(reverse('usercp_avatar'))
+
+    message = request.messages.get_message('usercp_avatar')
+    if request.method == 'POST':
+        if request.csrf.request_secure(request):
+            try:
+                image_path = settings.MEDIA_ROOT + 'avatars/'
+                if upload:
+                    source = Image.open(image_path + request.user.avatar_temp)
+                else:
+                    source = Image.open(image_path + request.user.avatar_original)
+                width, height = source.size
+
+                aspect = float(width) / float(request.POST['crop_b'])
+                crop_x = int(aspect * float(request.POST['crop_x']))
+                crop_y = int(aspect * float(request.POST['crop_y']))
+                crop_w = int(aspect * float(request.POST['crop_w']))
+                crop = source.crop((crop_x, crop_y, crop_x + crop_w, crop_y + crop_w))
+
+                if upload:
+                    image_name, image_extension = path(request.user.avatar_temp).splitext()
+                else:
+                    image_name, image_extension = path(request.user.avatar_original).splitext()
+                image_name = '%s_%s%s' % (request.user.pk, random_string(8), image_extension)
+                resizeimage(crop, settings.AVATAR_SIZES[0], image_path + image_name, info=source.info, format=source.format)
+                for size in settings.AVATAR_SIZES[1:]:
+                    resizeimage(crop, size, image_path + str(size) + '_' + image_name, info=source.info, format=source.format)
+
+                request.user.delete_avatar_image()
+                if upload:
+                    request.user.delete_avatar_original()
+                    request.user.avatar_type = 'upload'
+                    request.user.avatar_original = '%s_org_%s%s' % (request.user.pk, random_string(8), image_extension)
+                    source.save(image_path + request.user.avatar_original)
+                request.user.delete_avatar_temp()
+                request.user.avatar_image = image_name
+                request.user.save(force_update=True)
+                messages.success(request, _("Your avatar has been cropped."), 'usercp_avatar')
+                return redirect(reverse('usercp_avatar'))
+            except Exception:
+                message = Message(_("Form contains errors."), 'error')
+        else:
+            message = Message(_("Request authorisation is invalid."), 'error')
+
+
+    return render_to_response('usercp/avatar_crop.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'after_upload': upload,
+                                  'avatar_size': settings.AVATAR_SIZES[0],
+                                  'source': 'avatars/%s' % (request.user.avatar_temp if upload else request.user.avatar_original),
+                                  'tab': 'avatar'}));

+ 72 - 71
misago/apps/usercp/credentials/views.py

@@ -1,71 +1,72 @@
-from django.core.exceptions import ValidationError
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error404
-from misago.decorators import block_guest
-from misago.messages import Message
-from misago.shortcuts import render_to_response
-from misago.utils.strings import random_string
-from misago.apps.usercp.template import RequestContext
-from misago.apps.usercp.credentials.forms import CredentialsChangeForm
-
-@block_guest
-def credentials(request):
-    message = request.messages.get_message('usercp_credentials')
-    if request.method == 'POST':
-        form = CredentialsChangeForm(request.POST, request=request)
-        if form.is_valid():
-            token = random_string(12)
-            request.user.email_user(
-                                    request,
-                                    'users/new_credentials',
-                                    _("Activate new Sign-In Credentials"),
-                                    {'token': token}
-                                    )
-            request.session['new_credentials'] = {
-                                                  'token': token,
-                                                  'email_hash': request.user.email_hash,
-                                                  'new_email': form.cleaned_data['new_email'],
-                                                  'new_password': form.cleaned_data['new_password'],
-                                                  }
-            if form.cleaned_data['new_email']:
-                request.user.email = form.cleaned_data['new_email']
-                request.messages.set_flash(Message(_("We have sent e-mail message to your new e-mail address with link you have to click to confirm change of your sign-in credentials. This link will be valid only for duration of this session, do not sign out until you confirm change!")), 'success', 'usercp_credentials')
-            else:
-                request.messages.set_flash(Message(_("We have sent e-mail message to your e-mail address with link you have to click to confirm change of your sign-in credentials. This link will be valid only for duration of this session, do not sign out until you confirm change!")), 'success', 'usercp_credentials')
-            return redirect(reverse('usercp_credentials'))
-        message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = CredentialsChangeForm(request=request)
-
-    return render_to_response('usercp/credentials.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'form': form,
-                                  'tab': 'credentials'}));
-
-
-@block_guest
-def activate(request, token):
-    new_credentials = request.session.get('new_credentials')
-    if not new_credentials or new_credentials['token'] != token:
-        return error404(request)
-
-    if new_credentials['new_email']:
-        request.user.set_email(new_credentials['new_email'])
-    if new_credentials['new_password']:
-        request.user.set_password(new_credentials['new_password'])
-
-    try:
-        request.user.full_clean()
-        request.user.save(force_update=True)
-        request.user.sessions.exclude(id=request.session.id).delete()
-        request.user.signin_tokens.all().delete()
-        request.messages.set_flash(Message(_("%(username)s, your Sign-In credentials have been changed.") % {'username': request.user.username}), 'success', 'security')
-        request.session.sign_out(request)
-        del request.session['new_credentials']
-        return redirect(reverse('sign_in'))
-    except ValidationError:
-        request.messages.set_flash(Message(_("Your new credentials have been invalidated. Please try again.")), 'error', 'usercp_credentials')
-        return redirect(reverse('usercp_credentials'))
+from django.core.exceptions import ValidationError
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.errors import error404
+from misago.decorators import block_guest
+from misago.messages import Message
+from misago.shortcuts import render_to_response
+from misago.utils.strings import random_string
+from misago.apps.usercp.template import RequestContext
+from misago.apps.usercp.credentials.forms import CredentialsChangeForm
+
+@block_guest
+def credentials(request):
+    message = request.messages.get_message('usercp_credentials')
+    if request.method == 'POST':
+        form = CredentialsChangeForm(request.POST, request=request)
+        if form.is_valid():
+            token = random_string(12)
+            request.user.email_user(
+                                    request,
+                                    'users/new_credentials',
+                                    _("Activate new Sign-In Credentials"),
+                                    {'token': token}
+                                    )
+            request.session['new_credentials'] = {
+                                                  'token': token,
+                                                  'email_hash': request.user.email_hash,
+                                                  'new_email': form.cleaned_data['new_email'],
+                                                  'new_password': form.cleaned_data['new_password'],
+                                                  }
+            if form.cleaned_data['new_email']:
+                request.user.email = form.cleaned_data['new_email']
+                messages.success(request, _("We have sent e-mail message to your new e-mail address with link you have to click to confirm change of your sign-in credentials. This link will be valid only for duration of this session, do not sign out until you confirm change!"), 'usercp_credentials')
+            else:
+                messages.success(request, _("We have sent e-mail message to your e-mail address with link you have to click to confirm change of your sign-in credentials. This link will be valid only for duration of this session, do not sign out until you confirm change!"), 'usercp_credentials')
+            return redirect(reverse('usercp_credentials'))
+        message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = CredentialsChangeForm(request=request)
+
+    return render_to_response('usercp/credentials.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'form': form,
+                                  'tab': 'credentials'}));
+
+
+@block_guest
+def activate(request, token):
+    new_credentials = request.session.get('new_credentials')
+    if not new_credentials or new_credentials['token'] != token:
+        return error404(request)
+
+    if new_credentials['new_email']:
+        request.user.set_email(new_credentials['new_email'])
+    if new_credentials['new_password']:
+        request.user.set_password(new_credentials['new_password'])
+
+    try:
+        request.user.full_clean()
+        request.user.save(force_update=True)
+        request.user.sessions.exclude(id=request.session.id).delete()
+        request.user.signin_tokens.all().delete()
+        messages.success(request, _("%(username)s, your Sign-In credentials have been changed.") % {'username': request.user.username}, 'security')
+        request.session.sign_out(request)
+        del request.session['new_credentials']
+        return redirect(reverse('sign_in'))
+    except ValidationError:
+        messages.error(request, _("Your new credentials have been invalidated. Please try again."), 'usercp_credentials')
+        return redirect(reverse('usercp_credentials'))

+ 41 - 40
misago/apps/usercp/options/views.py

@@ -1,40 +1,41 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.decorators import block_guest
-from misago.messages import Message
-from misago.shortcuts import render_to_response
-from misago.apps.usercp.options.forms import UserForumOptionsForm
-from misago.apps.usercp.template import RequestContext
-
-@block_guest
-def options(request):
-    message = request.messages.get_message('usercp_options')
-    if request.method == 'POST':
-        form = UserForumOptionsForm(request.POST, request=request)
-        if form.is_valid():
-            request.user.hide_activity = form.cleaned_data['hide_activity']
-            request.user.allow_pds = form.cleaned_data['allow_pds']
-            request.user.receive_newsletters = form.cleaned_data['newsletters']
-            request.user.timezone = form.cleaned_data['timezone']
-            request.user.subscribe_start = form.cleaned_data['subscribe_start']
-            request.user.subscribe_reply = form.cleaned_data['subscribe_reply']
-            request.user.save(force_update=True)
-            request.messages.set_flash(Message(_("Forum options have been changed.")), 'success', 'usercp_options')
-            return redirect(reverse('usercp'))
-        message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = UserForumOptionsForm(request=request, initial={
-                                                             'newsletters': request.user.receive_newsletters,
-                                                             'hide_activity': request.user.hide_activity,
-                                                             'allow_pds': request.user.allow_pds,
-                                                             'timezone': request.user.timezone,
-                                                             'subscribe_start': request.user.subscribe_start,
-                                                             'subscribe_reply': request.user.subscribe_reply,
-                                                             })
-
-    return render_to_response('usercp/options.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'tab': 'options',
-                                  'form': form}));
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.decorators import block_guest
+from misago.messages import Message
+from misago.shortcuts import render_to_response
+from misago.apps.usercp.options.forms import UserForumOptionsForm
+from misago.apps.usercp.template import RequestContext
+
+@block_guest
+def options(request):
+    message = request.messages.get_message('usercp_options')
+    if request.method == 'POST':
+        form = UserForumOptionsForm(request.POST, request=request)
+        if form.is_valid():
+            request.user.hide_activity = form.cleaned_data['hide_activity']
+            request.user.allow_pds = form.cleaned_data['allow_pds']
+            request.user.receive_newsletters = form.cleaned_data['newsletters']
+            request.user.timezone = form.cleaned_data['timezone']
+            request.user.subscribe_start = form.cleaned_data['subscribe_start']
+            request.user.subscribe_reply = form.cleaned_data['subscribe_reply']
+            request.user.save(force_update=True)
+            messages.success(request, _("Forum options have been changed."), 'usercp_options')
+            return redirect(reverse('usercp'))
+        message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = UserForumOptionsForm(request=request, initial={
+                                                             'newsletters': request.user.receive_newsletters,
+                                                             'hide_activity': request.user.hide_activity,
+                                                             'allow_pds': request.user.allow_pds,
+                                                             'timezone': request.user.timezone,
+                                                             'subscribe_start': request.user.subscribe_start,
+                                                             'subscribe_reply': request.user.subscribe_reply,
+                                                             })
+
+    return render_to_response('usercp/options.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'tab': 'options',
+                                  'form': form}));

+ 46 - 45
misago/apps/usercp/signature/views.py

@@ -1,45 +1,46 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error403, error404
-from misago.decorators import block_guest
-from misago.markdown import signature_markdown
-from misago.messages import Message
-from misago.shortcuts import render_to_response
-from misago.apps.usercp.template import RequestContext
-from misago.apps.usercp.signature.forms import SignatureForm
-
-@block_guest
-def signature(request):
-    # Intercept all requests if we can't use signature
-    if not request.acl.usercp.can_use_signature():
-        return error403(request)
-    if request.user.signature_ban:
-        return render_to_response('usercp/signature_banned.html',
-                                  context_instance=RequestContext(request, {
-                                      'tab': 'signature'}));
-
-    siggy_text = ''
-    message = request.messages.get_message('usercp_signature')
-    if request.method == 'POST':
-        form = SignatureForm(request.POST, request=request, initial={'signature': request.user.signature})
-        if form.is_valid():
-            request.user.signature = form.cleaned_data['signature']
-            if request.user.signature:
-                request.user.signature_preparsed = signature_markdown(request.acl,
-                                                                      request.user.signature)
-            else:
-                request.user.signature_preparsed = None
-            request.user.save(force_update=True)
-            request.messages.set_flash(Message(_("Your signature has been changed.")), 'success', 'usercp_signature')
-            return redirect(reverse('usercp_signature'))
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = SignatureForm(request=request, initial={'signature': request.user.signature})
-
-    return render_to_response('usercp/signature.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'tab': 'signature',
-                                  'form': form}));
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.errors import error403, error404
+from misago.decorators import block_guest
+from misago.markdown import signature_markdown
+from misago.messages import Message
+from misago.shortcuts import render_to_response
+from misago.apps.usercp.template import RequestContext
+from misago.apps.usercp.signature.forms import SignatureForm
+
+@block_guest
+def signature(request):
+    # Intercept all requests if we can't use signature
+    if not request.acl.usercp.can_use_signature():
+        return error403(request)
+    if request.user.signature_ban:
+        return render_to_response('usercp/signature_banned.html',
+                                  context_instance=RequestContext(request, {
+                                      'tab': 'signature'}));
+
+    siggy_text = ''
+    message = request.messages.get_message('usercp_signature')
+    if request.method == 'POST':
+        form = SignatureForm(request.POST, request=request, initial={'signature': request.user.signature})
+        if form.is_valid():
+            request.user.signature = form.cleaned_data['signature']
+            if request.user.signature:
+                request.user.signature_preparsed = signature_markdown(request.acl,
+                                                                      request.user.signature)
+            else:
+                request.user.signature_preparsed = None
+            request.user.save(force_update=True)
+            messages.success(request, _("Your signature has been changed."), 'usercp_signature')
+            return redirect(reverse('usercp_signature'))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = SignatureForm(request=request, initial={'signature': request.user.signature})
+
+    return render_to_response('usercp/signature.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'tab': 'signature',
+                                  'form': form}));

+ 71 - 70
misago/apps/usercp/username/views.py

@@ -1,70 +1,71 @@
-from datetime import timedelta
-from django.core.urlresolvers import reverse
-from django.db.models import F
-from django.shortcuts import redirect
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error404
-from misago.decorators import block_guest
-from misago.messages import Message
-from misago.models import Alert, User, UsernameChange
-from misago.shortcuts import render_to_response
-from misago.utils.translation import ugettext_lazy
-from misago.apps.usercp.template import RequestContext
-from misago.apps.usercp.username.forms import UsernameChangeForm
-
-@block_guest
-def username(request):
-    if not request.acl.usercp.show_username_change():
-        return error404(request)
-
-    changes_left = request.acl.usercp.changes_left(request.user)
-    next_change = None
-    if request.acl.usercp.changes_expire() and not changes_left:
-        next_change = request.user.namechanges.filter(
-                                                      date__gte=timezone.now() - timedelta(days=request.acl.usercp.acl['changes_expire']),
-                                                      ).order_by('-date')[0]
-        next_change = next_change.date + timedelta(days=request.acl.usercp.acl['changes_expire'])
-
-    message = request.messages.get_message('usercp_username')
-    if request.method == 'POST':
-        if not changes_left:
-            message = Message(_("You have exceeded the maximum number of name changes."), 'error')
-            form = UsernameChangeForm(request=request)
-        else:
-            org_username = request.user.username
-            form = UsernameChangeForm(request.POST, request=request)
-            if form.is_valid():
-                request.user.set_username(form.cleaned_data['username'])
-                request.user.save(force_update=True)
-                request.user.sync_username()
-                request.user.namechanges.create(date=timezone.now(), old_username=org_username)
-                request.messages.set_flash(Message(_("Your username has been changed.")), 'success', 'usercp_username')
-                # Alert followers of namechange
-                alert_time = timezone.now()
-                bulk_alerts = []
-                alerted_users = []
-                for follower in request.user.follows_set.iterator():
-                    alerted_users.append(follower.pk)
-                    alert = Alert(user=follower, message=ugettext_lazy("User that you are following, %(username)s, has changed his name to %(newname)s").message, date=alert_time)
-                    alert.strong('username', org_username)
-                    alert.profile('newname', request.user)
-                    alert.hydrate()
-                    bulk_alerts.append(alert)
-                if bulk_alerts:
-                    Alert.objects.bulk_create(bulk_alerts)
-                    User.objects.filter(id__in=alerted_users).update(alerts=F('alerts') + 1)
-                # Hop back
-                return redirect(reverse('usercp_username'))
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = UsernameChangeForm(request=request)
-
-    return render_to_response('usercp/username.html',
-                              context_instance=RequestContext(request, {
-                                  'message': message,
-                                  'changes_left': changes_left,
-                                  'form': form,
-                                  'next_change': next_change,
-                                  'changes_history': request.user.namechanges.order_by('-date')[:10],
-                                  'tab': 'username'}));
+from datetime import timedelta
+from django.core.urlresolvers import reverse
+from django.db.models import F
+from django.shortcuts import redirect
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.errors import error404
+from misago.decorators import block_guest
+from misago.messages import Message
+from misago.models import Alert, User, UsernameChange
+from misago.shortcuts import render_to_response
+from misago.utils.translation import ugettext_lazy
+from misago.apps.usercp.template import RequestContext
+from misago.apps.usercp.username.forms import UsernameChangeForm
+
+@block_guest
+def username(request):
+    if not request.acl.usercp.show_username_change():
+        return error404(request)
+
+    changes_left = request.acl.usercp.changes_left(request.user)
+    next_change = None
+    if request.acl.usercp.changes_expire() and not changes_left:
+        next_change = request.user.namechanges.filter(
+                                                      date__gte=timezone.now() - timedelta(days=request.acl.usercp.acl['changes_expire']),
+                                                      ).order_by('-date')[0]
+        next_change = next_change.date + timedelta(days=request.acl.usercp.acl['changes_expire'])
+
+    message = request.messages.get_message('usercp_username')
+    if request.method == 'POST':
+        if not changes_left:
+            message = Message(_("You have exceeded the maximum number of name changes."), 'error')
+            form = UsernameChangeForm(request=request)
+        else:
+            org_username = request.user.username
+            form = UsernameChangeForm(request.POST, request=request)
+            if form.is_valid():
+                request.user.set_username(form.cleaned_data['username'])
+                request.user.save(force_update=True)
+                request.user.sync_username()
+                request.user.namechanges.create(date=timezone.now(), old_username=org_username)
+                messages.success(request, _("Your username has been changed."), 'usercp_username')
+                # Alert followers of namechange
+                alert_time = timezone.now()
+                bulk_alerts = []
+                alerted_users = []
+                for follower in request.user.follows_set.iterator():
+                    alerted_users.append(follower.pk)
+                    alert = Alert(user=follower, message=ugettext_lazy("User that you are following, %(username)s, has changed his name to %(newname)s").message, date=alert_time)
+                    alert.strong('username', org_username)
+                    alert.profile('newname', request.user)
+                    alert.hydrate()
+                    bulk_alerts.append(alert)
+                if bulk_alerts:
+                    Alert.objects.bulk_create(bulk_alerts)
+                    User.objects.filter(id__in=alerted_users).update(alerts=F('alerts') + 1)
+                # Hop back
+                return redirect(reverse('usercp_username'))
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = UsernameChangeForm(request=request)
+
+    return render_to_response('usercp/username.html',
+                              context_instance=RequestContext(request, {
+                                  'message': message,
+                                  'changes_left': changes_left,
+                                  'form': form,
+                                  'next_change': next_change,
+                                  'changes_history': request.user.namechanges.order_by('-date')[:10],
+                                  'tab': 'username'}));

+ 77 - 76
misago/apps/usercp/views.py

@@ -1,76 +1,77 @@
-from django.core.urlresolvers import NoReverseMatch
-from django.shortcuts import redirect
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error404
-from misago.apps.profiles.decorators import user_view
-from misago.decorators import block_guest, check_csrf
-from misago.messages import Message
-from misago.models import User
-from misago.utils.translation import ugettext_lazy
-
-def fallback(request):
-    try:
-        return redirect(request.POST.get('fallback', '/'))
-    except NoReverseMatch:
-        return redirect('index')
-
-
-@block_guest
-@check_csrf
-@user_view
-def follow(request, user):
-    if request.user.pk == user.pk:
-        return error404(request)
-    if not request.user.is_following(user):
-        request.messages.set_flash(Message(_("You are now following %(username)s") % {'username': user.username}), 'success')
-        request.user.follows.add(user)
-        request.user.following += 1
-        request.user.save(force_update=True)
-        user.followers += 1
-        if not user.is_ignoring(request.user):
-            alert = user.alert(ugettext_lazy("%(username)s is now following you").message)
-            alert.profile('username', request.user)
-            alert.save_all()
-        else:
-            user.save(force_update=True)
-    return fallback(request)
-
-
-@block_guest
-@check_csrf
-@user_view
-def unfollow(request, user):
-    if request.user.pk == user.pk:
-        return error404(request)
-    if request.user.is_following(user):
-        request.messages.set_flash(Message(_("You have stopped following %(username)s") % {'username': user.username}))
-        request.user.follows.remove(user)
-        request.user.following -= 1
-        request.user.save(force_update=True)
-        user.followers -= 1
-        user.save(force_update=True)
-    return fallback(request)
-
-
-@block_guest
-@check_csrf
-@user_view
-def ignore(request, user):
-    if request.user.pk == user.pk:
-        return error404(request)
-    if not request.user.is_ignoring(user):
-        request.messages.set_flash(Message(_("You are now ignoring %(username)s") % {'username': user.username}), 'success')
-        request.user.ignores.add(user)
-    return fallback(request)
-
-
-@block_guest
-@check_csrf
-@user_view
-def unignore(request, user):
-    if request.user.pk == user.pk:
-        return error404(request)
-    if request.user.is_ignoring(user):
-        request.messages.set_flash(Message(_("You have stopped ignoring %(username)s") % {'username': user.username}))
-        request.user.ignores.remove(user)
-    return fallback(request)
+from django.core.urlresolvers import NoReverseMatch
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago import messages
+from misago.apps.errors import error404
+from misago.apps.profiles.decorators import user_view
+from misago.decorators import block_guest, check_csrf
+from misago.messages import Message
+from misago.models import User
+from misago.utils.translation import ugettext_lazy
+
+def fallback(request):
+    try:
+        return redirect(request.POST.get('fallback', '/'))
+    except NoReverseMatch:
+        return redirect('index')
+
+
+@block_guest
+@check_csrf
+@user_view
+def follow(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if not request.user.is_following(user):
+        messages.success(request, _("You are now following %(username)s") % {'username': user.username})
+        request.user.follows.add(user)
+        request.user.following += 1
+        request.user.save(force_update=True)
+        user.followers += 1
+        if not user.is_ignoring(request.user):
+            alert = user.alert(ugettext_lazy("%(username)s is now following you").message)
+            alert.profile('username', request.user)
+            alert.save_all()
+        else:
+            user.save(force_update=True)
+    return fallback(request)
+
+
+@block_guest
+@check_csrf
+@user_view
+def unfollow(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if request.user.is_following(user):
+        messages.info(request, _("You have stopped following %(username)s") % {'username': user.username})
+        request.user.follows.remove(user)
+        request.user.following -= 1
+        request.user.save(force_update=True)
+        user.followers -= 1
+        user.save(force_update=True)
+    return fallback(request)
+
+
+@block_guest
+@check_csrf
+@user_view
+def ignore(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if not request.user.is_ignoring(user):
+        messages.success(request, _("You are now ignoring %(username)s") % {'username': user.username})
+        request.user.ignores.add(user)
+    return fallback(request)
+
+
+@block_guest
+@check_csrf
+@user_view
+def unignore(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if request.user.is_ignoring(user):
+        messages.info(request, _("You have stopped ignoring %(username)s") % {'username': user.username})
+        request.user.ignores.remove(user)
+    return fallback(request)

+ 17 - 7
misago/messages.py

@@ -9,14 +9,17 @@ class Messages(object):
         self.messages = session.get('messages_list', [])
         self.messages = session.get('messages_list', [])
         self.session['messages_list'] = []
         self.session['messages_list'] = []
 
 
-    def set_message(self, message, type='info', owner=None):
-        message.type = type
-        message.owner = owner
-        self.messages.append(message)
+    def set_message(self, message, level='info', owner=None):
+        msg = Message(message)
+        msg.level = level
+        msg.owner = owner
+        self.messages.append(msg)
+        return msg
 
 
     def set_flash(self, message, level='info', owner=None):
     def set_flash(self, message, level='info', owner=None):
-        self.set_message(message, level, owner)
-        self.session['messages_list'].append(message)
+        msg = self.set_message(message, level, owner)
+        self.session['messages_list'].append(msg)
+        return msg
 
 
     def get_message(self, owner=None):
     def get_message(self, owner=None):
         for index, message in enumerate(self.messages):
         for index, message in enumerate(self.messages):
@@ -43,9 +46,16 @@ class Message(object):
         self.message = message
         self.message = message
         self.owner = owner
         self.owner = owner
 
 
+    def __unicode__(self):
+        return self.message
+
+
+def get_messages(request, owner=None):
+    return request.messages.get_messages(owner)
+
 
 
 def add_message(request, level, message, owner=None):
 def add_message(request, level, message, owner=None):
-    request.messages.set_message(message, level=level, owner=owner)
+    request.messages.set_flash(unicode(message), level=level, owner=owner)
 
 
 
 
 def info(request, message, owner=None):
 def info(request, message, owner=None):

+ 3 - 2
misago/utils/views.py

@@ -3,9 +3,10 @@ from django.core.urlresolvers import reverse
 from django.http import HttpResponse
 from django.http import HttpResponse
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
+from misago import messages
 
 
-def redirect_message(request, message, type='info', owner=None):
-    request.messages.set_flash(message, type, owner)
+def redirect_message(request, level, message, owner=None):
+    messages.add_message(request, level, message, owner)
     return redirect(reverse('index'))
     return redirect(reverse('index'))
 
 
 
 

+ 4 - 4
templates/admin/macros.html

@@ -10,16 +10,16 @@
 
 
 {# Render single message #}
 {# Render single message #}
 {% macro draw_message(message, class='') %}
 {% macro draw_message(message, class='') %}
-  <div class="alert alert-{{ message.type }}{% if class %} {{ class }}{% endif %}">
+  <div class="alert alert-{{ message.level }}{% if class %} {{ class }}{% endif %}">
   	{{ draw_message_icon(message) }} <p><strong>{{ message.message }}</strong></p>
   	{{ draw_message_icon(message) }} <p><strong>{{ message.message }}</strong></p>
   </div>
   </div>
 {%- endmacro %}
 {%- endmacro %}
 
 
 {# Render single message #}
 {# Render single message #}
 {% macro draw_message_icon(message) -%}
 {% macro draw_message_icon(message) -%}
-  	<div class="alert-icon"><span><i class="icon-{% if message.type == 'error' -%}remove
-  		{%- elif message.type == 'success' -%}ok
-  		{%- elif message.type == 'info' -%}info-sign
+  	<div class="alert-icon"><span><i class="icon-{% if message.level == 'error' -%}remove
+  		{%- elif message.level == 'success' -%}ok
+  		{%- elif message.level == 'info' -%}info-sign
   		{%- else -%}warning-sign
   		{%- else -%}warning-sign
   		{%- endif %} icon-white"></i></span></div>
   		{%- endif %} icon-white"></i></span></div>
 {%- endmacro %}
 {%- endmacro %}

+ 2 - 2
templates/admin/signin.html

@@ -5,10 +5,10 @@
 {% block title %}{{ page_title(title=_('Sign In')) }}{% endblock %}
 {% block title %}{{ page_title(title=_('Sign In')) }}{% endblock %}
 
 
 {% block header %}<strong>Misago</strong> {% trans %}Board Administration{% endtrans %}{% endblock %}
 {% block header %}<strong>Misago</strong> {% trans %}Board Administration{% endtrans %}{% endblock %}
-      
+
 {% block content %}
 {% block content %}
           {% if message %}
           {% if message %}
-          <div class="alert alert-{{ message.type }} block-alert">
+          <div class="alert alert-{{ message.level }} block-alert">
             {{ draw_message_icon(message) }}
             {{ draw_message_icon(message) }}
 	        <p><strong>{{ message.message }}</strong></p>
 	        <p><strong>{{ message.message }}</strong></p>
           </div>
           </div>

+ 4 - 4
templates/cranefly/macros.html

@@ -20,16 +20,16 @@
 
 
 {# Render single message #}
 {# Render single message #}
 {% macro draw_message(message, class='') %}
 {% macro draw_message(message, class='') %}
-  <div class="alert alert-{{ message.type }}{% if class %} {{ class }}{% endif %}">
+  <div class="alert alert-{{ message.level }}{% if class %} {{ class }}{% endif %}">
   	{{ draw_message_icon(message) }} <p><strong>{{ message.message }}</strong></p>
   	{{ draw_message_icon(message) }} <p><strong>{{ message.message }}</strong></p>
   </div>
   </div>
 {%- endmacro %}
 {%- endmacro %}
 
 
 {# Render icon #}
 {# Render icon #}
 {% macro draw_message_icon(message) -%}
 {% macro draw_message_icon(message) -%}
-  	<div class="alert-icon"><span><i class="icon-{% if message.type == 'error' -%}remove
-  		{%- elif message.type == 'success' -%}ok
-  		{%- elif message.type == 'info' -%}info-sign
+  	<div class="alert-icon"><span><i class="icon-{% if message.level == 'error' -%}remove
+  		{%- elif message.level == 'success' -%}ok
+  		{%- elif message.level == 'info' -%}info-sign
   		{%- else -%}warning-sign
   		{%- else -%}warning-sign
   		{%- endif %} icon-white"></i></span></div>
   		{%- endif %} icon-white"></i></span></div>
 {%- endmacro %}
 {%- endmacro %}

+ 2 - 2
templates/cranefly/signin.html

@@ -3,7 +3,7 @@
 {% import "cranefly/macros.html" as macros with context %}
 {% import "cranefly/macros.html" as macros with context %}
 
 
 {% block title %}{{ macros.page_title(title=_('Sign In')) }}{% endblock %}
 {% block title %}{{ macros.page_title(title=_('Sign In')) }}{% endblock %}
-     
+
 {% block content %}
 {% block content %}
 <div class="row">
 <div class="row">
   <div class="span6 offset3">
   <div class="span6 offset3">
@@ -15,7 +15,7 @@
 
 
       {% if message %}
       {% if message %}
       <div class="messages-list">
       <div class="messages-list">
-        <div class="alert alert-{{ message.type }}">
+        <div class="alert alert-{{ message.level }}">
           {{ macros.draw_message_icon(message) }}
           {{ macros.draw_message_icon(message) }}
           <p><strong>{{ message.message }}</strong></p>
           <p><strong>{{ message.message }}</strong></p>
           {% if bad_password %}
           {% if bad_password %}