Просмотр исходного кода

Simplified checkpoints implementation. #141

Ralfp 12 лет назад
Родитель
Сommit
fd0aaa4a3b

+ 1 - 3
misago/acl/permissions/threads.py

@@ -558,9 +558,7 @@ class ThreadsACL(BaseACL):
         except KeyError:
             raise ACLError403(_("You don't have permission to see who voted on this post."))
 
-    def can_see_checkpoint(self, forum, checkpoint):
-        if not checkpoint.deleted:
-            return True
+    def can_see_all_checkpoints(self, forum):
         try:
             return self.acl[forum.pk]['can_see_deleted_checkpoints']
         except KeyError:

+ 3 - 6
misago/apps/privatethreads/jumps.py

@@ -73,8 +73,7 @@ class InviteUserView(JumpView, TypeMixin):
                 user.sync_pds = True
                 user.save(force_update=True)
                 user.email_user(self.request, 'private_thread_invite', _("You've been invited to private thread \"%(thread)s\" by %(user)s") % {'thread': self.thread.name, 'user': self.request.user.username}, {'author': self.request.user, 'thread': self.thread})
-                self.thread.last_post.set_checkpoint(self.request, 'invited', user)
-                self.thread.last_post.save(force_update=True)
+                self.thread.set_checkpoint(self.request, 'invited', user)
                 self.request.messages.set_flash(Message(_('%(user)s has been added to this thread.') % {'user': user.username}), 'success', 'threads')
         except User.DoesNotExist:
             self.request.messages.set_flash(Message(_('User with requested username could not be found.')), 'error', 'threads')
@@ -102,15 +101,13 @@ class RemoveUserView(JumpView, TypeMixin):
                 return self.threads_list_redirect()
             # Nope, see if we removed ourselves
             if user.pk == self.request.user.pk:
-                self.thread.last_post.set_checkpoint(self.request, 'left')
-                self.thread.last_post.save(force_update=True)
+                self.thread.set_checkpoint(self.request, 'left')
                 self.request.messages.set_flash(Message(_('You have left the "%(thread)s" thread.') % {'thread': self.thread.name}), 'info', 'threads')
                 return self.threads_list_redirect()
             # Nope, somebody else removed user
             user.sync_pds = True
             user.save(force_update=True)
-            self.thread.last_post.set_checkpoint(self.request, 'removed', user)
-            self.thread.last_post.save(force_update=True)
+            self.thread.set_checkpoint(self.request, 'removed', user)
             self.request.messages.set_flash(Message(_('Selected participant was removed from thread.')), 'info', 'threads')
             return self.retreat_redirect()
         except User.DoesNotExist:

+ 1 - 4
misago/apps/privatethreads/mixins.py

@@ -19,15 +19,12 @@ class TypeMixin(object):
             pass
 
     def invite_users(self, users):
-        sync_last_post = False
         for user in users:
             if not user in self.thread.participants.all():
                 self.thread.participants.add(user)
                 user.email_user(self.request, 'private_thread_invite', _("You've been invited to private thread \"%(thread)s\" by %(user)s") % {'thread': self.thread.name, 'user': self.request.user.username}, {'author': self.request.user, 'thread': self.thread})
                 if self.action == 'new_reply':
-                    self.thread.last_post.set_checkpoint(self.request, 'invited', user)
-        if sync_last_post:
-            self.thread.last_post.save(force_update=True)
+                    self.thread.set_checkpoint(self.request, 'invited', user)
 
     def force_stats_sync(self):
         self.thread.participants.exclude(id=self.request.user.id).update(sync_pds=True)

+ 3 - 3
misago/apps/privatethreads/urls.py

@@ -30,9 +30,9 @@ urlpatterns = patterns('misago.apps.privatethreads',
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="private_post_delete"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="private_post_hide"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="private_post_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="private_post_checkpoint_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="private_post_checkpoint_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="private_post_checkpoint_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="private_post_checkpoint_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="private_post_checkpoint_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="private_post_checkpoint_show"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="private_post_info"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="private_thread_changelog"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="private_thread_changelog_diff"),

+ 2 - 3
misago/apps/reports/list.py

@@ -72,10 +72,9 @@ class ThreadsListView(ThreadsListBaseView, ThreadsListModeration, TypeMixin):
             if thread.pk in ids:
                 if thread.original_weight != thread.weight:
                     if thread.weight == 1:
-                        thread.last_post.set_checkpoint(self.request, 'resolved')
+                        thread.set_checkpoint(self.request, 'resolved')
                     if thread.weight == 0:
-                        thread.last_post.set_checkpoint(self.request, 'bogus')
-                    thread.last_post.save(force_update=True)
+                        thread.set_checkpoint(self.request, 'bogus')
                 if thread.original_weight == 2 and thread.report_for_id:
                     reported_posts.append(thread.report_for.pk)
                     reported_threads.append(thread.report_for.thread_id)

+ 2 - 2
misago/apps/reports/posting.py

@@ -15,9 +15,9 @@ class SetStateCheckpointMixin(object):
             if self.thread.original_weight == 2:
                 self.request.monitor.decrease('reported_posts')
             if self.thread.weight == 1:
-                self.thread.last_post.set_checkpoint(self.request, 'resolved')
+                self.thread.set_checkpoint(self.request, 'resolved')
             if self.thread.weight == 0:
-                self.thread.last_post.set_checkpoint(self.request, 'bogus')
+                self.thread.set_checkpoint(self.request, 'bogus')
 
 
 class EditThreadView(SetStateCheckpointMixin, EditThreadBaseView, TypeMixin):

+ 2 - 4
misago/apps/reports/thread.py

@@ -39,13 +39,11 @@ class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
         return actions
 
     def after_thread_action_sticky(self):
-        self.thread.last_post.set_checkpoint(self.request, 'resolved')
-        self.thread.last_post.save(force_update=True)
+        self.thread.set_checkpoint(self.request, 'resolved')
         self.request.messages.set_flash(Message(_('Report has been set as resolved.')), 'success', 'threads')
 
     def after_thread_action_normal(self):
-        self.thread.last_post.set_checkpoint(self.request, 'bogus')
-        self.thread.last_post.save(force_update=True)
+        self.thread.set_checkpoint(self.request, 'bogus')
         self.request.messages.set_flash(Message(_('Report has been set as bogus.')), 'success', 'threads')
 
     def after_thread_action_undelete(self):

+ 3 - 3
misago/apps/reports/urls.py

@@ -22,9 +22,9 @@ urlpatterns = patterns('misago.apps.reports',
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="report_post_delete"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="report_post_hide"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="report_post_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="report_post_checkpoint_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="report_post_checkpoint_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="report_post_checkpoint_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="report_post_checkpoint_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="report_post_checkpoint_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="report_post_checkpoint_show"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="report_post_info"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="report_changelog"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="report_changelog_diff"),

+ 3 - 3
misago/apps/threads/urls.py

@@ -30,9 +30,9 @@ urlpatterns = patterns('misago.apps.threads',
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="post_delete"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="post_hide"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="post_show"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="post_checkpoint_delete"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="post_checkpoint_hide"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="post_checkpoint_show"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="post_checkpoint_delete"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="post_checkpoint_hide"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="post_checkpoint_show"),
     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"),

+ 11 - 20
misago/apps/threadtype/delete.py

@@ -27,7 +27,7 @@ class DeleteHideBaseView(ViewBase):
             self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
 
         if self.kwargs.get('checkpoint'):
-            self.checkpoint = self.post.checkpoint_set.get(id=self.kwargs.get('checkpoint'))
+            self.checkpoint = self.thread.checkpoint_set.get(id=self.kwargs.get('checkpoint'))
             self.request.acl.threads.allow_checkpoint_view(self.forum, self.checkpoint)
 
         self.set_context()
@@ -81,8 +81,7 @@ class HideThreadBaseView(DeleteHideBaseView):
     def delete(self):
         self.thread.start_post.deleted = True
         self.thread.start_post.save(force_update=True)
-        self.thread.last_post.set_checkpoint(self.request, 'deleted')
-        self.thread.last_post.save(force_update=True)
+        self.thread.set_checkpoint(self.request, 'deleted')
         self.thread.sync()
         self.thread.save(force_update=True)
         self.forum.sync()
@@ -108,8 +107,7 @@ class ShowThreadBaseView(DeleteHideBaseView):
     def delete(self):
         self.thread.start_post.deleted = False
         self.thread.start_post.save(force_update=True)
-        self.thread.last_post.set_checkpoint(self.request, 'undeleted')
-        self.thread.last_post.save(force_update=True)
+        self.thread.set_checkpoint(self.request, 'undeleted')
         self.thread.sync()
         self.thread.save(force_update=True)
         self.forum.sync()
@@ -130,7 +128,6 @@ class DeleteReplyBaseView(DeleteHideBaseView):
                                                    self.thread, self.post, True)
 
     def delete(self):
-        self.post.pass_checkpoints()
         self.post.delete()
         self.thread.sync()
         self.thread.save(force_update=True)
@@ -204,17 +201,17 @@ class DeleteCheckpointBaseView(DeleteHideBaseView):
 
     def delete(self):
         self.checkpoint.delete()
-        self.post.checkpoints = self.post.checkpoint_set.count() > 0
-        self.post.save(force_update=True)
 
     def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been deleted.")), 'success', 'threads_%s' % self.post.pk)
+        self.request.messages.set_flash(Message(_("Selected checkpoint has been deleted.")), 'success', 'threads')
 
     def response(self):
-        return self.redirect_to_post(self.post)
+        if 'retreat' in self.request.POST:
+            return redirect(self.request.POST.get('retreat'))
+        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
 
 
-class HideCheckpointBaseView(DeleteHideBaseView):
+class HideCheckpointBaseView(DeleteCheckpointBaseView):
     def set_context(self):
         self.request.acl.threads.allow_checkpoint_hide(self.forum)
         if self.checkpoint.deleted:
@@ -225,13 +222,10 @@ class HideCheckpointBaseView(DeleteHideBaseView):
         self.checkpoint.save(force_update=True)
 
     def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been hidden.")), 'success', 'threads_%s' % self.post.pk)
-
-    def response(self):
-        return self.redirect_to_post(self.post)
+        self.request.messages.set_flash(Message(_("Selected checkpoint has been hidden.")), 'success', 'threads')
 
 
-class ShowCheckpointBaseView(DeleteHideBaseView):
+class ShowCheckpointBaseView(DeleteCheckpointBaseView):
     def set_context(self):
         self.request.acl.threads.allow_checkpoint_show(self.forum)
         if not self.checkpoint.deleted:
@@ -242,7 +236,4 @@ class ShowCheckpointBaseView(DeleteHideBaseView):
         self.checkpoint.save(force_update=True)
 
     def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been restored.")), 'success', 'threads_%s' % self.post.pk)
-
-    def response(self):
-        return self.redirect_to_post(self.post)
+        self.request.messages.set_flash(Message(_("Selected checkpoint has been restored.")), 'success', 'threads')

+ 2 - 3
misago/apps/threadtype/jumps.py

@@ -263,10 +263,9 @@ class ReportPostBaseView(JumpView):
                 if report and report.start_poster_id != request.user.pk:
                     # Append our q.q to existing report?
                     try:
-                        report.start_post.checkpoint_set.get(user=request.user, action="reported")
+                        report.checkpoint_set.get(user=request.user, action="reported")
                     except Checkpoint.DoesNotExist:
-                        report.start_post.set_checkpoint(self.request, 'reported', user)
-                        report.start_post.save(force_update=True)
+                        report.set_checkpoint(self.request, 'reported', user)
                     made_report = True
 
             if not report:

+ 6 - 21
misago/apps/threadtype/list/moderation.py

@@ -15,7 +15,6 @@ class ThreadsListModeration(object):
 
     def _action_accept(self, ids):
         accepted = 0
-        last_posts = []
         users = []
         for thread in self.threads:
             if thread.pk in ids and thread.moderated:
@@ -26,15 +25,13 @@ class ThreadsListModeration(object):
                 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)
+                thread.set_checkpoint(self.request, 'accepted')
                 # 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()
@@ -102,8 +99,7 @@ class ThreadsListModeration(object):
                 for thread in threads:
                     thread.move_to(new_forum)
                     thread.save(force_update=True)
-                    thread.last_post.set_checkpoint(self.request, 'moved', forum=self.forum)
-                    thread.last_post.save(force_update=True)
+                    thread.set_checkpoint(self.request, 'moved', forum=self.forum)
                 new_forum.sync()
                 new_forum.save(force_update=True)
                 self.forum.sync()
@@ -183,14 +179,11 @@ class ThreadsListModeration(object):
 
     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)
+                thread.set_checkpoint(self.request, 'opened')
         if opened:
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
             Thread.objects.filter(id__in=opened).update(closed=False)
         return opened
 
@@ -202,14 +195,11 @@ class ThreadsListModeration(object):
 
     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)
+                thread.set_checkpoint(self.request, 'closed')
         if closed:
-            Post.objects.filter(id__in=last_posts).update(checkpoints=True)
             Thread.objects.filter(id__in=closed).update(closed=True)
         return closed
 
@@ -221,7 +211,6 @@ class ThreadsListModeration(object):
 
     def _action_undelete(self, ids):
         undeleted = []
-        last_posts = []
         posts = 0
         for thread in self.threads:
             if thread.pk in ids and thread.deleted:
@@ -229,13 +218,12 @@ class ThreadsListModeration(object):
                 posts += thread.replies + 1
                 thread.start_post.deleted = False
                 thread.start_post.save(force_update=True)
-                thread.last_post.set_checkpoint(self.request, 'undeleted')
+                thread.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)
         return undeleted
 
@@ -247,7 +235,6 @@ class ThreadsListModeration(object):
 
     def _action_soft(self, ids):
         deleted = []
-        last_posts = []
         posts = 0
         for thread in self.threads:
             if thread.pk in ids and not thread.deleted:
@@ -255,14 +242,12 @@ class ThreadsListModeration(object):
                 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)
+                thread.set_checkpoint(self.request, 'deleted')
         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)
         return deleted
 

+ 2 - 4
misago/apps/threadtype/posting/editreply.py

@@ -32,11 +32,9 @@ class EditReplyBaseView(PostingBaseView):
             self.thread.closed = not self.thread.closed
             changed_thread = True
             if self.thread.closed:
-                self.thread.last_post.set_checkpoint(self.request, 'closed')
+                self.thread.set_checkpoint(self.request, 'closed')
             else:
-                self.thread.last_post.set_checkpoint(self.request, 'opened')
-            if self.thread.last_post_id != self.post.pk or not changed_post:
-                self.thread.last_post.save(force_update=True)
+                self.thread.set_checkpoint(self.request, 'opened')
 
         if ('thread_weight' in form.cleaned_data and
                 form.cleaned_data['thread_weight'] != self.thread.weight):

+ 2 - 4
misago/apps/threadtype/posting/editthread.py

@@ -35,11 +35,9 @@ class EditThreadBaseView(PostingBaseView):
             self.thread.closed = not self.thread.closed
             changed_thread = True
             if self.thread.closed:
-                self.thread.last_post.set_checkpoint(self.request, 'closed')
+                self.thread.set_checkpoint(self.request, 'closed')
             else:
-                self.thread.last_post.set_checkpoint(self.request, 'opened')
-            if self.thread.last_post_id != self.post.pk or not changed_post:
-                self.thread.last_post.save(force_update=True)
+                self.thread.set_checkpoint(self.request, 'opened')
 
         if ('thread_weight' in form.cleaned_data and
                 form.cleaned_data['thread_weight'] != self.thread.weight):

+ 3 - 5
misago/apps/threadtype/posting/newreply.py

@@ -104,15 +104,13 @@ class NewReplyBaseView(PostingBaseView):
                 and not merged and not moderation and not self.thread.closed
                 and self.thread.replies >= self.request.settings.thread_length):
             self.thread.closed = True
-            self.post.set_checkpoint(self.request, 'limit')
-            self.post.save(force_update=True)
+            self.thread.set_checkpoint(self.request, 'limit')
         elif 'close_thread' in form.cleaned_data and form.cleaned_data['close_thread']:
             self.thread.closed = not self.thread.closed
             if self.thread.closed:
-                self.post.set_checkpoint(self.request, 'closed')
+                self.thread.set_checkpoint(self.request, 'closed')
             else:
-                self.post.set_checkpoint(self.request, 'opened')
-            checkpoint_post.save(force_update=True)
+                self.thread.set_checkpoint(self.request, 'opened')
 
         # Save updated thread
         self.thread.save(force_update=True)

+ 6 - 7
misago/apps/threadtype/thread/moderation/thread.py

@@ -12,7 +12,7 @@ class ThreadModeration(object):
         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')
+        self.thread.set_checkpoint(self.request, 'accepted')
         # Sync user
         if self.thread.last_post.user:
             self.thread.start_post.user.threads += 1
@@ -62,8 +62,7 @@ class ThreadModeration(object):
                 new_forum = form.cleaned_data['new_forum']
                 self.thread.move_to(new_forum)
                 self.thread.save(force_update=True)
-                self.thread.last_post.set_checkpoint(self.request, 'moved', forum=self.forum)
-                self.thread.last_post.save(force_update=True)
+                self.thread.set_checkpoint(self.request, 'moved', forum=self.forum)
                 self.forum.sync()
                 self.forum.save(force_update=True)
                 new_forum.sync()
@@ -87,7 +86,7 @@ class ThreadModeration(object):
     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.thread.set_checkpoint(self.request, 'opened')
         self.after_thread_action_open()
 
     def after_thread_action_open(self):
@@ -96,7 +95,7 @@ class ThreadModeration(object):
     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.thread.set_checkpoint(self.request, 'closed')
         self.after_thread_action_close()
 
     def after_thread_action_close(self):
@@ -111,7 +110,7 @@ class ThreadModeration(object):
         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')
+        self.thread.set_checkpoint(self.request, 'undeleted')
         # Update forum
         self.forum.sync()
         self.forum.save(force_update=True)
@@ -132,7 +131,7 @@ class ThreadModeration(object):
         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')
+        self.thread.set_checkpoint(self.request, 'deleted')
         # Update forum
         self.forum.sync()
         self.forum.save(force_update=True)

+ 8 - 7
misago/apps/threadtype/thread/views.py

@@ -37,7 +37,7 @@ class ThreadBaseView(ViewBase):
 
     def fetch_posts(self):
         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')
+        self.posts = self.request.acl.threads.filter_posts(self.request, self.thread, Post.objects.filter(thread=self.thread)).prefetch_related('user', 'user__rank')
         
         if self.thread.merges > 0:
             self.posts = self.posts.order_by('merge', 'pk')
@@ -49,8 +49,11 @@ class ThreadBaseView(ViewBase):
         except Http404:
             return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
 
+        checkpoints_range = None
         if self.request.settings.posts_per_page < self.count:
-            self.posts = self.posts[self.pagination['start']:self.pagination['stop']]
+            self.posts = self.posts[self.pagination['start']:self.pagination['stop'] + 1]
+            checkpoints_range = self.posts[-1].date
+            self.posts = self.posts[0:-1]
 
         self.read_date = self.tracker.read_date(self.thread)
 
@@ -67,11 +70,9 @@ class ThreadBaseView(ViewBase):
             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
-            post.checkpoints_visible = []
-            if post.checkpoint_set.all():
-                for checkpoint in post.checkpoint_set.all():
-                    if self.request.acl.threads.can_see_checkpoint(self.forum, checkpoint):
-                        post.checkpoints_visible.append(checkpoint)
+
+        self.thread.set_checkpoints(self.request.acl.threads.can_see_all_checkpoints(self.forum),
+                                    self.posts, checkpoints_range)
 
         last_post = self.posts[len(self.posts) - 1]
 

+ 410 - 0
misago/migrations/0010_auto__del_field_checkpoint_post__del_field_monitoritem_value__add_fiel.py

@@ -0,0 +1,410 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Deleting field 'Checkpoint.post'
+        db.delete_column(u'misago_checkpoint', 'post_id')
+
+        # Deleting field 'Post.checkpoints'
+        db.delete_column(u'misago_post', 'checkpoints')
+
+
+    def backwards(self, orm):
+
+        # User chose to not deal with backwards NULL issues for 'Checkpoint.post'
+        raise RuntimeError("Cannot reverse this migration. 'Checkpoint.post' and its values cannot be restored.")
+        # Adding field 'MonitorItem.value'
+        db.add_column(u'misago_monitoritem', 'value',
+                      self.gf('django.db.models.fields.TextField')(null=True, blank=True),
+                      keep_default=False)
+
+        # Deleting field 'MonitorItem._value'
+        db.delete_column(u'misago_monitoritem', 'value')
+
+        # Adding field 'Post.checkpoints'
+        db.add_column(u'misago_post', 'checkpoints',
+                      self.gf('django.db.models.fields.BooleanField')(default=False),
+                      keep_default=False)
+
+
+    models = {
+        'misago.alert': {
+            'Meta': {'object_name': 'Alert'},
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"}),
+            'variables': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'misago.ban': {
+            'Meta': {'object_name': 'Ban'},
+            'ban': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'test': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.change': {
+            'Meta': {'object_name': 'Change'},
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'change': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
+            'post_content': ('django.db.models.fields.TextField', [], {}),
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'thread_name_new': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'thread_name_old': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.checkpoint': {
+            'Meta': {'object_name': 'Checkpoint'},
+            'action': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'old_forum': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['misago.Forum']"}),
+            'old_forum_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'old_forum_slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'target_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'target_user_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'target_user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.fixture': {
+            'Meta': {'object_name': 'Fixture'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.forum': {
+            'Meta': {'object_name': 'Forum'},
+            'attrs': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description_preparsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_poster': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'last_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_thread': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Thread']"}),
+            'last_thread_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_thread_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_thread_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['misago.Forum']"}),
+            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'posts_delta': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'prune_last': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'prune_start': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'pruned_archive': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Forum']"}),
+            'redirect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'redirects': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'redirects_delta': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'show_details': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
+            'special': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'threads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'threads_delta': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
+        },
+        'misago.forumread': {
+            'Meta': {'object_name': 'ForumRead'},
+            'cleared': ('django.db.models.fields.DateTimeField', [], {}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
+        },
+        'misago.forumrole': {
+            'Meta': {'object_name': 'ForumRole'},
+            '_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'permissions'", 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.karma': {
+            'Meta': {'object_name': 'Karma'},
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.monitoritem': {
+            'Meta': {'object_name': 'MonitorItem'},
+            '_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'value'", 'blank': 'True'}),
+            'id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'default': "'int'", 'max_length': '255'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'misago.newsletter': {
+            'Meta': {'object_name': 'Newsletter'},
+            'content_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'content_plain': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ignore_subscriptions': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'progress': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'ranks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Rank']", 'symmetrical': 'False'}),
+            'step_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '32'})
+        },
+        'misago.post': {
+            'Meta': {'object_name': 'Post'},
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'downvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'edit_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'edit_reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'edit_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'edit_user_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'edit_user_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'edits': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'mentions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'mention_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'merge': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'post': ('django.db.models.fields.TextField', [], {}),
+            'post_preparsed': ('django.db.models.fields.TextField', [], {}),
+            'protected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'reported': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'upvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.pruningpolicy': {
+            'Meta': {'object_name': 'PruningPolicy'},
+            'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_visit': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'registered': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.rank': {
+            'Meta': {'object_name': 'Rank'},
+            'as_tab': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'criteria': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'on_index': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'roles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Role']", 'symmetrical': 'False'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'special': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
+        },
+        'misago.role': {
+            'Meta': {'object_name': 'Role'},
+            '_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'permissions'", 'blank': 'True'}),
+            '_special': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_column': "'special'", 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'protected': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'misago.session': {
+            'Meta': {'object_name': 'Session'},
+            'admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'crawler': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'data': ('django.db.models.fields.TextField', [], {'db_column': "'session_data'"}),
+            'id': ('django.db.models.fields.CharField', [], {'max_length': '42', 'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'last': ('django.db.models.fields.DateTimeField', [], {}),
+            'matched': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'rank': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Rank']"}),
+            'start': ('django.db.models.fields.DateTimeField', [], {}),
+            'team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"})
+        },
+        'misago.setting': {
+            'Meta': {'object_name': 'Setting'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'extra': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'field': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.SettingsGroup']", 'to_field': "'key'"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'normalize_to': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'separator': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
+            'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'value_default': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'misago.settingsgroup': {
+            'Meta': {'object_name': 'SettingsGroup'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.signinattempt': {
+            'Meta': {'object_name': 'SignInAttempt'},
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
+        },
+        'misago.thread': {
+            'Meta': {'object_name': 'Thread'},
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'downvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last': ('django.db.models.fields.DateTimeField', [], {}),
+            'last_post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Post']"}),
+            'last_poster': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'last_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'merges': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'participants': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'private_thread_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'replies': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'replies_deleted': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'replies_moderated': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'replies_reported': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'report_for': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'report_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Post']"}),
+            'score': ('django.db.models.fields.PositiveIntegerField', [], {'default': '30'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
+            'start': ('django.db.models.fields.DateTimeField', [], {}),
+            'start_post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Post']"}),
+            'start_poster': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'start_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'start_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
+            'start_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'upvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'weight': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.threadread': {
+            'Meta': {'object_name': 'ThreadRead'},
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
+        },
+        'misago.token': {
+            'Meta': {'object_name': 'Token'},
+            'accessed': ('django.db.models.fields.DateTimeField', [], {}),
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.CharField', [], {'max_length': '42', 'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'signin_tokens'", 'to': "orm['misago.User']"})
+        },
+        'misago.user': {
+            'Meta': {'object_name': 'User'},
+            'acl_key': ('django.db.models.fields.CharField', [], {'max_length': '12', 'null': 'True', 'blank': 'True'}),
+            'activation': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'alerts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'alerts_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'allow_pds': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'avatar_ban': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'avatar_ban_reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'avatar_ban_reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'avatar_image': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'avatar_original': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'avatar_temp': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'avatar_type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}),
+            'email_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
+            'followers': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'following': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'follows': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'follows_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'hide_activity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ignores': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ignores_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'is_team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'join_agent': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'join_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'join_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'karma_given_n': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'karma_given_p': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'karma_n': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'karma_p': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'last_agent': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
+            'last_post': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_search': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'password_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'rank': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Rank']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'ranking': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'receive_newsletters': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'roles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Role']", 'symmetrical': 'False'}),
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'signature_ban': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'signature_ban_reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'signature_ban_reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'signature_preparsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'subscribe_reply': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'subscribe_start': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'sync_pds': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'threads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'timezone': ('django.db.models.fields.CharField', [], {'default': "'utc'", 'max_length': '255'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '12', 'null': 'True', 'blank': 'True'}),
+            'unread_pds': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username_slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
+            'votes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.usernamechange': {
+            'Meta': {'object_name': 'UsernameChange'},
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'old_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'namechanges'", 'to': "orm['misago.User']"})
+        },
+        'misago.watchedthread': {
+            'Meta': {'object_name': 'WatchedThread'},
+            'email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_read': ('django.db.models.fields.DateTimeField', [], {}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
+        }
+    }
+
+    complete_apps = ['misago']

+ 0 - 20
misago/models/checkpointmodel.py

@@ -5,7 +5,6 @@ from misago.signals import (merge_post, merge_thread, move_forum_content,
 class Checkpoint(models.Model):
     forum = models.ForeignKey('Forum')
     thread = models.ForeignKey('Thread')
-    post = models.ForeignKey('Post')
     action = models.CharField(max_length=255)
     user = models.ForeignKey('User', null=True, blank=True, on_delete=models.SET_NULL)
     user_name = models.CharField(max_length=255)
@@ -59,22 +58,3 @@ def merge_thread_handler(sender, **kwargs):
     Checkpoint.objects.filter(thread=sender).delete()
 
 merge_thread.connect(merge_thread_handler, dispatch_uid="merge_threads_checkpoints")
-
-
-def move_posts_handler(sender, **kwargs):
-    if sender.checkpoints:
-        prev_post = Post.objects.filter(thread=sender.thread_id).filter(merge__lte=sender.merge).exclude(id=sender.pk).order_by('merge', '-id')[:1][0]
-        Checkpoint.objects.filter(post=sender).update(post=prev_post)
-        prev_post.checkpoints = True
-        prev_post.save(force_update=True)
-    sender.checkpoints = False
-
-move_post.connect(move_posts_handler, dispatch_uid="move_posts_checkpoints")
-
-
-def merge_posts_handler(sender, **kwargs):
-    Checkpoint.objects.filter(post=sender).update(post=kwargs['new_post'])
-    if sender.checkpoints:
-        kwargs['new_post'].checkpoints = True
-
-merge_post.connect(merge_posts_handler, dispatch_uid="merge_posts_checkpoints")

+ 1 - 49
misago/models/postmodel.py

@@ -27,7 +27,6 @@ class Post(models.Model):
     upvotes = models.PositiveIntegerField(default=0)
     downvotes = models.PositiveIntegerField(default=0)
     mentions = models.ManyToManyField('User', related_name="mention_set")
-    checkpoints = models.BooleanField(default=False)
     date = models.DateTimeField()
     edits = models.PositiveIntegerField(default=0)
     edit_date = models.DateTimeField(null=True, blank=True)
@@ -80,40 +79,6 @@ class Post(models.Model):
         post.post = '%s\n- - -\n%s' % (post.post, self.post)
         merge_post.send(sender=self, new_post=post)
 
-    def set_checkpoint(self, request, action, user=None, forum=None):
-        if request.user.is_authenticated():
-            self.checkpoints = True
-            self.checkpoint_set.create(
-                                       forum=self.forum,
-                                       thread=self.thread,
-                                       post=self,
-                                       action=action,
-                                       user=request.user,
-                                       user_name=request.user.username,
-                                       user_slug=request.user.username_slug,
-                                       date=timezone.now(),
-                                       ip=request.session.get_ip(request),
-                                       agent=request.META.get('HTTP_USER_AGENT'),
-                                       target_user=user,
-                                       target_user_name=(user.username if user else None),
-                                       target_user_slug=(user.username_slug if user else None),
-                                       old_forum=forum,
-                                       old_forum_name=(forum.name if forum else None),
-                                       old_forum_slug=(forum.slug if forum else None),
-                                       )
-            
-    def previous(self):
-        return self.thread.post_set.filter(merge__lte=self.merge).exclude(id=self.pk).order_by('-merge', '-date')[:1][0]
-
-    def pass_checkpoints(self):
-        if self.checkpoints:
-            prev = self.previous()
-            self.checkpoints = False
-            self.checkpoint_set.update(post=prev)
-            if not prev.checkpoints:
-                prev.checkpoints = True
-                prev.save(force_update=True)
-
     def notify_mentioned(self, request, thread_type, users):
         from misago.acl.builder import acl
         from misago.acl.exceptions import ACLError403, ACLError404
@@ -161,24 +126,11 @@ def delete_user_content_handler(sender, **kwargs):
     from misago.models import Thread
 
     threads = []
-    prev_posts = []
-
-    for post in sender.post_set.filter(checkpoints=True):
-        threads.append(post.thread_id)
-        prev_post = Post.objects.filter(thread=post.thread_id).exclude(merge__gt=post.merge).exclude(user=sender).order_by('merge', '-id')[:1][0]
-        post.checkpoint_set.update(post=prev_post)
-        if not prev_post.pk in prev_posts:
-            prev_posts.append(prev_post.pk)
-
-    sender.post_set.all().delete()
-    Post.objects.filter(id__in=prev_posts).update(checkpoints=True)
-
     for post in sender.post_set.distinct().values('thread_id').iterator():
         if not post['thread_id'] in threads:
             threads.append(post['thread_id'])
 
-    for post in Post.objects.filter(user=sender):
-        post.delete()
+    sender.post_set.all().delete()
 
     for thread in Thread.objects.filter(id__in=threads):
         thread.sync()

+ 35 - 0
misago/models/threadmodel.py

@@ -100,6 +100,41 @@ class Thread(models.Model):
     def get_date(self):
         return self.start
 
+    def set_checkpoints(self, show_all, posts, stop=None):
+        qs = self.checkpoint_set.filter(date__gte=posts[0].date)
+        if not show_all:
+            qs = qs.filter(deleted=False)
+        if stop:
+            qs = qs.filter(date__lte=stop)
+        checkpoints = [i for i in qs]
+
+        i_max = len(posts) - 1
+        for i, post in enumerate(posts):
+            post.checkpoints_visible = []
+            for c in checkpoints:
+                if c.date >= post.date and (i == i_max or c.date < posts[i+1].date):
+                    post.checkpoints_visible.append(c)
+
+    def set_checkpoint(self, request, action, user=None, forum=None):
+        if request.user.is_authenticated():
+            self.checkpoint_set.create(
+                                       forum=self.forum,
+                                       thread=self,
+                                       action=action,
+                                       user=request.user,
+                                       user_name=request.user.username,
+                                       user_slug=request.user.username_slug,
+                                       date=timezone.now(),
+                                       ip=request.session.get_ip(request),
+                                       agent=request.META.get('HTTP_USER_AGENT'),
+                                       target_user=user,
+                                       target_user_name=(user.username if user else None),
+                                       target_user_slug=(user.username_slug if user else None),
+                                       old_forum=forum,
+                                       old_forum_name=(forum.name if forum else None),
+                                       old_forum_slug=(forum.slug if forum else None),
+                                       )
+
     def new_start_post(self, post):
         self.start = post.date
         self.start_post = post

+ 3 - 1
requirements.txt

@@ -1,5 +1,6 @@
 django
 django-debug-toolbar
+django-haystack
 django-mptt
 coffin
 jinja2
@@ -9,4 +10,5 @@ Pillow
 pytz
 recaptcha-client
 South
-Unidecode
+Unidecode
+whoosh

+ 6 - 3
templates/cranefly/private_threads/thread.html

@@ -335,20 +335,23 @@
               {% if user.is_authenticated() %}
               {% if acl.threads.can_delete_checkpoint(forum) %}
               {% if checkpoint.deleted %}
-              <form action="{% url 'private_post_checkpoint_show' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
+              <form action="{% url 'private_post_checkpoint_show' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
                 <button type="submit" class="btn btn-link btn-show">{% trans %}Restore{% endtrans %}</button>
               </form>
               {% else %}
-              <form action="{% url 'private_post_checkpoint_hide' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
+              <form action="{% url 'private_post_checkpoint_hide' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
                 <button type="submit" class="btn btn-link btn-hide">{% trans %}Hide{% endtrans %}</button>
               </form>
               {% endif %}
               {% endif %}
               {% if acl.threads.can_delete_checkpoint(forum) == 2 %}
-              <form action="{% url 'private_post_checkpoint_delete' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline prompt-delete-checkpoint">
+              <form action="{% url 'private_post_checkpoint_delete' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline prompt-delete-checkpoint">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
                 <button type="submit" class="btn btn-link btn-delete">{% trans %}Delete{% endtrans %}</button>
               </form>
               {% endif %}

+ 7 - 4
templates/cranefly/reports/thread.html

@@ -102,7 +102,7 @@
             {% endif %}
 
             {% if posts_form and thread.start_post_id == post.pk %}
-            <a href="{% url 'report_find' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-perma tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}"><i class="icon-">#1</i></a>
+            <a href="{% url 'report_find' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-perma tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#1</i></a>
             {% else %}
             <a href="{% url 'report_find' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-perma tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#{{ pagination['start'] + loop.index0 }}</a>
             {% endif %}
@@ -249,20 +249,23 @@
           {%- endif -%}
           {% if acl.threads.can_delete_checkpoint(forum) %}
           {% if checkpoint.deleted %}
-          <form action="{% url 'report_post_checkpoint_show' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
+          <form action="{% url 'report_post_checkpoint_show' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
             <button type="submit" class="btn btn-link btn-show">{% trans %}Restore{% endtrans %}</button>
           </form>
           {% else %}
-          <form action="{% url 'report_post_checkpoint_hide' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
+          <form action="{% url 'report_post_checkpoint_hide' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
             <button type="submit" class="btn btn-link btn-hide">{% trans %}Hide{% endtrans %}</button>
           </form>
           {% endif %}
           {% endif %}
           {% if acl.threads.can_delete_checkpoint(forum) == 2 %}
-          <form action="{% url 'report_post_checkpoint_delete' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline prompt-delete-checkpoint">
+          <form action="{% url 'report_post_checkpoint_delete' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline prompt-delete-checkpoint">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
             <button type="submit" class="btn btn-link btn-delete">{% trans %}Delete{% endtrans %}</button>
           </form>
           {% endif %}

+ 6 - 3
templates/cranefly/threads/thread.html

@@ -353,20 +353,23 @@
           {% if user.is_authenticated() %}
           {% if acl.threads.can_delete_checkpoint(forum) %}
           {% if checkpoint.deleted %}
-          <form action="{% url 'post_checkpoint_show' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
+          <form action="{% url 'post_checkpoint_show' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
             <button type="submit" class="btn btn-link btn-show">{% trans %}Restore{% endtrans %}</button>
           </form>
           {% else %}
-          <form action="{% url 'post_checkpoint_hide' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
+          <form action="{% url 'post_checkpoint_hide' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
             <button type="submit" class="btn btn-link btn-hide">{% trans %}Hide{% endtrans %}</button>
           </form>
           {% endif %}
           {% endif %}
           {% if acl.threads.can_delete_checkpoint(forum) == 2 %}
-          <form action="{% url 'post_checkpoint_delete' slug=thread.slug, thread=thread.pk, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline prompt-delete-checkpoint">
+          <form action="{% url 'post_checkpoint_delete' slug=thread.slug, thread=thread.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline prompt-delete-checkpoint">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+            <input type="hidden" name="retreat" value="{{ request_path }}#post-{{ post.pk }}">
             <button type="submit" class="btn btn-link btn-delete">{% trans %}Delete{% endtrans %}</button>
           </form>
           {% endif %}