Browse Source

Don't allow trading threads between different forums types

Ralfp 12 years ago
parent
commit
4888aca38d

+ 14 - 14
misago/apps/oldthreads/views/jumps.py

@@ -1,5 +1,5 @@
 from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
+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 _
@@ -10,9 +10,9 @@ 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 BaseView
+from misago.apps.threads.views.base import ViewBase
 
-class JumpView(BaseView):
+class JumpViewBase(ViewBase):
     def fetch_thread(self, thread):
         self.thread = Thread.objects.get(pk=thread)
         self.forum = self.thread.forum
@@ -23,11 +23,11 @@ class JumpView(BaseView):
         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(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 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))
+            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.')
@@ -49,24 +49,24 @@ class JumpView(BaseView):
 
 class LastReplyView(JumpView):
     def make_jump(self):
-        return self.redirect(self.thread.post_set.order_by('-id')[:1][0])
+        return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
 
 
 class FindReplyView(JumpView):
     def make_jump(self):
-        return self.redirect(self.post)
+        return self.redirect_to_post(self.post)
 
 
 class NewReplyView(JumpView):
     def make_jump(self):
         if not self.request.user.is_authenticated():
-            return self.redirect(self.thread.post_set.order_by('-id')[:1][0])
+            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(self.thread.post_set.order_by('-id')[:1][0])
-        return self.redirect(post[0])
+            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+        return self.redirect_to_post(post[0])
 
 
 class FirstModeratedView(JumpView):
@@ -74,7 +74,7 @@ class FirstModeratedView(JumpView):
         if not self.request.acl.threads.can_approve(self.forum):
             raise ACLError404()
         try:
-            return self.redirect(
+            return self.redirect_to_post(
                 self.thread.post_set.get(moderated=True))
         except Post.DoesNotExist:
             return error404(self.request)
@@ -85,7 +85,7 @@ class FirstReportedView(JumpView):
         if not self.request.acl.threads.can_mod_posts(self.forum):
             raise ACLError404()
         try:
-            return self.redirect(
+            return self.redirect_to_post(
                 self.thread.post_set.get(reported=True))
         except Post.DoesNotExist:
             return error404(self.request)
@@ -221,7 +221,7 @@ class UpvotePostView(JumpView):
             request.user.save(force_update=True)
             if self.post.user_id:
                 self.post.user.save(force_update=True)
-            return self.redirect(self.post)
+            return self.redirect_to_post(self.post)
         return view(self.request)
     
     def check_acl(self, request):

+ 1 - 2
misago/apps/threads/mixins.py

@@ -2,8 +2,7 @@ from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 
 class TypeMixin(object):
-    templates_prefix = 'threads'
-    thread_url = 'thread'
+    type_prefix = 'thread'
 
     def threads_list_redirect(self):
         return redirect(reverse('forum', kwargs={'forum': self.forum.pk, 'slug': self.forum.slug}))

+ 16 - 2
misago/apps/threadtype/base.py

@@ -1,4 +1,5 @@
 from django.core.urlresolvers import reverse
+from django.http import Http404
 from django.shortcuts import redirect
 from misago.models import Forum, Thread, Post
 from misago.utils.pagination import make_pagination
@@ -21,8 +22,21 @@ class ViewBase(object):
     def set_post_contex(self):
         pass
 
+    def check_forum_type(self):
+        type_prefix = self.type_prefix
+        if type_prefix == 'thread':
+            type_prefix = 'root'
+        else:
+            type_prefix = '%ss' % type_prefix
+        try:
+            if self.parents[0].parent_id != Forum.objects.special_pk(type_prefix):
+                raise Http404()
+        except (AttributeError, IndexError):
+            if self.forum.special != type_prefix:
+                raise Http404()
+
     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(self.thread_url, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % post.pk))
-        return redirect(reverse(self.thread_url, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % post.pk))
+            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % post.pk))
+        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % post.pk))

+ 233 - 0
misago/apps/threadtype/jumps.py

@@ -0,0 +1,233 @@
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.errors import error403, error404
+from misago.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.threadtype.base import BaseView
+
+class JumpView(BaseView):
+    def fetch_thread(self, thread):
+        self.thread = Thread.objects.get(pk=thread)
+        self.forum = self.thread.forum
+        self.request.acl.forums.allow_forum_view(self.forum)
+        self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
+
+    def fetch_post(self, post):
+        self.post = self.thread.post_set.get(pk=post)
+        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
+
+    def make_jump(self):
+        raise NotImplementedError('JumpView cannot be called directly.')
+
+    def __call__(self, request, slug=None, thread=None, post=None):
+        self.request = request
+        try:
+            self.fetch_thread(thread)
+            self.check_forum_type()
+            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 LastReplyBaseView(JumpView):
+    def make_jump(self):
+        return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+
+
+class FindReplyBaseView(JumpView):
+    def make_jump(self):
+        return self.redirect_to_post(self.post)
+
+
+class NewReplyBaseView(JumpView):
+    def make_jump(self):
+        if not self.request.user.is_authenticated():
+            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+        tracker = ThreadsTracker(self.request, self.forum)
+        read_date = tracker.read_date(self.thread)
+        post = self.thread.post_set.filter(date__gt=read_date).order_by('id')[:1]
+        if not post:
+            return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
+        return self.redirect_to_post(post[0])
+
+
+class FirstModeratedBaseView(JumpView):
+    def make_jump(self):
+        if not self.request.acl.threads.can_approve(self.forum):
+            raise ACLError404()
+        try:
+            return self.redirect_to_post(
+                self.thread.post_set.get(moderated=True))
+        except Post.DoesNotExist:
+            return error404(self.request)
+
+
+class FirstReportedBaseView(JumpView):
+    def make_jump(self):
+        if not self.request.acl.threads.can_mod_posts(self.forum):
+            raise ACLError404()
+        try:
+            return self.redirect_to_post(
+                self.thread.post_set.get(reported=True))
+        except Post.DoesNotExist:
+            return error404(self.request)
+
+
+class ShowHiddenRepliesBaseView(JumpView):
+    def make_jump(self):
+        @block_guest
+        @check_csrf
+        def view(request):
+            ignored_exclusions = request.session.get('unignore_threads', [])
+            ignored_exclusions.append(self.thread.pk)
+            request.session['unignore_threads'] = ignored_exclusions
+            request.messages.set_flash(Message(_('Replies made to this thread by members on your ignore list have been revealed.')), 'success', 'threads')
+            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+        return view(self.request)
+
+
+class WatchThreadBaseView(JumpView):
+    def get_retreat(self):
+        return redirect(self.request.POST.get('retreat', reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug})))
+
+    def update_watcher(self, request, watcher):
+        request.messages.set_flash(Message(_('This thread has been added to your watched threads list.')), 'success', 'threads')
+
+    def make_jump(self):
+        @block_guest
+        @check_csrf
+        def view(request):
+            try:
+                watcher = WatchedThread.objects.get(user=request.user, thread=self.thread)
+            except WatchedThread.DoesNotExist:
+                watcher = WatchedThread()
+                watcher.user = request.user
+                watcher.forum = self.forum
+                watcher.thread = self.thread
+                watcher.last_read = timezone.now()
+            self.update_watcher(request, watcher)
+            if watcher.pk:
+                watcher.save(force_update=True)
+            else:
+                watcher.save(force_insert=True)
+            return self.get_retreat()
+        return view(self.request)
+
+
+class WatchEmailThreadBaseView(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 UnwatchThreadBaseView(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 UnwatchEmailThreadBaseView(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 UpvotePostBaseView(JumpView):        
+    def make_jump(self):
+        @block_guest
+        @check_csrf
+        def view(request):
+            if self.post.user_id == request.user.id:
+                return error404(request)
+            self.check_acl(request)
+            try:
+                vote = Karma.objects.get(user=request.user, post=self.post)
+                if self.thread.start_post_id == self.post.pk:
+                    if vote.score > 0:
+                        self.thread.upvotes -= 1
+                    else:
+                        self.thread.downvotes -= 1
+                if vote.score > 0:
+                    self.post.upvotes -= 1
+                    request.user.karma_given_p -= 1
+                    if self.post.user_id:
+                        self.post.user.karma_p -= 1
+                else:
+                    self.post.downvotes -= 1
+                    request.user.karma_given_n -= 1
+                    if self.post.user_id:
+                        self.post.user.karma_n -= 1
+            except Karma.DoesNotExist:
+                vote = Karma()
+            vote.forum = self.forum
+            vote.thread = self.thread
+            vote.post = self.post
+            vote.user = request.user
+            vote.user_name = request.user.username
+            vote.user_slug = request.user.username_slug
+            vote.date = timezone.now()
+            vote.ip = request.session.get_ip(request)
+            vote.agent = request.META.get('HTTP_USER_AGENT')
+            self.make_vote(request, vote)
+            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 DownvotePostBaseView(UpvotePostView):
+    def check_acl(self, request):
+        request.acl.threads.allow_post_downvote(self.forum)
+    
+    def make_vote(self, request, vote):
+        vote.score = -1

+ 2 - 2
misago/apps/threadtype/list/moderation.py

@@ -87,7 +87,7 @@ class ThreadsListModeration(object):
             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(('%s/move_threads.html' % self.templates_prefix),
+        return self.request.theme.render_to_response(('%ss/move_threads.html' % self.type_prefix),
                                                      {
                                                       'message': self.message,
                                                       'forum': self.forum,
@@ -137,7 +137,7 @@ class ThreadsListModeration(object):
             self.message = Message(form.non_field_errors()[0], 'error')
         else:
             form = MergeThreadsForm(request=self.request, threads=threads)
-        return self.request.theme.render_to_response(('%s/merge.html' % self.templates_prefix),
+        return self.request.theme.render_to_response(('%ss/merge.html' % self.type_prefix),
                                                      {
                                                       'message': self.message,
                                                       'forum': self.forum,

+ 1 - 1
misago/apps/threadtype/list/views.py

@@ -109,7 +109,7 @@ class ThreadsListBaseView(ViewBase):
         # Merge proxy into forum
         self.forum.closed = self.proxy.closed
 
-        return request.theme.render_to_response(('%s/list.html' % self.templates_prefix),
+        return request.theme.render_to_response(('%ss/list.html' % self.type_prefix),
                                                 {
                                                  'message': self.message,
                                                  'forum': self.forum,

+ 2 - 1
misago/apps/threadtype/posting/base.py

@@ -77,6 +77,7 @@ class PostingBaseView(ViewBase):
 
         try:
             self._set_context()
+            self.check_forum_type()
             if request.method == 'POST':
                 # Create correct form instance
                 if self.allow_quick_reply and 'quick_reply' in request.POST:
@@ -112,7 +113,7 @@ class PostingBaseView(ViewBase):
         except ACLError404 as e:
             return error404(request, unicode(e))
 
-        return request.theme.render_to_response(('%s/posting.html' % self.templates_prefix),
+        return request.theme.render_to_response(('%ss/posting.html' % self.type_prefix),
                                                 {
                                                  'action': self.action,
                                                  'message': self.message,

+ 10 - 4
misago/apps/threadtype/posting/forms.py

@@ -24,8 +24,8 @@ class PostingForm(Form, ValidatePostLengthMixin):
                        ]
 
         # Can we change threads states?
-        if (self.request.acl.threads.can_pin_threads(self.forum) >= self.thread.weight and
-                self.request.acl.threads.can_pin_threads(self.forum)):
+        if (self.request.acl.threads.can_pin_threads(self.forum) and
+            (not self.thread or self.request.acl.threads.can_pin_threads(self.forum) >= self.thread.weight)):
             thread_weight = []
             if self.request.acl.threads.can_pin_threads(self.forum) == 2:
                 thread_weight.append((2, _("Announcement")))
@@ -33,11 +33,15 @@ class PostingForm(Form, ValidatePostLengthMixin):
             thread_weight.append((0, _("Standard")))
             if thread_weight:
                 self.layout[0][1].append(('thread_weight', {'label': _("Thread Importance")}))
+                try:
+                    current_weight = self.thread.weight
+                except AttributeError:
+                    current_weight = 0
                 self.fields['thread_weight'] = forms.TypedChoiceField(widget=forms.RadioSelect,
                                                                       choices=thread_weight,
                                                                       required=False,
                                                                       coerce=int,
-                                                                      initial=(self.thread.weight if self.thread else 0))
+                                                                      initial=current_weight)
 
         # Can we lock threads?
         if self.request.acl.threads.can_close(self.forum):
@@ -50,8 +54,10 @@ class PostingForm(Form, ValidatePostLengthMixin):
     def clean_thread_weight(self):
         data = self.cleaned_data['thread_weight']
         if not data:
-            if self.thread:
+            try:
                 return self.thread.weight
+            except AttributeError:
+                pass
             return 0
         return data
 

+ 2 - 2
misago/apps/threadtype/thread/moderation/posts.py

@@ -85,7 +85,7 @@ class PostsModeration(object):
                     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(self.thread_url, kwargs={'thread': new_thread.pk, 'slug': new_thread.slug}))
+                return redirect(reverse(self.type_prefix, kwargs={'thread': new_thread.pk, 'slug': new_thread.slug}))
             message = Message(form.non_field_errors()[0], 'error')
         else:
             form = SplitThreadForm(request=self.request, initial={
@@ -132,7 +132,7 @@ class PostsModeration(object):
                     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(self.thread_url, kwargs={'thread': thread.pk, 'slug': thread.slug}))
+                return redirect(reverse(self.type_prefix, kwargs={'thread': thread.pk, 'slug': thread.slug}))
             message = Message(form.non_field_errors()[0], 'error')
         else:
             form = MovePostsForm(request=self.request)

+ 2 - 1
misago/apps/threadtype/thread/views.py

@@ -163,6 +163,7 @@ class ThreadBaseView(ViewBase):
         self.message = request.messages.get_message('threads')
         try:
             self.fetch_thread()
+            self.check_forum_type()
             self.fetch_posts()
             self.make_thread_form()
             if self.thread_form:
@@ -184,7 +185,7 @@ class ThreadBaseView(ViewBase):
         # Merge proxy into forum
         self.forum.closed = self.proxy.closed
 
-        return request.theme.render_to_response(('%s/thread.html' % self.templates_prefix),
+        return request.theme.render_to_response(('%ss/thread.html' % self.type_prefix),
                                                 {
                                                  'message': self.message,
                                                  'forum': self.forum,