Browse Source

Threads functionality reimplemented

Ralfp 12 years ago
parent
commit
25b57cc6d4

+ 0 - 0
misago/apps/oldthreads/__init__.py


+ 0 - 225
misago/apps/oldthreads/forms.py

@@ -1,225 +0,0 @@
-from django import forms
-from django.conf import settings
-from django.utils.translation import ungettext, ugettext_lazy as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.forms import Form, ForumChoiceField
-from misago.models import Forum, Thread
-from misago.utils.strings import slugify
-from misago.validators import validate_sluggable
-
-class ThreadNameMixin(object):
-    def clean_thread_name(self):
-        data = self.cleaned_data['thread_name']
-        slug = slugify(data)
-        if len(slug) < self.request.settings['thread_name_min']:
-            raise forms.ValidationError(ungettext(
-                                                  "Thread name must contain at least one alpha-numeric character.",
-                                                  "Thread name must contain at least %(count)d alpha-numeric characters.",
-                                                  self.request.settings['thread_name_min']
-                                                  ) % {'count': self.request.settings['thread_name_min']})
-        if len(data) > self.request.settings['thread_name_max']:
-            raise forms.ValidationError(ungettext(
-                                                  "Thread name cannot be longer than %(count)d character.",
-                                                  "Thread name cannot be longer than %(count)d characters.",
-                                                  self.request.settings['thread_name_max']
-                                                  ) % {'count': self.request.settings['thread_name_max']})
-        return data
-
-
-class PostForm(Form, ThreadNameMixin):
-    post = forms.CharField(widget=forms.Textarea)
-
-    def __init__(self, data=None, file=None, request=None, mode=None, *args, **kwargs):
-        self.mode = mode
-        super(PostForm, self).__init__(data, file, request=request, *args, **kwargs)
-
-    def finalize_form(self):
-        self.layout = [
-                       [
-                        None,
-                        [
-                         ('thread_name', {'label': _("Thread Name")}),
-                         ('edit_reason', {'label': _("Edit Reason")}),
-                         ('post', {'label': _("Post Content")}),
-                         ],
-                        ],
-                       ]
-
-        if self.mode in ['edit_thread', 'edit_post']:
-            self.fields['edit_reason'] = forms.CharField(max_length=255, required=False, help_text=_("Optional reason for changing this post."))
-        else:
-            del self.layout[0][1][1]
-
-        if self.mode not in ['edit_thread', 'new_thread']:
-            del self.layout[0][1][0]
-        else:
-            self.fields['thread_name'] = forms.CharField(
-                                                         max_length=self.request.settings['thread_name_max'],
-                                                         validators=[validate_sluggable(
-                                                                                        _("Thread name must contain at least one alpha-numeric character."),
-                                                                                        _("Thread name is too long. Try shorter name.")
-                                                                                        )])
-
-    def clean_post(self):
-        data = self.cleaned_data['post']
-        if len(data) < self.request.settings['post_length_min']:
-            raise forms.ValidationError(ungettext(
-                                                  "Post content cannot be empty.",
-                                                  "Post content cannot be shorter than %(count)d characters.",
-                                                  self.request.settings['post_length_min']
-                                                  ) % {'count': self.request.settings['post_length_min']})
-        return data
-
-
-
-class SplitThreadForm(Form, ThreadNameMixin):
-    def finalize_form(self):
-        self.layout = [
-                       [
-                        None,
-                        [
-                         ('thread_name', {'label': _("New Thread Name")}),
-                         ('thread_forum', {'label': _("New Thread Forum")}),
-                         ],
-                        ],
-                       ]
-
-        self.fields['thread_name'] = forms.CharField(
-                                                     max_length=self.request.settings['thread_name_max'],
-                                                     validators=[validate_sluggable(
-                                                                                    _("Thread name must contain at least one alpha-numeric character."),
-                                                                                    _("Thread name is too long. Try shorter name.")
-                                                                                    )])
-        self.fields['thread_forum'] = ForumChoiceField(queryset=Forum.tree.get(special='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']))
-
-    def clean_thread_forum(self):
-        new_forum = self.cleaned_data['thread_forum']
-        # Assert its forum and its not current forum
-        if new_forum.type != 'forum':
-            raise forms.ValidationError(_("This is not a forum."))
-        return new_forum
-
-
-class MovePostsForm(Form, ThreadNameMixin):
-    error_source = 'thread_url'
-
-    def __init__(self, data=None, request=None, thread=None, *args, **kwargs):
-        self.thread = thread
-        super(MovePostsForm, self).__init__(data, request=request, *args, **kwargs)
-
-    def finalize_form(self):
-        self.layout = [
-                       [
-                        None,
-                        [
-                         ('thread_url', {'label': _("New Thread Link"), 'help_text': _("To select new thread, simply copy and paste here its link.")}),
-                         ],
-                        ],
-                       ]
-
-        self.fields['thread_url'] = forms.CharField()
-
-    def clean_thread_url(self):
-        from django.core.urlresolvers import resolve
-        from django.http import Http404
-        thread_url = self.cleaned_data['thread_url']
-        try:
-            thread_url = thread_url[len(settings.BOARD_ADDRESS):]
-            match = resolve(thread_url)
-            thread = Thread.objects.get(pk=match.kwargs['thread'])
-            self.request.acl.threads.allow_thread_view(self.request.user, thread)
-            if thread.pk == self.thread.pk:
-                raise forms.ValidationError(_("New thread is same as current one."))
-            return thread
-        except (Http404, KeyError):
-            raise forms.ValidationError(_("This is not a correct thread URL."))
-        except (Thread.DoesNotExist, ACLError403, ACLError404):
-            raise forms.ValidationError(_("Thread could not be found."))
-
-
-class QuickReplyForm(Form):
-    post = forms.CharField(widget=forms.Textarea)
-
-
-class MoveThreadsForm(Form):
-    error_source = 'new_forum'
-
-    def __init__(self, data=None, request=None, forum=None, *args, **kwargs):
-        self.forum = forum
-        super(MoveThreadsForm, self).__init__(data, request=request, *args, **kwargs)
-
-    def finalize_form(self):
-        self.fields['new_forum'] = ForumChoiceField(queryset=Forum.tree.get(special='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']))
-        self.layout = [
-                       [
-                        None,
-                        [
-                         ('new_forum', {'label': _("Move Threads to"), 'help_text': _("Select forum you want to move threads to.")}),
-                         ],
-                        ],
-                       ]
-
-    def clean_new_forum(self):
-        new_forum = self.cleaned_data['new_forum']
-        # Assert its forum and its not current forum
-        if new_forum.type != 'forum':
-            raise forms.ValidationError(_("This is not forum."))
-        if new_forum.pk == self.forum.pk:
-            raise forms.ValidationError(_("New forum is same as current one."))
-        return new_forum
-
-
-class MergeThreadsForm(Form, ThreadNameMixin):
-    def __init__(self, data=None, request=None, threads=[], *args, **kwargs):
-        self.threads = threads
-        super(MergeThreadsForm, self).__init__(data, request=request, *args, **kwargs)
-
-    def finalize_form(self):
-        self.fields['new_forum'] = ForumChoiceField(queryset=Forum.tree.get(special='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']), initial=self.threads[0].forum)
-        self.fields['thread_name'] = forms.CharField(
-                                                     max_length=self.request.settings['thread_name_max'],
-                                                     initial=self.threads[0].name,
-                                                     validators=[validate_sluggable(
-                                                                                    _("Thread name must contain at least one alpha-numeric character."),
-                                                                                    _("Thread name is too long. Try shorter name.")
-                                                                                    )])
-        self.layout = [
-                       [
-                        None,
-                        [
-                         ('thread_name', {'label': _("Thread Name"), 'help_text': _("Name of new thread that will be created as result of merge.")}),
-                         ('new_forum', {'label': _("Thread Forum"), 'help_text': _("Select forum you want to put new thread in.")}),
-                         ],
-                        ],
-                       [
-                        _("Merge Order"),
-                        [
-                         ],
-                        ],
-                       ]
-
-        choices = []
-        for i, thread in enumerate(self.threads):
-            choices.append((str(i), i + 1))
-        for i, thread in enumerate(self.threads):
-            self.fields['thread_%s' % thread.pk] = forms.ChoiceField(choices=choices, initial=str(i))
-            self.layout[1][1].append(('thread_%s' % thread.pk, {'label': thread.name}))
-
-    def clean_new_forum(self):
-        new_forum = self.cleaned_data['new_forum']
-        # Assert its forum
-        if new_forum.type != 'forum':
-            raise forms.ValidationError(_("This is not forum."))
-        return new_forum
-
-    def clean(self):
-        cleaned_data = super(MergeThreadsForm, self).clean()
-        self.merge_order = {}
-        lookback = []
-        for thread in self.threads:
-            order = int(cleaned_data['thread_%s' % thread.pk])
-            if order in lookback:
-                raise forms.ValidationError(_("One or more threads have same position in merge order."))
-            lookback.append(order)
-            self.merge_order[order] = thread
-        return cleaned_data

+ 0 - 34
misago/apps/oldthreads/urls.py

@@ -1,34 +0,0 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.threads.views',
-    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'ThreadsView', name="forum"),
-    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/(?P<page>\d+)/$', 'ThreadsView', name="forum"),
-    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/new/$', 'PostingNewThreadView', name="thread_new"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'ThreadView', name="thread"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'LastReplyView', name="thread_last"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'FindReplyView', name="thread_find"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'NewReplyView', name="thread_new"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/moderated/$', 'FirstModeratedView', name="thread_moderated"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'FirstReportedView', name="thread_reported"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'ShowHiddenRepliesView', name="thread_show_hidden"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'WatchThreadView', name="thread_watch"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'WatchEmailThreadView', name="thread_watch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'UnwatchThreadView', name="thread_unwatch"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'UnwatchEmailThreadView', name="thread_unwatch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>\d+)/$', 'ThreadView', name="thread"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'PostingView', name="thread_reply", kwargs={'mode': 'new_post'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'PostingNewReplyView', name="thread_reply"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'PostingEditThreadView', name="thread_edit"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'PostingEditReplyView', name="post_edit"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'DeleteView', name="thread_delete", kwargs={'mode': 'delete_thread'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'DeleteView', name="thread_hide", kwargs={'mode': 'hide_thread'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'DeleteView', name="post_delete", kwargs={'mode': 'delete_post'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'DeleteView', name="post_hide", kwargs={'mode': 'hide_post'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'DetailsView', name="post_info"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/upvote/$', 'UpvotePostView', name="post_upvote"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', 'DownvotePostView', name="post_downvote"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/votes/$', 'KarmaVotesView', name="post_votes"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'ChangelogView', name="changelog"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'ChangelogDiffView', name="changelog_diff"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'ChangelogRevertView', name="changelog_revert"),
-)

+ 0 - 8
misago/apps/oldthreads/views/__init__.py

@@ -1,8 +0,0 @@
-from misago.apps.threads.views.list import *
-from misago.apps.threads.views.jumps import *
-from misago.apps.threads.views.thread import *
-from misago.apps.threads.views.delete import *
-from misago.apps.threads.views.karmas import *
-from misago.apps.threads.views.posting import *
-from misago.apps.threads.views.details import *
-from misago.apps.threads.views.changelog import *

+ 0 - 14
misago/apps/oldthreads/views/base.py

@@ -1,14 +0,0 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from misago.utils.pagination import make_pagination
-
-class BaseView(object):
-    def __new__(cls, request, **kwargs):
-        obj = super(BaseView, cls).__new__(cls)
-        return obj(request, **kwargs)
-
-    def redirect_to_post(self, post):
-        pagination = make_pagination(0, self.request.acl.threads.filter_posts(self.request, self.thread, self.thread.post_set).filter(id__lte=post.pk).count(), self.request.settings.posts_per_page)
-        if pagination['total'] > 1:
-            return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % post.pk))
-        return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % post.pk))

+ 0 - 125
misago/apps/oldthreads/views/changelog.py

@@ -1,125 +0,0 @@
-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.utils.datesformats import reldate
-from misago.utils.strings import slugify
-from misago.utils.pagination import make_pagination
-from misago.apps.threads.views.base import BaseView
-
-class ChangelogBaseView(BaseView):
-    def fetch_target(self, kwargs):
-        self.thread = Thread.objects.get(pk=kwargs['thread'])
-        self.forum = self.thread.forum
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-        self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-        self.post = Post.objects.select_related('user').get(pk=kwargs['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 dispatch(self, request, **kwargs):
-        raise NotImplementedError('ChangelogBaseView cannot be called directly. Did you forget to define custom "dispatch" method?')
-
-    def __call__(self, request, **kwargs):
-        self.request = request
-        self.forum = None
-        self.thread = None
-        self.post = None
-        try:
-            self.fetch_target(kwargs)
-            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.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-        return self.dispatch(request, **kwargs)
-
-
-class ChangelogView(ChangelogBaseView):
-    def dispatch(self, request, **kwargs):
-        return request.theme.render_to_response('threads/changelog.html',
-                                                {
-                                                 '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 ChangelogDiffView(ChangelogBaseView):
-    def fetch_target(self, kwargs):
-        super(ChangelogDiffView, self).fetch_target(kwargs)
-        self.change = self.post.change_set.get(pk=kwargs['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 request.theme.render_to_response('threads/changelog_diff.html',
-                                                {
-                                                 '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 ChangelogRevertView(ChangelogDiffView):
-    def fetch_target(self, kwargs):
-        super(ChangelogDiffView, self).fetch_target(kwargs)
-        self.change = self.post.change_set.get(pk=kwargs['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('changelog_diff', 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(request, 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)
-        pagination = make_pagination(0, request.acl.threads.filter_posts(self.request, self.thread, self.thread.post_set).filter(id__lte=self.post.pk).count(), self.request.settings.posts_per_page)
-        if pagination['total'] > 1:
-            return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % self.post.pk))
-        return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))

+ 0 - 105
misago/apps/oldthreads/views/delete.py

@@ -1,105 +0,0 @@
-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.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.messages import Message
-from misago.models import Forum, Thread, Post
-from misago.utils.pagination import make_pagination
-from misago.apps.threads.views.base import BaseView
-
-class DeleteView(BaseView):
-    def fetch_thread(self, kwargs):
-        self.thread = Thread.objects.get(pk=kwargs['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)
-        if self.mode in ['tread_delete', 'hide_thread']:
-            self.request.acl.threads.allow_delete_thread(
-                                                         self.request.user,
-                                                         self.forum,
-                                                         self.thread,
-                                                         self.thread.start_post,
-                                                         self.mode == 'delete_thread')
-            # 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 fetch_post(self, kwargs):
-        self.post = self.thread.post_set.get(pk=kwargs['post'])
-        if self.post.pk == self.thread.start_post_id:
-            raise Post.DoesNotExist()
-        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
-        self.request.acl.threads.allow_delete_post(
-                                                   self.request.user,
-                                                   self.forum,
-                                                   self.thread,
-                                                   self.post,
-                                                   self.mode == 'delete_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 __call__(self, request, **kwargs):
-        self.request = request
-        self.mode = kwargs['mode']
-        try:
-            if not request.user.is_authenticated():
-                raise ACLError403(_("Guest, you have to sign-in in order to be able to delete replies."))
-            self.fetch_thread(kwargs)
-            if self.mode in ['hide_post', 'delete_post']:
-                self.fetch_post(kwargs)
-        except (Thread.DoesNotExist, Post.DoesNotExist):
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-
-        if self.mode == 'delete_thread':
-            self.thread.delete()
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
-            return redirect(reverse('forum', kwargs={'forum': self.thread.forum.pk, 'slug': self.thread.forum.slug}))
-
-        if self.mode == 'hide_thread':
-            self.thread.start_post.deleted = True
-            self.thread.start_post.save(force_update=True)
-            self.thread.last_post.set_checkpoint(request, 'deleted')
-            self.thread.last_post.save(force_update=True)
-            self.thread.sync()
-            self.thread.save(force_update=True)
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
-            if request.acl.threads.can_see_deleted_threads(self.thread.forum):
-                return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-            return redirect(reverse('forum', kwargs={'forum': self.thread.forum.pk, 'slug': self.thread.forum.slug}))
-
-        if self.mode == 'delete_post':
-            self.post.delete()
-            self.thread.sync()
-            self.thread.save(force_update=True)
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            request.messages.set_flash(Message(_("Selected Reply has been deleted.")), 'success', 'threads')
-            return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-
-        if self.mode == 'hide_post':
-            self.post.deleted = True
-            self.post.edit_date = timezone.now()
-            self.post.edit_user = request.user
-            self.post.edit_user_name = request.user.username
-            self.post.edit_user_slug = request.user.username_slug
-            self.post.save(force_update=True)
-            self.thread.sync()
-            self.thread.save(force_update=True)
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            request.messages.set_flash(Message(_("Selected Reply has been deleted.")), 'success', 'threads_%s' % self.post.pk)
-            return self.redirect_to_post(self.post)

+ 0 - 40
misago/apps/oldthreads/views/details.py

@@ -1,40 +0,0 @@
-from django.template import RequestContext
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.models import Forum, Thread, Post
-from misago.apps.threads.views.base import BaseView
-
-class DetailsView(BaseView):
-    def fetch_target(self, kwargs):
-        self.thread = Thread.objects.get(pk=kwargs['thread'])
-        self.forum = self.thread.forum
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-        self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-        self.post = Post.objects.select_related('user').get(pk=kwargs['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.users.allow_details_view()
-
-    def __call__(self, request, **kwargs):
-        self.request = request
-        self.forum = None
-        self.thread = None
-        self.post = None
-        try:
-            self.fetch_target(kwargs)
-        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist):
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-        return request.theme.render_to_response('threads/details.html',
-                                                {
-                                                 'forum': self.forum,
-                                                 'parents': self.parents,
-                                                 'thread': self.thread,
-                                                 'post': self.post,
-                                                 },
-                                                context_instance=RequestContext(request))

+ 0 - 239
misago/apps/oldthreads/views/jumps.py

@@ -1,239 +0,0 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect as django_redirect
-from django.template import RequestContext
-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.decorators import block_guest, check_csrf
-from misago.messages import Message
-from misago.models import Forum, Thread, Post, Karma, WatchedThread
-from misago.readstrackers import ThreadsTracker
-from misago.utils.pagination import make_pagination
-from misago.apps.threads.views.base import ViewBase
-
-class JumpViewBase(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 redirect_to_post(self, post):
-        pagination = make_pagination(0, self.request.acl.threads.filter_posts(self.request, self.thread, self.thread.post_set.filter(date__lt=post.date)).count() + 1, self.request.settings.posts_per_page)
-        if pagination['total'] > 1:
-            return django_redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % post.pk))
-        return django_redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % post.pk))
-
-    def make_jump(self):
-        raise NotImplementedError('JumpView cannot be called directly.')
-
-    def __call__(self, request, slug=None, thread=None, post=None):
-        self.request = request
-        try:
-            self.fetch_thread(thread)
-            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.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-
-
-class LastReplyView(JumpView):
-    def make_jump(self):
-        return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
-
-
-class FindReplyView(JumpView):
-    def make_jump(self):
-        return self.redirect_to_post(self.post)
-
-
-class NewReplyView(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 FirstModeratedView(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 FirstReportedView(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 ShowHiddenRepliesView(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('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
-        return view(self.request)
-
-
-class WatchThreadView(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.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 WatchEmailThreadView(WatchThreadView):
-    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 UnwatchThreadView(WatchThreadView):
-    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 UnwatchEmailThreadView(WatchThreadView):
-    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 UpvotePostView(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)
-            request.messages.set_flash(Message(_('Your vote has been saved.')), 'success', 'threads_%s' % self.post.pk)
-            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 += request.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 -= request.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)
-            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 DownvotePostView(UpvotePostView):
-    def check_acl(self, request):
-        request.acl.threads.allow_post_downvote(self.forum)
-    
-    def make_vote(self, request, vote):
-        vote.score = -1

+ 0 - 42
misago/apps/oldthreads/views/karmas.py

@@ -1,42 +0,0 @@
-from django.template import RequestContext
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.models import Forum, Thread, Post
-from misago.apps.threads.views.base import BaseView
-
-class KarmaVotesView(BaseView):
-    def fetch_target(self, kwargs):
-        self.thread = Thread.objects.get(pk=kwargs['thread'])
-        self.forum = self.thread.forum
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-        self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-        self.post = Post.objects.select_related('user').get(pk=kwargs['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_post_votes_view(self.forum)
-
-    def __call__(self, request, **kwargs):
-        self.request = request
-        self.forum = None
-        self.thread = None
-        self.post = None
-        try:
-            self.fetch_target(kwargs)
-        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist):
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-        return request.theme.render_to_response('threads/karmas.html',
-                                                {
-                                                 'forum': self.forum,
-                                                 'parents': self.parents,
-                                                 'thread': self.thread,
-                                                 'post': self.post,
-                                                 'upvotes': self.post.karma_set.filter(score=1),
-                                                 'downvotes': self.post.karma_set.filter(score=-1),
-                                                 },
-                                                context_instance=RequestContext(request))

+ 0 - 392
misago/apps/oldthreads/views/list.py

@@ -1,392 +0,0 @@
-from django.core.urlresolvers import reverse
-from django.db.models import Q
-from django import forms
-from django.forms import ValidationError
-from django.shortcuts import redirect
-from django.template import RequestContext
-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.forms import Form, FormLayout, FormFields
-from misago.messages import Message
-from misago.models import Forum, Thread, Post
-from misago.readstrackers import ForumsTracker, ThreadsTracker
-from misago.utils.strings import slugify
-from misago.utils.pagination import make_pagination
-from misago.apps.threads.forms import MoveThreadsForm, MergeThreadsForm
-from misago.apps.threads.views.base import BaseView
-
-class ThreadsView(BaseView):
-    def fetch_forum(self, forum):
-        self.forum = Forum.objects.get(pk=forum, type='forum')
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.parents = Forum.objects.forum_parents(self.forum.pk)
-        if self.forum.lft + 1 != self.forum.rght:
-            self.forum.subforums = Forum.objects.treelist(self.request.acl.forums, self.forum, tracker=ForumsTracker(self.request.user))
-        self.tracker = ThreadsTracker(self.request, self.forum)
-
-    def fetch_threads(self, page):
-        self.count = self.request.acl.threads.filter_threads(self.request, self.forum, Thread.objects.filter(forum=self.forum).filter(weight__lt=2)).count()
-        self.pagination = make_pagination(page, self.count, self.request.settings.threads_per_page)
-        self.threads = []
-        ignored_users = []
-        queryset_anno = Thread.objects.filter(Q(forum=Forum.objects.special_pk('announcements')) | (Q(forum=self.forum) & Q(weight=2)))
-        queryset_threads = self.request.acl.threads.filter_threads(self.request, self.forum, Thread.objects.filter(forum=self.forum).filter(weight__lt=2)).order_by('-weight', '-last')
-        if self.request.user.is_authenticated():
-            ignored_users = self.request.user.ignored_users()
-            if ignored_users:
-                queryset_threads = queryset_threads.extra(where=["`threads_thread`.`start_poster_id` IS NULL OR `threads_thread`.`start_poster_id` NOT IN (%s)" % ','.join([str(i) for i in ignored_users])])
-        if self.request.settings.avatars_on_threads_list:
-            queryset_anno = queryset_anno.prefetch_related('start_poster', 'last_post')
-            queryset_threads = queryset_threads.prefetch_related('start_poster', 'last_poster')
-        for thread in queryset_anno:
-            self.threads.append(thread)
-        for thread in queryset_threads:
-            self.threads.append(thread)
-        if self.request.settings.threads_per_page < self.count:
-            self.threads = self.threads[self.pagination['start']:self.pagination['stop']]
-        for thread in self.threads:
-            thread.is_read = self.tracker.is_read(thread)
-            thread.last_poster_ignored = thread.last_poster_id in ignored_users
-
-    def get_thread_actions(self):
-        acl = self.request.acl.threads.get_role(self.forum)
-        actions = []
-        try:
-            if acl['can_approve']:
-                actions.append(('accept', _('Accept threads')))
-            if acl['can_pin_threads'] == 2:
-                actions.append(('annouce', _('Change to announcements')))
-            if acl['can_pin_threads'] > 0:
-                actions.append(('sticky', _('Change to sticky threads')))
-            if acl['can_pin_threads'] > 0:
-                actions.append(('normal', _('Change to standard thread')))
-            if acl['can_move_threads_posts']:
-                actions.append(('move', _('Move threads')))
-                actions.append(('merge', _('Merge threads')))
-            if acl['can_close_threads']:
-                actions.append(('open', _('Open threads')))
-                actions.append(('close', _('Close threads')))
-            if acl['can_delete_threads']:
-                actions.append(('undelete', _('Undelete threads')))
-                actions.append(('soft', _('Soft delete threads')))
-            if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Hard delete threads')))
-        except KeyError:
-            pass
-        return actions
-
-    def make_form(self):
-        self.form = None
-        list_choices = self.get_thread_actions();
-        if (not self.request.user.is_authenticated()
-            or not list_choices):
-            return
-
-        form_fields = {}
-        form_fields['list_action'] = forms.ChoiceField(choices=list_choices)
-        list_choices = []
-        for item in self.threads:
-            if item.forum_id == self.forum.pk:
-                list_choices.append((item.pk, None))
-        if not list_choices:
-            return
-        form_fields['list_items'] = forms.MultipleChoiceField(choices=list_choices, widget=forms.CheckboxSelectMultiple)
-        self.form = type('ThreadsViewForm', (Form,), form_fields)
-
-    def handle_form(self):
-        if self.request.method == 'POST':
-            self.form = self.form(self.request.POST, request=self.request)
-            if self.form.is_valid():
-                checked_items = []
-                posts = []
-                for thread in self.threads:
-                    if str(thread.pk) in self.form.cleaned_data['list_items'] and thread.forum_id == self.forum.pk:
-                        posts.append(thread.start_post_id)
-                        if thread.start_post_id != thread.last_post_id:
-                            posts.append(thread.last_post_id)
-                        checked_items.append(thread.pk)
-                if checked_items:
-                    if posts:
-                        for post in Post.objects.filter(id__in=posts).prefetch_related('user'):
-                            for thread in self.threads:
-                                if thread.start_post_id == post.pk:
-                                    thread.start_post = post
-                                if thread.last_post_id == post.pk:
-                                    thread.last_post = post
-                                if thread.start_post_id == post.pk or thread.last_post_id == post.pk:
-                                    break
-                    form_action = getattr(self, 'action_' + self.form.cleaned_data['list_action'])
-                    try:
-                        response = form_action(checked_items)
-                        if response:
-                            return response
-                        return redirect(self.request.path)
-                    except forms.ValidationError as e:
-                        self.message = Message(e.messages[0], 'error')
-                else:
-                    self.message = Message(_("You have to select at least one thread."), 'error')
-            else:
-                if 'list_action' in self.form.errors:
-                    self.message = Message(_("Action requested is incorrect."), 'error')
-                else:
-                    self.message = Message(form.non_field_errors()[0], 'error')
-        else:
-            self.form = self.form(request=self.request)
-
-    def action_accept(self, ids):
-        accepted = 0
-        last_posts = []
-        users = []
-        for thread in self.threads:
-            if thread.pk in ids and thread.moderated:
-                accepted += 1
-                # Sync thread and post
-                thread.moderated = False
-                thread.replies_moderated -= 1
-                thread.save(force_update=True)
-                thread.start_post.moderated = False
-                thread.start_post.save(force_update=True)
-                thread.last_post.set_checkpoint(self.request, 'accepted')
-                last_posts.append(thread.last_post.pk)
-                # Sync user
-                if thread.last_post.user:
-                    thread.start_post.user.threads += 1
-                    thread.start_post.user.posts += 1
-                    users.append(thread.start_post.user)
-        if accepted:
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
-            self.request.monitor['threads'] = int(self.request.monitor['threads']) + accepted
-            self.request.monitor['posts'] = int(self.request.monitor['posts']) + accepted
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            for user in users:
-                user.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected threads have been marked as reviewed and made visible to other members.')), 'success', 'threads')
-
-    def action_annouce(self, ids):
-        acl = self.request.acl.threads.get_role(self.forum)
-        annouced = []
-        for thread in self.threads:
-            if thread.pk in ids and thread.weight < 2:
-                annouced.append(thread.pk)
-        if annouced:
-            Thread.objects.filter(id__in=annouced).update(weight=2)
-            self.request.messages.set_flash(Message(_('Selected threads have been turned into announcements.')), 'success', 'threads')
-
-    def action_sticky(self, ids):
-        acl = self.request.acl.threads.get_role(self.forum)
-        sticky = []
-        for thread in self.threads:
-            if thread.pk in ids and thread.weight != 1 and (acl['can_pin_threads'] == 2 or thread.weight < 2):
-                sticky.append(thread.pk)
-        if sticky:
-            Thread.objects.filter(id__in=sticky).update(weight=1)
-            self.request.messages.set_flash(Message(_('Selected threads have been sticked to the top of list.')), 'success', 'threads')
-
-    def action_normal(self, ids):
-        normalised = []
-        for thread in self.threads:
-            if thread.pk in ids and thread.weight > 0:
-                normalised.append(thread.pk)
-        if normalised:
-            Thread.objects.filter(id__in=normalised).update(weight=0)
-            self.request.messages.set_flash(Message(_('Selected threads weight has been removed.')), 'success', 'threads')
-
-    def action_move(self, ids):
-        threads = []
-        for thread in self.threads:
-            if thread.pk in ids:
-                threads.append(thread)
-        if self.request.POST.get('origin') == 'move_form':
-            form = MoveThreadsForm(self.request.POST, request=self.request, forum=self.forum)
-            if form.is_valid():
-                new_forum = form.cleaned_data['new_forum']
-                for thread in threads:
-                    thread.move_to(new_forum)
-                    thread.save(force_update=True)
-                new_forum.sync()
-                new_forum.save(force_update=True)
-                self.forum.sync()
-                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')
-                return None
-            self.message = Message(form.non_field_errors()[0], 'error')
-        else:
-            form = MoveThreadsForm(request=self.request, forum=self.forum)
-        return self.request.theme.render_to_response('threads/move_threads.html',
-                                                     {
-                                                      'message': self.message,
-                                                      'forum': self.forum,
-                                                      'parents': self.parents,
-                                                      'threads': threads,
-                                                      'form': FormLayout(form),
-                                                      },
-                                                     context_instance=RequestContext(self.request));
-
-    def action_merge(self, ids):
-        if len(ids) < 2:
-            raise ValidationError(_("You have to pick two or more threads to merge."))
-        threads = []
-        for thread in self.threads:
-            if thread.pk in ids:
-                threads.append(thread)
-        if self.request.POST.get('origin') == 'merge_form':
-            form = MergeThreadsForm(self.request.POST, request=self.request, threads=threads)
-            if form.is_valid():
-                new_thread = Thread.objects.create(
-                                                   forum=form.cleaned_data['new_forum'],
-                                                   name=form.cleaned_data['thread_name'],
-                                                   slug=slugify(form.cleaned_data['thread_name']),
-                                                   start=timezone.now(),
-                                                   last=timezone.now()
-                                                   )
-                last_merge = 0
-                last_thread = None
-                merged = []
-                for i in range(0, len(threads)):
-                    thread = form.merge_order[i]
-                    merged.append(thread.pk)
-                    if last_thread and last_thread.last > thread.start:
-                        last_merge += thread.merges + 1
-                    thread.merge_with(new_thread, last_merge=last_merge)
-                    last_thread = thread
-                Thread.objects.filter(id__in=merged).delete()
-                new_thread.sync()
-                new_thread.save(force_update=True)
-                self.forum.sync()
-                self.forum.save(force_update=True)
-                if form.cleaned_data['new_forum'].pk != self.forum.pk:
-                    form.cleaned_data['new_forum'].sync()
-                    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')
-                return None
-            self.message = Message(form.non_field_errors()[0], 'error')
-        else:
-            form = MergeThreadsForm(request=self.request, threads=threads)
-        return self.request.theme.render_to_response('threads/merge.html',
-                                                     {
-                                                      'message': self.message,
-                                                      'forum': self.forum,
-                                                      'parents': self.parents,
-                                                      'threads': threads,
-                                                      'form': FormLayout(form),
-                                                      },
-                                                     context_instance=RequestContext(self.request));
-
-    def action_open(self, ids):
-        opened = []
-        last_posts = []
-        for thread in self.threads:
-            if thread.pk in ids and thread.closed:
-                opened.append(thread.pk)
-                thread.last_post.set_checkpoint(self.request, 'opened')
-                last_posts.append(thread.last_post.pk)
-        if opened:
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
-            Thread.objects.filter(id__in=opened).update(closed=False)
-            self.request.messages.set_flash(Message(_('Selected threads have been opened.')), 'success', 'threads')
-
-    def action_close(self, ids):
-        closed = []
-        last_posts = []
-        for thread in self.threads:
-            if thread.pk in ids and not thread.closed:
-                closed.append(thread.pk)
-                thread.last_post.set_checkpoint(self.request, 'closed')
-                last_posts.append(thread.last_post.pk)
-        if closed:
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
-            Thread.objects.filter(id__in=closed).update(closed=True)
-            self.request.messages.set_flash(Message(_('Selected threads have been closed.')), 'success', 'threads')
-
-    def action_undelete(self, ids):
-        undeleted = []
-        last_posts = []
-        posts = 0
-        for thread in self.threads:
-            if thread.pk in ids and thread.deleted:
-                undeleted.append(thread.pk)
-                posts += thread.replies + 1
-                thread.start_post.deleted = False
-                thread.start_post.save(force_update=True)
-                thread.last_post.set_checkpoint(self.request, 'undeleted')
-        if undeleted:
-            self.request.monitor['threads'] = int(self.request.monitor['threads']) + len(undeleted)
-            self.request.monitor['posts'] = int(self.request.monitor['posts']) + posts
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
-            Thread.objects.filter(id__in=undeleted).update(deleted=False)
-            self.request.messages.set_flash(Message(_('Selected threads have been undeleted.')), 'success', 'threads')
-
-    def action_soft(self, ids):
-        deleted = []
-        last_posts = []
-        posts = 0
-        for thread in self.threads:
-            if thread.pk in ids and not thread.deleted:
-                deleted.append(thread.pk)
-                posts += thread.replies + 1
-                thread.start_post.deleted = True
-                thread.start_post.save(force_update=True)
-                thread.last_post.set_checkpoint(self.request, 'deleted')
-                last_posts.append(thread.last_post.pk)
-        if deleted:
-            self.request.monitor['threads'] = int(self.request.monitor['threads']) - len(deleted)
-            self.request.monitor['posts'] = int(self.request.monitor['posts']) - posts
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
-            Thread.objects.filter(id__in=deleted).update(deleted=True)
-            self.request.messages.set_flash(Message(_('Selected threads have been softly deleted.')), 'success', 'threads')
-
-    def action_hard(self, ids):
-        deleted = []
-        posts = 0
-        for thread in self.threads:
-            if thread.pk in ids:
-                deleted.append(thread.pk)
-                posts += thread.replies + 1
-                thread.delete()
-        if deleted:
-            self.request.monitor['threads'] = int(self.request.monitor['threads']) - len(deleted)
-            self.request.monitor['posts'] = int(self.request.monitor['posts']) - posts
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected threads have been deleted.')), 'success', 'threads')
-
-    def __call__(self, request, slug=None, forum=None, page=0):
-        self.request = request
-        self.pagination = None
-        self.parents = None
-        self.message = request.messages.get_message('threads')
-        try:
-            self.fetch_forum(forum)
-            self.fetch_threads(page)
-            self.make_form()
-            if self.form:
-                response = self.handle_form()
-                if response:
-                    return response
-        except Forum.DoesNotExist:
-            return error404(request)
-        except ACLError403 as e:
-            return error403(request, e.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-        # Merge proxy into forum
-        self.forum.closed = self.proxy.closed
-        return request.theme.render_to_response('threads/list.html',
-                                                {
-                                                 'message': self.message,
-                                                 'forum': self.forum,
-                                                 'parents': self.parents,
-                                                 'count': self.count,
-                                                 'list_form': FormFields(self.form).fields if self.form else None,
-                                                 'threads': self.threads,
-                                                 'pagination': self.pagination,
-                                                 },
-                                                context_instance=RequestContext(request));

+ 0 - 395
misago/apps/oldthreads/views/posting.py

@@ -1,395 +0,0 @@
-from datetime import timedelta
-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.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404
-from misago.forms import FormLayout
-from misago.markdown import post_markdown
-from misago.messages import Message
-from misago.models import Forum, Thread, Post, WatchedThread
-from misago.utils.datesformats import date
-from misago.utils.strings import slugify
-from misago.utils.pagination import make_pagination
-from misago.utils.translation import ugettext_lazy
-from misago.apps.threads.forms import PostForm
-from misago.apps.threads.views.base import BaseView
-
-class PostingView(BaseView):
-    def fetch_target(self, kwargs):
-        if self.mode == 'new_thread':
-            self.fetch_forum(kwargs)
-        else:
-            self.fetch_thread(kwargs)
-            if self.mode == 'edit_thread':
-                self.fetch_post(self.thread.start_post_id)
-            if self.mode == 'edit_post':
-                self.fetch_post(kwargs['post'])
-
-    def fetch_forum(self, kwargs):
-        self.forum = Forum.objects.get(pk=kwargs['forum'], type='forum')
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_new_threads(self.proxy)
-        self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-
-    def fetch_thread(self, kwargs):
-        self.thread = Thread.objects.get(pk=kwargs['thread'])
-        self.forum = self.thread.forum
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-        self.request.acl.threads.allow_reply(self.proxy, self.thread)
-        self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-        if kwargs.get('quote'):
-            self.quote = Post.objects.select_related('user').get(pk=kwargs['quote'], thread=self.thread.pk)
-            self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.quote)
-
-    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)
-        if self.mode == 'edit_thread':
-            self.request.acl.threads.allow_thread_edit(self.request.user, self.proxy, self.thread, self.post)
-        if self.mode == 'edit_post':
-            self.request.acl.threads.allow_reply_edit(self.request.user, self.proxy, self.thread, self.post)
-
-    def get_form(self, bound=False):
-        initial = {}
-        if self.mode == 'edit_thread':
-            initial['thread_name'] = self.thread.name
-        if self.mode in ['edit_thread', 'edit_post']:
-            initial['post'] = self.post.post
-        if self.quote:
-            quote_post = []
-            if self.quote.user:
-                quote_post.append('@%s' % self.quote.user.username)
-            else:
-                quote_post.append('@%s' % self.quote.user_name)
-            for line in self.quote.post.splitlines():
-                quote_post.append('> %s' % line)
-            quote_post.append('\n')
-            initial['post'] = '\n'.join(quote_post)
-
-        if bound:
-            return PostForm(self.request.POST, request=self.request, mode=self.mode, initial=initial)
-        return PostForm(request=self.request, mode=self.mode, initial=initial)
-
-    def __call__(self, request, **kwargs):
-        self.request = request
-        self.forum = None
-        self.thread = None
-        self.quote = None
-        self.post = None
-        self.parents = None
-        self.mode = kwargs.get('mode')
-        if self.request.POST.get('quick_reply') and self.mode == 'new_post':
-            self.mode = 'new_post_quick'
-        try:
-            self.fetch_target(kwargs)
-            if not request.user.is_authenticated():
-                raise ACLError403(_("Guest, you have to sign-in in order to post replies."))
-        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist):
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-
-        message = request.messages.get_message('threads')
-        if request.method == 'POST':
-            form = self.get_form(True)
-            # Show message preview
-            if 'preview' in request.POST:
-                if form['post'].value():
-                    md, preparsed = post_markdown(request, form['post'].value())
-                else:
-                    md, preparsed = None, None
-                form.empty_errors()
-                return request.theme.render_to_response('threads/posting.html',
-                                                        {
-                                                         'mode': self.mode,
-                                                         'forum': self.forum,
-                                                         'thread': self.thread,
-                                                         'post': self.post,
-                                                         'quote': self.quote,
-                                                         'parents': self.parents,
-                                                         'message': message,
-                                                         'preview': preparsed,
-                                                         'form': FormLayout(form),
-                                                         },
-                                                        context_instance=RequestContext(request));
-            # Commit form to database
-            if form.is_valid():                
-                # Record original vars if user is editing 
-                if self.mode in ['edit_thread', 'edit_post']:
-                    old_name = self.thread.name
-                    old_post = self.post.post
-                    # If there is no change, throw user back
-                    changed_name = (old_name != form.cleaned_data['thread_name']) if self.mode == 'edit_thread' else False
-                    changed_post = old_post != form.cleaned_data['post']
-                    changed_anything = changed_name or changed_post
-
-                # Some extra initialisation
-                now = timezone.now()
-                md = None
-                moderation = False
-                if not request.acl.threads.acl[self.forum.pk]['can_approve']:
-                    if self.mode == 'new_thread' and request.acl.threads.acl[self.forum.pk]['can_start_threads'] == 1:
-                        moderation = True
-                    if self.mode in ['new_post', 'new_post_quick'] and request.acl.threads.acl[self.forum.pk]['can_write_posts'] == 1:
-                        moderation = True
-
-                # Get or create new thread
-                if self.mode == 'new_thread':
-                    thread = Thread.objects.create(
-                                                   forum=self.forum,
-                                                   name=form.cleaned_data['thread_name'],
-                                                   slug=slugify(form.cleaned_data['thread_name']),
-                                                   start=now,
-                                                   last=now,
-                                                   moderated=moderation,
-                                                   score=request.settings['thread_ranking_initial_score'],
-                                                   )
-                    if moderation:
-                        thread.replies_moderated += 1
-                else:
-                    thread = self.thread
-                    if self.mode == 'edit_thread':
-                        thread.name = form.cleaned_data['thread_name']
-                        thread.slug = slugify(form.cleaned_data['thread_name'])
-                thread.previous_last = thread.last
-
-                # Create new message
-                if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
-                    # Use last post instead?
-                    if self.mode in ['new_post', 'new_post_quick']:
-                        merge_diff = (now - self.thread.last)
-                        merge_diff = (merge_diff.days * 86400) + merge_diff.seconds
-                    if (self.mode in ['new_post', 'new_post_quick']
-                        and request.settings.post_merge_time
-                        and merge_diff < (request.settings.post_merge_time * 60)
-                        and self.thread.last_poster_id == request.user.id):
-                        # Overtake posting
-                        post = self.thread.last_post
-                        post.appended = True
-                        post.moderated = moderation
-                        post.date = now
-                        post.post = '%s\n\n- - -\n**%s**\n%s' % (post.post, _("Added on %(date)s:") % {'date': date(now, 'SHORT_DATETIME_FORMAT')}, form.cleaned_data['post'])
-                        md, post.post_preparsed = post_markdown(request, post.post)
-                        post.save(force_update=True)
-                        thread.last = now
-                        thread.save(force_update=True)
-                        self.forum.last = now
-                        self.forum.save(force_update=True)
-                        # Ignore rest of posting action
-                        request.messages.set_flash(Message(_("Your reply has been added to previous one.")), 'success', 'threads_%s' % post.pk)
-                        return self.redirect_to_post(post)
-                    else:
-                        md, post_preparsed = post_markdown(request, form.cleaned_data['post'])
-                        post = Post.objects.create(
-                                                   forum=self.forum,
-                                                   thread=thread,
-                                                   merge=thread.merges,
-                                                   user=request.user,
-                                                   user_name=request.user.username,
-                                                   ip=request.session.get_ip(request),
-                                                   agent=request.META.get('HTTP_USER_AGENT'),
-                                                   post=form.cleaned_data['post'],
-                                                   post_preparsed=post_preparsed,
-                                                   date=now,
-                                                   moderated=moderation,
-                                                   )
-                        post.appended = False
-                elif changed_post:
-                    # Change message
-                    post = self.post
-                    post.post = form.cleaned_data['post']
-                    md, post.post_preparsed = post_markdown(request, form.cleaned_data['post'])
-                    post.edits += 1
-                    post.edit_date = now
-                    post.edit_user = request.user
-                    post.edit_user_name = request.user.username
-                    post.edit_user_slug = request.user.username_slug
-                    post.save(force_update=True)
-
-                # Record this edit in changelog?
-                if self.mode in ['edit_thread', 'edit_post'] and changed_anything:
-                    self.post.change_set.create(
-                                                forum=self.forum,
-                                                thread=self.thread,
-                                                post=self.post,
-                                                user=request.user,
-                                                user_name=request.user.username,
-                                                user_slug=request.user.username_slug,
-                                                date=now,
-                                                ip=request.session.get_ip(request),
-                                                agent=request.META.get('HTTP_USER_AGENT'),
-                                                reason=form.cleaned_data['edit_reason'],
-                                                size=len(self.post.post),
-                                                change=len(self.post.post) - len(old_post),
-                                                thread_name_old=old_name if self.mode == 'edit_thread' and form.cleaned_data['thread_name'] != old_name else None,
-                                                thread_name_new=self.thread.name if self.mode == 'edit_thread' and form.cleaned_data['thread_name'] != old_name else None,
-                                                post_content=old_post,
-                                                )
-
-                # Set thread start post and author data
-                if self.mode == 'new_thread':
-                    thread.start_post = post
-                    thread.start_poster = request.user
-                    thread.start_poster_name = request.user.username
-                    thread.start_poster_slug = request.user.username_slug
-                    if request.user.rank and request.user.rank.style:
-                        thread.start_poster_style = request.user.rank.style
-                    # Reward user for posting new thread?
-                    if not request.user.last_post or request.user.last_post < timezone.now() - timedelta(seconds=request.settings['score_reward_new_post_cooldown']):
-                        request.user.score += request.settings['score_reward_new_thread']
-
-                # New post - increase post counters, thread score
-                # Notify quoted post author and close thread if it has hit limit
-                if self.mode in ['new_post', 'new_post_quick']:
-                    if moderation:
-                        thread.replies_moderated += 1
-                    else:
-                        thread.replies += 1
-                        if thread.last_poster_id != request.user.pk:
-                            thread.score += request.settings['thread_ranking_reply_score']
-                        # Notify quoted poster of reply?
-                        if self.quote and self.quote.user_id and self.quote.user_id != request.user.pk and not self.quote.user.is_ignoring(request.user):
-                            alert = self.quote.user.alert(ugettext_lazy("%(username)s has replied to your post in thread %(thread)s").message)
-                            alert.profile('username', request.user)
-                            alert.post('thread', self.thread, post)
-                            alert.save_all()
-                        if (self.request.settings.thread_length > 0
-                            and not thread.closed
-                            and thread.replies >= self.request.settings.thread_length):
-                            thread.closed = True
-                            post.set_checkpoint(self.request, 'limit')
-                    # Reward user for posting new post?
-                    if not post.appended and (not request.user.last_post or request.user.last_post < timezone.now() - timedelta(seconds=request.settings['score_reward_new_post_cooldown'])):
-                        request.user.score += request.settings['score_reward_new_post']
-
-                # Update last poster data
-                if not moderation and self.mode not in ['edit_thread', 'edit_post']:
-                    thread.last = now
-                    thread.last_post = post
-                    thread.last_poster = request.user
-                    thread.last_poster_name = request.user.username
-                    thread.last_poster_slug = request.user.username_slug
-                    thread.last_poster_style = request.user.rank.style
-
-                # Final update of thread entry
-                if self.mode != 'edit_post':
-                    thread.save(force_update=True)
-
-                # Update forum and monitor
-                if not moderation:
-                    if self.mode == 'new_thread':
-                        self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
-                        self.forum.threads += 1
-
-                    if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
-                        self.request.monitor['posts'] = int(self.request.monitor['posts']) + 1
-                        self.forum.posts += 1
-
-                    if self.mode in ['new_thread', 'new_post', 'new_post_quick'] or (
-                        self.mode == 'edit_thread'
-                        and self.forum.last_thread_id == thread.pk
-                        and self.forum.last_thread_name != thread.name):
-                        self.forum.last_thread = thread
-                        self.forum.last_thread_name = thread.name
-                        self.forum.last_thread_slug = thread.slug
-                        self.forum.last_thread_date = thread.last
-
-                    if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
-                        self.forum.last_poster = thread.last_poster
-                        self.forum.last_poster_name = thread.last_poster_name
-                        self.forum.last_poster_slug = thread.last_poster_slug
-                        self.forum.last_poster_style = thread.last_poster_style
-
-                    if self.mode != 'edit_post':
-                        self.forum.save(force_update=True)
-
-                # Update user
-                if not moderation:
-                    if self.mode == 'new_thread':
-                        request.user.threads += 1
-                    request.user.posts += 1
-                if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
-                    request.user.last_post = thread.last
-                    request.user.save(force_update=True)
-                    
-                # Notify users about post
-                if md:
-                    try:
-                        if self.quote and self.quote.user_id:
-                            del md.mentions[self.quote.user.username_slug]
-                    except KeyError:
-                        pass
-                    if md.mentions:
-                        post.notify_mentioned(request, md.mentions)
-                        post.save(force_update=True)
-
-                # Set thread watch status
-                if self.mode == 'new_thread' and request.user.subscribe_start:
-                    WatchedThread.objects.create(
-                                               user=request.user,
-                                               forum=self.forum,
-                                               thread=thread,
-                                               last_read=now,
-                                               email=(request.user.subscribe_start == 2),
-                                               )
-                    
-                if self.mode in ['new_post', 'new_post_quick'] and request.user.subscribe_reply:
-                    try:
-                        watcher = WatchedThread.objects.get(user=request.user, thread=self.thread)
-                    except WatchedThread.DoesNotExist:
-                        WatchedThread.objects.create(
-                                                   user=request.user,
-                                                   forum=self.forum,
-                                                   thread=thread,
-                                                   last_read=now,
-                                                   email=(request.user.subscribe_reply == 2),
-                                                   )
-
-                # Set flash and redirect user to his post
-                if self.mode == 'new_thread':
-                    if moderation:
-                        request.messages.set_flash(Message(_("New thread has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads')
-                    else:
-                        request.messages.set_flash(Message(_("New thread has been posted.")), 'success', 'threads')
-                    return redirect(reverse('thread', kwargs={'thread': thread.pk, 'slug': thread.slug}) + ('#post-%s' % post.pk))
-
-                if self.mode in ['new_post', 'new_post_quick']:
-                    thread.email_watchers(request, post)
-                    if moderation:
-                        request.messages.set_flash(Message(_("Your reply has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads_%s' % post.pk)
-                    else:
-                        request.messages.set_flash(Message(_("Your reply has been posted.")), 'success', 'threads_%s' % post.pk)
-                    return self.redirect_to_post(post)
-
-                if self.mode == 'edit_thread':
-                    request.messages.set_flash(Message(_("Your thread has been edited.")), 'success', 'threads_%s' % self.post.pk)
-                if self.mode == 'edit_post':
-                    request.messages.set_flash(Message(_("Your reply has been edited.")), 'success', 'threads_%s' % self.post.pk)
-                    return self.redirect_to_post(self.post)
-                return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
-            message = Message(form.non_field_errors()[0], 'error')
-        else:
-            form = self.get_form()
-
-        # Merge proxy into forum
-        self.forum.closed = self.proxy.closed
-        return request.theme.render_to_response('threads/posting.html',
-                                                {
-                                                 'mode': self.mode,
-                                                 'forum': self.forum,
-                                                 'thread': self.thread,
-                                                 'post': self.post,
-                                                 'quote': self.quote,
-                                                 'parents': self.parents,
-                                                 'message': message,
-                                                 'form': FormLayout(form),
-                                                 },
-                                                context_instance=RequestContext(request));

+ 0 - 565
misago/apps/oldthreads/views/thread.py

@@ -1,565 +0,0 @@
-from django.core.urlresolvers import reverse
-from django import forms
-from django.db.models import F
-from django.forms import ValidationError
-from django.shortcuts import redirect
-from django.template import RequestContext
-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.forms import Form, FormLayout, FormFields
-from misago.markdown import post_markdown
-from misago.messages import Message
-from misago.models import Forum, Thread, Post, Karma, Change, Checkpoint, WatchedThread
-from misago.readstrackers import ThreadsTracker
-from misago.utils.strings import slugify
-from misago.utils.pagination import make_pagination
-from misago.apps.threads.forms import MoveThreadsForm, SplitThreadForm, MovePostsForm, QuickReplyForm
-from misago.apps.threads.views.base import BaseView
-
-class ThreadView(BaseView):
-    def fetch_thread(self, thread):
-        self.thread = Thread.objects.get(pk=thread)
-        self.forum = self.thread.forum
-        self.proxy = Forum.objects.parents_aware_forum(self.forum)
-        self.request.acl.forums.allow_forum_view(self.forum)
-        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
-        self.parents = Forum.objects.forum_parents(self.forum.pk, True)
-        self.tracker = ThreadsTracker(self.request, self.forum)
-        if self.request.user.is_authenticated():
-            try:
-                self.watcher = WatchedThread.objects.get(user=self.request.user, thread=self.thread)
-            except WatchedThread.DoesNotExist:
-                pass
-
-    def fetch_posts(self, page):
-        self.count = self.request.acl.threads.filter_posts(self.request, self.thread, Post.objects.filter(thread=self.thread)).count()
-        self.posts = self.request.acl.threads.filter_posts(self.request, self.thread, Post.objects.filter(thread=self.thread)).prefetch_related('checkpoint_set', 'user', 'user__rank')
-        if self.thread.merges > 0:
-            self.posts = self.posts.order_by('merge', 'pk')
-        else:
-            self.posts = self.posts.order_by('pk')
-        self.pagination = make_pagination(page, self.count, self.request.settings.posts_per_page)
-        if self.request.settings.posts_per_page < self.count:
-            self.posts = self.posts[self.pagination['start']:self.pagination['stop']]
-        self.read_date = self.tracker.read_date(self.thread)
-        ignored_users = []
-        if self.request.user.is_authenticated():
-            ignored_users = self.request.user.ignored_users()
-        posts_dict = {}
-        for post in self.posts:
-            posts_dict[post.pk] = post
-            post.message = self.request.messages.get_message('threads_%s' % post.pk)
-            post.is_read = post.date <= self.read_date or (post.pk != self.thread.start_post_id and post.moderated)
-            post.karma_vote = None
-            post.ignored = self.thread.start_post_id != post.pk and not self.thread.pk in self.request.session.get('unignore_threads', []) and post.user_id in ignored_users
-            if post.ignored:
-                self.ignored = True
-        last_post = self.posts[len(self.posts) - 1]
-        if not self.tracker.is_read(self.thread):
-            self.tracker.set_read(self.thread, last_post)
-            self.tracker.sync()
-        if self.watcher and last_post.date > self.watcher.last_read:
-            self.watcher.last_read = timezone.now()
-            self.watcher.save(force_update=True)
-        if self.request.user.is_authenticated():
-            for karma in Karma.objects.filter(post_id__in=posts_dict.keys()).filter(user=self.request.user):
-                posts_dict[karma.post_id].karma_vote = karma
-
-    def get_post_actions(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        actions = []
-        try:
-            if acl['can_approve'] and self.thread.replies_moderated > 0:
-                actions.append(('accept', _('Accept posts')))
-            if acl['can_move_threads_posts']:
-                actions.append(('merge', _('Merge posts into one')))
-                actions.append(('split', _('Split posts to new thread')))
-                actions.append(('move', _('Move posts to other thread')))
-            if acl['can_protect_posts']:
-                actions.append(('protect', _('Protect posts')))
-                actions.append(('unprotect', _('Remove posts protection')))
-            if acl['can_delete_posts']:
-                if self.thread.replies_deleted > 0:
-                    actions.append(('undelete', _('Undelete posts')))
-                actions.append(('soft', _('Soft delete posts')))
-            if acl['can_delete_posts'] == 2:
-                actions.append(('hard', _('Hard delete posts')))
-        except KeyError:
-            pass
-        return actions
-
-    def make_posts_form(self):
-        self.posts_form = None
-        list_choices = self.get_post_actions();
-        if (not self.request.user.is_authenticated()
-            or not list_choices):
-            return
-
-        form_fields = {}
-        form_fields['list_action'] = forms.ChoiceField(choices=list_choices)
-        list_choices = []
-        for item in self.posts:
-            list_choices.append((item.pk, None))
-        if not list_choices:
-            return
-        form_fields['list_items'] = forms.MultipleChoiceField(choices=list_choices, widget=forms.CheckboxSelectMultiple)
-        self.posts_form = type('PostsViewForm', (Form,), form_fields)
-
-    def handle_posts_form(self):
-        if self.request.method == 'POST' and self.request.POST.get('origin') == 'posts_form':
-            self.posts_form = self.posts_form(self.request.POST, request=self.request)
-            if self.posts_form.is_valid():
-                checked_items = []
-                for post in self.posts:
-                    if str(post.pk) in self.posts_form.cleaned_data['list_items']:
-                        checked_items.append(post.pk)
-                if checked_items:
-                    form_action = getattr(self, 'post_action_' + self.posts_form.cleaned_data['list_action'])
-                    try:
-                        response = form_action(checked_items)
-                        if response:
-                            return response
-                        return redirect(self.request.path)
-                    except forms.ValidationError as e:
-                        self.message = Message(e.messages[0], 'error')
-                else:
-                    self.message = Message(_("You have to select at least one post."), 'error')
-            else:
-                if 'list_action' in self.posts_form.errors:
-                    self.message = Message(_("Action requested is incorrect."), 'error')
-                else:
-                    self.message = Message(posts_form.non_field_errors()[0], 'error')
-        else:
-            self.posts_form = self.posts_form(request=self.request)
-
-    def post_action_accept(self, ids):
-        accepted = 0
-        for post in self.posts:
-            if post.pk in ids and post.moderated:
-                accepted += 1
-        if accepted:
-            self.thread.post_set.filter(id__in=ids).update(moderated=False)
-            self.thread.sync()
-            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')
-
-    def post_action_merge(self, ids):
-        users = []
-        posts = []
-        for post in self.posts:
-            if post.pk in ids:
-                posts.append(post)
-                if not post.user_id in users:
-                    users.append(post.user_id)
-                if len(users) > 1:
-                    raise forms.ValidationError(_("You cannot merge replies made by different members!"))
-        if len(posts) < 2:
-            raise forms.ValidationError(_("You have to select two or more posts you want to merge."))
-        new_post = posts[0]
-        for post in posts[1:]:
-            post.merge_with(new_post)
-            post.delete()
-        md, new_post.post_preparsed = post_markdown(self.request, new_post.post)
-        new_post.save(force_update=True)
-        self.thread.sync()
-        self.thread.save(force_update=True)
-        self.forum.sync()
-        self.forum.save(force_update=True)
-        self.request.messages.set_flash(Message(_('Selected posts have been merged into one message.')), 'success', 'threads')
-
-    def post_action_split(self, ids):
-        for id in ids:
-            if id == self.thread.start_post_id:
-                raise forms.ValidationError(_("You cannot split first post from thread."))
-        message = None
-        if self.request.POST.get('do') == 'split':
-            form = SplitThreadForm(self.request.POST, request=self.request)
-            if form.is_valid():
-                new_thread = Thread()
-                new_thread.forum = form.cleaned_data['thread_forum']
-                new_thread.name = form.cleaned_data['thread_name']
-                new_thread.slug = slugify(form.cleaned_data['thread_name'])
-                new_thread.start = timezone.now()
-                new_thread.last = timezone.now()
-                new_thread.start_poster_name = 'n'
-                new_thread.start_poster_slug = 'n'
-                new_thread.last_poster_name = 'n'
-                new_thread.last_poster_slug = 'n'
-                new_thread.save(force_insert=True)
-                prev_merge = -1
-                merge = -1
-                for post in self.posts:
-                    if post.pk in ids:
-                        if prev_merge != post.merge:
-                            prev_merge = post.merge
-                            merge += 1
-                        post.merge = merge
-                        post.move_to(new_thread)
-                        post.save(force_update=True)
-                new_thread.sync()
-                new_thread.save(force_update=True)
-                self.thread.sync()
-                self.thread.save(force_update=True)
-                self.forum.sync()
-                self.forum.save(force_update=True)
-                if new_thread.forum != self.forum:
-                    new_thread.forum.sync()
-                    new_thread.forum.save(force_update=True)
-                self.request.messages.set_flash(Message(_("Selected posts have been split to new thread.")), 'success', 'threads')
-                return redirect(reverse('thread', kwargs={'thread': new_thread.pk, 'slug': new_thread.slug}))
-            message = Message(form.non_field_errors()[0], 'error')
-        else:
-            form = SplitThreadForm(request=self.request, initial={
-                                                                  'thread_name': _('[Split] %s') % self.thread.name,
-                                                                  'thread_forum': self.forum,
-                                                                  })
-        return self.request.theme.render_to_response('threads/split.html',
-                                                     {
-                                                      'message': message,
-                                                      'forum': self.forum,
-                                                      'parents': self.parents,
-                                                      'thread': self.thread,
-                                                      'posts': ids,
-                                                      'form': FormLayout(form),
-                                                      },
-                                                     context_instance=RequestContext(self.request));
-
-    def post_action_move(self, ids):
-        message = None
-        if self.request.POST.get('do') == 'move':
-            form = MovePostsForm(self.request.POST, request=self.request, thread=self.thread)
-            if form.is_valid():
-                thread = form.cleaned_data['thread_url']
-                prev_merge = -1
-                merge = -1
-                for post in self.posts:
-                    if post.pk in ids:
-                        if prev_merge != post.merge:
-                            prev_merge = post.merge
-                            merge += 1
-                        post.merge = merge + thread.merges
-                        post.move_to(thread)
-                        post.save(force_update=True)
-                if self.thread.post_set.count() == 0:
-                    self.thread.delete()
-                else:
-                    self.thread.sync()
-                    self.thread.save(force_update=True)
-                thread.sync()
-                thread.save(force_update=True)
-                thread.forum.sync()
-                thread.forum.save(force_update=True)
-                if self.forum.pk != thread.forum.pk:
-                    self.forum.sync()
-                    self.forum.save(force_update=True)
-                self.request.messages.set_flash(Message(_("Selected posts have been moved to new thread.")), 'success', 'threads')
-                return redirect(reverse('thread', kwargs={'thread': thread.pk, 'slug': thread.slug}))
-            message = Message(form.non_field_errors()[0], 'error')
-        else:
-            form = MovePostsForm(request=self.request)
-        return self.request.theme.render_to_response('threads/move_posts.html',
-                                                     {
-                                                      'message': message,
-                                                      'forum': self.forum,
-                                                      'parents': self.parents,
-                                                      'thread': self.thread,
-                                                      'posts': ids,
-                                                      'form': FormLayout(form),
-                                                      },
-                                                     context_instance=RequestContext(self.request));
-
-    def post_action_undelete(self, ids):
-        undeleted = []
-        for post in self.posts:
-            if post.pk in ids and post.deleted:
-                undeleted.append(post.pk)
-        if undeleted:
-            self.thread.post_set.filter(id__in=undeleted).update(deleted=False)
-            self.thread.sync()
-            self.thread.save(force_update=True)
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been restored.')), 'success', 'threads')
-
-    def post_action_protect(self, ids):
-        protected = 0
-        for post in self.posts:
-            if post.pk in ids and not post.protected:
-                protected += 1
-        if protected:
-            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')
-
-    def post_action_unprotect(self, ids):
-        unprotected = 0
-        for post in self.posts:
-            if post.pk in ids and post.protected:
-                unprotected += 1
-        if unprotected:
-            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')
-
-    def post_action_soft(self, ids):
-        deleted = []
-        for post in self.posts:
-            if post.pk in ids and not post.deleted:
-                if post.pk == self.thread.start_post_id:
-                    raise forms.ValidationError(_("You cannot delete first post of thread using this action. If you want to delete thread, use thread moderation instead."))
-                deleted.append(post.pk)
-        if deleted:
-            self.thread.post_set.filter(id__in=deleted).update(deleted=True)
-            self.thread.sync()
-            self.thread.save(force_update=True)
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been deleted.')), 'success', 'threads')
-
-    def post_action_hard(self, ids):
-        deleted = []
-        for post in self.posts:
-            if post.pk in ids:
-                if post.pk == self.thread.start_post_id:
-                    raise forms.ValidationError(_("You cannot delete first post of thread using this action. If you want to delete thread, use thread moderation instead."))
-                deleted.append(post.pk)
-        if deleted:
-            for post in self.posts:
-                if post.pk in deleted:
-                    post.delete()
-            self.thread.sync()
-            self.thread.save(force_update=True)
-            self.forum.sync()
-            self.forum.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been deleted.')), 'success', 'threads')
-
-    def get_thread_actions(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        actions = []
-        try:
-            if acl['can_approve'] and self.thread.moderated:
-                actions.append(('accept', _('Accept this thread')))
-            if acl['can_pin_threads'] == 2 and self.thread.weight < 2:
-                actions.append(('annouce', _('Change this thread to announcement')))
-            if acl['can_pin_threads'] > 0 and self.thread.weight != 1:
-                actions.append(('sticky', _('Change this thread to sticky')))
-            if acl['can_pin_threads'] > 0:
-                if self.thread.weight == 2:
-                    actions.append(('normal', _('Change this thread to normal')))
-                if self.thread.weight == 1:
-                    actions.append(('normal', _('Unpin this thread')))
-            if acl['can_move_threads_posts']:
-                actions.append(('move', _('Move this thread')))
-            if acl['can_close_threads']:
-                if self.thread.closed:
-                    actions.append(('open', _('Open this thread')))
-                else:
-                    actions.append(('close', _('Close this thread')))
-            if acl['can_delete_threads']:
-                if self.thread.deleted:
-                    actions.append(('undelete', _('Undelete this thread')))
-                else:
-                    actions.append(('soft', _('Soft delete this thread')))
-            if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Hard delete this thread')))
-        except KeyError:
-            pass
-        return actions
-
-    def make_thread_form(self):
-        self.thread_form = None
-        list_choices = self.get_thread_actions();
-        if (not self.request.user.is_authenticated()
-            or not list_choices):
-            return
-        form_fields = {'thread_action': forms.ChoiceField(choices=list_choices)}
-        self.thread_form = type('ThreadViewForm', (Form,), form_fields)
-
-    def handle_thread_form(self):
-        if self.request.method == 'POST' and self.request.POST.get('origin') == 'thread_form':
-            self.thread_form = self.thread_form(self.request.POST, request=self.request)
-            if self.thread_form.is_valid():
-                form_action = getattr(self, 'thread_action_' + self.thread_form.cleaned_data['thread_action'])
-                try:
-                    response = form_action()
-                    if response:
-                        return response
-                    return redirect(self.request.path)
-                except forms.ValidationError as e:
-                    self.message = Message(e.messages[0], 'error')
-            else:
-                if 'thread_action' in self.thread_form.errors:
-                    self.message = Message(_("Action requested is incorrect."), 'error')
-                else:
-                    self.message = Message(form.non_field_errors()[0], 'error')
-        else:
-            self.thread_form = self.thread_form(request=self.request)
-
-    def thread_action_accept(self):
-        # Sync thread and post
-        self.thread.moderated = False
-        self.thread.replies_moderated -= 1
-        self.thread.save(force_update=True)
-        self.thread.start_post.moderated = False
-        self.thread.start_post.save(force_update=True)
-        self.thread.last_post.set_checkpoint(self.request, 'accepted')
-        # Sync user
-        if self.thread.last_post.user:
-            self.thread.start_post.user.threads += 1
-            self.thread.start_post.user.posts += 1
-            self.thread.start_post.user.save(force_update=True)
-        # Sync forum
-        self.forum.sync()
-        self.forum.save(force_update=True)
-        # Update monitor
-        self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
-        self.request.monitor['posts'] = int(self.request.monitor['posts']) + self.thread.replies + 1
-        self.request.messages.set_flash(Message(_('Thread has been marked as reviewed and made visible to other members.')), 'success', 'threads')
-
-    def thread_action_annouce(self):
-        self.thread.weight = 2
-        self.thread.save(force_update=True)
-        self.request.messages.set_flash(Message(_('Thread has been turned into announcement.')), 'success', 'threads')
-
-    def thread_action_sticky(self):
-        self.thread.weight = 1
-        self.thread.save(force_update=True)
-        self.request.messages.set_flash(Message(_('Thread has been turned into sticky.')), 'success', 'threads')
-
-    def thread_action_normal(self):
-        self.thread.weight = 0
-        self.thread.save(force_update=True)
-        self.request.messages.set_flash(Message(_('Thread weight has been changed to normal.')), 'success', 'threads')
-
-    def thread_action_move(self):
-        message = None
-        if self.request.POST.get('do') == 'move':
-            form = MoveThreadsForm(self.request.POST, request=self.request, forum=self.forum)
-            if form.is_valid():
-                new_forum = form.cleaned_data['new_forum']
-                self.thread.move_to(new_forum)
-                self.thread.save(force_update=True)
-                self.forum.sync()
-                self.forum.save(force_update=True)
-                new_forum.sync()
-                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')
-                return None
-            message = Message(form.non_field_errors()[0], 'error')
-        else:
-            form = MoveThreadsForm(request=self.request, forum=self.forum)
-        return self.request.theme.render_to_response('threads/move_thread.html',
-                                                     {
-                                                      'message': message,
-                                                      'forum': self.forum,
-                                                      'parents': self.parents,
-                                                      'thread': self.thread,
-                                                      'form': FormLayout(form),
-                                                      },
-                                                     context_instance=RequestContext(self.request));
-
-    def thread_action_open(self):
-        self.thread.closed = False
-        self.thread.save(force_update=True)
-        self.thread.last_post.set_checkpoint(self.request, 'opened')
-        self.request.messages.set_flash(Message(_('Thread has been opened.')), 'success', 'threads')
-
-    def thread_action_close(self):
-        self.thread.closed = True
-        self.thread.save(force_update=True)
-        self.thread.last_post.set_checkpoint(self.request, 'closed')
-        self.request.messages.set_flash(Message(_('Thread has been closed.')), 'success', 'threads')
-
-    def thread_action_undelete(self):
-        # Update thread
-        self.thread.deleted = False
-        self.thread.replies_deleted -= 1
-        self.thread.save(force_update=True)
-        # Update first post in thread
-        self.thread.start_post.deleted = False
-        self.thread.start_post.save(force_update=True)
-        # Set checkpoint
-        self.thread.last_post.set_checkpoint(self.request, 'undeleted')
-        # Update forum
-        self.forum.sync()
-        self.forum.save(force_update=True)
-        # Update monitor
-        self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
-        self.request.monitor['posts'] = int(self.request.monitor['posts']) + self.thread.replies + 1
-        self.request.messages.set_flash(Message(_('Thread has been undeleted.')), 'success', 'threads')
-
-    def thread_action_soft(self):
-        # Update thread
-        self.thread.deleted = True
-        self.thread.replies_deleted += 1
-        self.thread.save(force_update=True)
-        # Update first post in thread
-        self.thread.start_post.deleted = True
-        self.thread.start_post.save(force_update=True)
-        # Set checkpoint
-        self.thread.last_post.set_checkpoint(self.request, 'deleted')
-        # Update forum
-        self.forum.sync()
-        self.forum.save(force_update=True)
-        # Update monitor
-        self.request.monitor['threads'] = int(self.request.monitor['threads']) - 1
-        self.request.monitor['posts'] = int(self.request.monitor['posts']) - self.thread.replies - 1
-        self.request.messages.set_flash(Message(_('Thread has been deleted.')), 'success', 'threads')
-
-    def thread_action_hard(self):
-        # Delete thread
-        self.thread.delete()
-        # Update forum
-        self.forum.sync()
-        self.forum.save(force_update=True)
-        # Update monitor
-        self.request.monitor['threads'] = int(self.request.monitor['threads']) - 1
-        self.request.monitor['posts'] = int(self.request.monitor['posts']) - self.thread.replies - 1
-        self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
-        return redirect(reverse('forum', kwargs={'forum': self.forum.pk, 'slug': self.forum.slug}))
-
-    def __call__(self, request, slug=None, thread=None, page=0):
-        self.request = request
-        self.pagination = None
-        self.parents = None
-        self.ignored = False
-        self.watcher = None
-        try:
-            self.fetch_thread(thread)
-            self.fetch_posts(page)
-            self.message = request.messages.get_message('threads')
-            self.make_thread_form()
-            if self.thread_form:
-                response = self.handle_thread_form()
-                if response:
-                    return response
-            self.make_posts_form()
-            if self.posts_form:
-                response = self.handle_posts_form()
-                if response:
-                    return response
-        except Thread.DoesNotExist:
-            return error404(self.request)
-        except ACLError403 as e:
-            return error403(request, e.message)
-        except ACLError404 as e:
-            return error404(request, e.message)
-        # Merge proxy into forum
-        self.forum.closed = self.proxy.closed
-        return request.theme.render_to_response('threads/thread.html',
-                                                {
-                                                 'message': self.message,
-                                                 'forum': self.forum,
-                                                 'parents': self.parents,
-                                                 'thread': self.thread,
-                                                 'is_read': self.tracker.is_read(self.thread),
-                                                 'count': self.count,
-                                                 'posts': self.posts,
-                                                 'ignored_posts': self.ignored,
-                                                 'watcher': self.watcher,
-                                                 'pagination': self.pagination,
-                                                 'quick_reply': FormFields(QuickReplyForm(request=request)).fields,
-                                                 'thread_form': FormFields(self.thread_form).fields if self.thread_form else None,
-                                                 'posts_form': FormFields(self.posts_form).fields if self.posts_form else None,
-                                                 },
-                                                context_instance=RequestContext(request));

+ 15 - 0
misago/apps/threads/changelog.py

@@ -0,0 +1,15 @@
+from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
+                                              ChangelogDiffBaseView,
+                                              ChangelogRevertBaseView)
+from misago.apps.threads.mixins import TypeMixin
+
+class ChangelogView(ChangelogChangesBaseView, TypeMixin):
+    pass
+
+
+class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
+    pass
+
+
+class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
+    pass

+ 9 - 0
misago/apps/threads/details.py

@@ -0,0 +1,9 @@
+from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
+from misago.apps.threads.mixins import TypeMixin
+
+class DetailsView(DetailsBaseView, TypeMixin):
+    pass
+
+
+class KarmaVotesView(KarmaVotesBaseView, TypeMixin):
+    pass

+ 5 - 8
misago/apps/threads/urls.py

@@ -26,12 +26,9 @@ urlpatterns = patterns('misago.apps.threads',
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="thread_hide", kwargs={'mode': 'hide_thread'}),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="thread_hide", kwargs={'mode': 'hide_thread'}),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="post_delete", kwargs={'mode': 'delete_post'}),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="post_delete", kwargs={'mode': 'delete_post'}),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="post_hide", kwargs={'mode': 'hide_post'}),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="post_hide", kwargs={'mode': 'hide_post'}),
-)
-
-urlpatterns += patterns('misago.apps.errors',
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'error_not_implemented', name="post_info"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/votes/$', 'error_not_implemented', name="post_votes"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'error_not_implemented', name="changelog"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'error_not_implemented', name="changelog_diff"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'error_not_implemented', name="changelog_revert"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="post_info"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/votes/$', 'details.KarmaVotesView', name="post_votes"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="thread_changelog"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="thread_changelog_diff"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="thread_changelog_revert"),
 )
 )

+ 123 - 0
misago/apps/threadtype/changelog.py

@@ -0,0 +1,123 @@
+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.utils.datesformats import reldate
+from misago.utils.strings import slugify
+from misago.utils.pagination import make_pagination
+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.fetch_target()
+            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.message)
+        except ACLError404 as e:
+            return error404(request, e.message)
+        return self.dispatch(request)
+
+
+class ChangelogChangesBaseView(ChangelogBaseView):
+    def dispatch(self, request, **kwargs):
+        return request.theme.render_to_response('%ss/changelog.html' % 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 request.theme.render_to_response('%ss/changelog_diff.html' % 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(request, 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)

+ 69 - 0
misago/apps/threadtype/details.py

@@ -0,0 +1,69 @@
+from django.template import RequestContext
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.errors import error403, error404
+from misago.models import Forum, Thread, Post
+from misago.apps.threadtype.base import ViewBase
+
+class ExtraBaseView(ViewBase):
+    def fetch_target(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)
+        self.request.acl.forums.allow_forum_view(self.forum)
+        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
+        if self.forum.level:
+            self.parents = Forum.objects.forum_parents(self.forum.pk, True)
+        self.check_forum_type()
+        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)
+
+    def __call__(self, request, **kwargs):
+        self.request = request
+        self.kwargs = kwargs
+        self.forum = None
+        self.thread = None
+        self.post = None
+        self.parents = []
+        try:
+            self.fetch_target()
+            self.check_acl()
+        except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist):
+            return error404(self.request)
+        except ACLError403 as e:
+            return error403(request, e.message)
+        except ACLError404 as e:
+            return error404(request, e.message)
+        return self.response()
+
+
+class DetailsBaseView(ExtraBaseView):
+    def check_acl(self):
+        self.request.acl.users.allow_details_view()
+
+    def response(self):
+        return self.request.theme.render_to_response('%ss/details.html' % self.type_prefix,
+                                                     {
+                                                      'forum': self.forum,
+                                                      'parents': self.parents,
+                                                      'thread': self.thread,
+                                                      'post': self.post,
+                                                     },
+                                                     context_instance=RequestContext(self.request))
+
+
+class KarmaVotesBaseView(ExtraBaseView):
+    def check_acl(self):
+        self.request.acl.threads.allow_post_votes_view(self.forum)
+
+    def response(self):
+        return self.request.theme.render_to_response('%ss/karmas.html' % self.type_prefix,
+                                                     {
+                                                      'forum': self.forum,
+                                                      'parents': self.parents,
+                                                      'thread': self.thread,
+                                                      'post': self.post,
+                                                      'upvotes': self.post.karma_set.filter(score=1),
+                                                      'downvotes': self.post.karma_set.filter(score=-1),
+                                                      },
+                                                     context_instance=RequestContext(self.request))

+ 0 - 0
misago/apps/threadtype/karmas.py


+ 1 - 0
misago/apps/threadtype/posting/editreply.py

@@ -42,6 +42,7 @@ class EditReplyBaseView(PostingBaseView):
             self.thread.save(force_update=True)
             self.thread.save(force_update=True)
 
 
         if changed_post:
         if changed_post:
+            self.post.post = form.cleaned_data['post']
             self.md, self.post.post_preparsed = post_markdown(self.request, form.cleaned_data['post'])
             self.md, self.post.post_preparsed = post_markdown(self.request, form.cleaned_data['post'])
             self.post.edits += 1
             self.post.edits += 1
             self.post.edit_date = now
             self.post.edit_date = now

+ 1 - 0
misago/apps/threadtype/posting/editthread.py

@@ -47,6 +47,7 @@ class EditThreadBaseView(PostingBaseView):
             self.thread.save(force_update=True)
             self.thread.save(force_update=True)
 
 
         if changed_post:
         if changed_post:
+            self.post.post = form.cleaned_data['post']
             self.md, self.post.post_preparsed = post_markdown(self.request, form.cleaned_data['post'])
             self.md, self.post.post_preparsed = post_markdown(self.request, form.cleaned_data['post'])
             self.post.edits += 1
             self.post.edits += 1
             self.post.edit_date = now
             self.post.edit_date = now

+ 3 - 3
templates/cranefly/threads/changelog.html

@@ -47,13 +47,13 @@
             </span>
             </span>
           </td>
           </td>
           <td>
           <td>
-            <a href="{% url 'changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}" class="change-no">#{{ loop.revindex }}</a>
+            <a href="{% url 'thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}" class="change-no">#{{ loop.revindex }}</a>
             {% if edit.reason %}
             {% if edit.reason %}
             <div class="change-reason">
             <div class="change-reason">
-              <a href="{% url 'changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}">{{ edit.reason }}</a>
+              <a href="{% url 'thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}">{{ edit.reason }}</a>
             </div>{% endif %}
             </div>{% endif %}
             <div class="change-description">
             <div class="change-description">
-              <a href="{% url 'changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}">
+              <a href="{% url 'thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}">
               {% if edit.change != 0 %}{% if edit.change > 0 -%}
               {% if edit.change != 0 %}{% if edit.change > 0 -%}
               {% trans chars=edit.change %}Added one character to post.{% pluralize %}Added {{ chars }} characters to post.{% endtrans %}
               {% trans chars=edit.change %}Added one character to post.{% pluralize %}Added {{ chars }} characters to post.{% endtrans %}
               {%- elif edit.change < 0 -%}
               {%- elif edit.change < 0 -%}

+ 4 - 4
templates/cranefly/threads/changelog_diff.html

@@ -8,7 +8,7 @@
 <li><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
 <li><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
 {% endfor %}
 {% endfor %}
 <li><a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
 <li><a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
-<li><a href="{% url 'changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}">{% trans post=post.pk %}Post #{{ post }} Changelog{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'thread_changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}">{% trans post=post.pk %}Post #{{ post }} Changelog{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
 <li class="active">{% trans date=change.date|reltimesince|low %}Edit from {{ date }}{% endtrans %}
 <li class="active">{% trans date=change.date|reltimesince|low %}Edit from {{ date }}{% endtrans %}
 {%- endblock %}
 {%- endblock %}
 
 
@@ -52,7 +52,7 @@
     <div class="diff-extra">
     <div class="diff-extra">
       {{ pager() }}
       {{ pager() }}
       {% if user.is_authenticated() and acl.threads.can_make_revert(forum, thread) %}
       {% if user.is_authenticated() and acl.threads.can_make_revert(forum, thread) %}
-      <form class="form-inline pull-right" action="{% url 'changelog_revert' thread=thread.pk, slug=thread.slug, post=post.pk, change=change.pk %}" method="post">
+      <form class="form-inline pull-right" action="{% url 'thread_changelog_revert' thread=thread.pk, slug=thread.slug, post=post.pk, change=change.pk %}" method="post">
         <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
         <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
         <button type="submit" class="btn btn-danger">{% trans %}Revert this edit{% endtrans %}</button></li>
         <button type="submit" class="btn btn-danger">{% trans %}Revert this edit{% endtrans %}</button></li>
       </form>
       </form>
@@ -89,8 +89,8 @@
 {% if prev or prev %}
 {% if prev or prev %}
 <div class="pagination pull-left">
 <div class="pagination pull-left">
   <ul>
   <ul>
-    {% if prev %}<li><a href="{% url 'changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=prev.pk %}"><i class="icon-chevron-left"></i> {{ prev.date|reldate }}</a></li>{% endif %}
-    {% if next %}<li><a href="{% url 'changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=next.pk %}">{{ next.date|reldate }} <i class="icon-chevron-right"></i></a></li>{% endif %}
+    {% if prev %}<li><a href="{% url 'thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=prev.pk %}"><i class="icon-chevron-left"></i> {{ prev.date|reldate }}</a></li>{% endif %}
+    {% if next %}<li><a href="{% url 'thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=next.pk %}">{{ next.date|reldate }} <i class="icon-chevron-right"></i></a></li>{% endif %}
   </ul>
   </ul>
 </div>
 </div>
 {% endif %}
 {% endif %}

+ 1 - 1
templates/cranefly/threads/thread.html

@@ -180,7 +180,7 @@
             {% if post.edits %}
             {% if post.edits %}
             <span class="separator">&ndash;</span>
             <span class="separator">&ndash;</span>
             {% if acl.threads.can_see_changelog(user, forum, post) %}
             {% if acl.threads.can_see_changelog(user, forum, post) %}
-            <a href="{% url 'changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-changelog tooltip-bottom" title="{% trans %}Show changelog{% endtrans %}">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</a>
+            <a href="{% url 'thread_changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-changelog tooltip-bottom" title="{% trans %}Show changelog{% endtrans %}">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</a>
             {% else %}
             {% else %}
             <span class="post-changelog">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</span>
             <span class="post-changelog">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</span>
             {% endif %}
             {% endif %}