Browse Source

Restore post/thread from from post level.

Ralfp 12 years ago
parent
commit
6fb6cf4570

+ 4 - 0
misago/acl/permissions/threads.py

@@ -415,6 +415,8 @@ class ThreadsACL(BaseACL):
     def can_delete_thread(self, user, forum, thread, post):
     def can_delete_thread(self, user, forum, thread, post):
         try:
         try:
             forum_role = self.acl[forum.pk]
             forum_role = self.acl[forum.pk]
+            if post.pk != thread.start_post_id:
+                return False
             if not forum_role['can_close_threads'] and (forum.closed or thread.closed):
             if not forum_role['can_close_threads'] and (forum.closed or thread.closed):
                 return False
                 return False
             if post.protected and not forum_role['can_protect_posts']:
             if post.protected and not forum_role['can_protect_posts']:
@@ -449,6 +451,8 @@ class ThreadsACL(BaseACL):
     def can_delete_post(self, user, forum, thread, post):
     def can_delete_post(self, user, forum, thread, post):
         try:
         try:
             forum_role = self.acl[forum.pk]
             forum_role = self.acl[forum.pk]
+            if post.pk == thread.start_post_id:
+                return False
             if not forum_role['can_close_threads'] and (forum.closed or thread.closed):
             if not forum_role['can_close_threads'] and (forum.closed or thread.closed):
                 return False
                 return False
             if post.protected and not forum_role['can_protect_posts']:
             if post.protected and not forum_role['can_protect_posts']:

+ 8 - 0
misago/apps/privatethreads/delete.py

@@ -9,6 +9,10 @@ class HideThreadView(HideThreadBaseView, TypeMixin):
     pass
     pass
 
 
 
 
+class ShowThreadView(ShowThreadBaseView, TypeMixin):
+    pass
+
+
 class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
 class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
     pass
     pass
 
 
@@ -17,6 +21,10 @@ class HideReplyView(HideReplyBaseView, TypeMixin):
     pass
     pass
 
 
 
 
+class ShowReplyView(ShowReplyBaseView, TypeMixin):
+    pass
+
+
 class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
 class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
     pass
     pass
 
 

+ 6 - 6
misago/apps/privatethreads/thread.py

@@ -17,10 +17,10 @@ class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
                 actions.append(('unprotect', _('Remove posts protection')))
                 actions.append(('unprotect', _('Remove posts protection')))
             if acl['can_delete_posts']:
             if acl['can_delete_posts']:
                 if self.thread.replies_deleted > 0:
                 if self.thread.replies_deleted > 0:
-                    actions.append(('undelete', _('Undelete posts')))
-                actions.append(('soft', _('Soft delete posts')))
+                    actions.append(('undelete', _('Restore posts')))
+                actions.append(('soft', _('Hide posts')))
             if acl['can_delete_posts'] == 2:
             if acl['can_delete_posts'] == 2:
-                actions.append(('hard', _('Hard delete posts')))
+                actions.append(('hard', _('Delete posts')))
         except KeyError:
         except KeyError:
             pass
             pass
         return actions
         return actions
@@ -36,11 +36,11 @@ class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
                     actions.append(('close', _('Close this thread')))
                     actions.append(('close', _('Close this thread')))
             if acl['can_delete_threads']:
             if acl['can_delete_threads']:
                 if self.thread.deleted:
                 if self.thread.deleted:
-                    actions.append(('undelete', _('Undelete this thread')))
+                    actions.append(('undelete', _('Restore this thread')))
                 else:
                 else:
-                    actions.append(('soft', _('Soft delete this thread')))
+                    actions.append(('soft', _('Hide this thread')))
             if acl['can_delete_threads'] == 2:
             if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Hard delete this thread')))
+                actions.append(('hard', _('Delete this thread')))
         except KeyError:
         except KeyError:
             pass
             pass
         return actions
         return actions

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

@@ -23,11 +23,13 @@ urlpatterns = patterns('misago.apps.privatethreads',
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/remove/$', 'jumps.RemoveUserView', name="private_thread_remove_user"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/remove/$', 'jumps.RemoveUserView', name="private_thread_remove_user"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="private_thread_delete"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="private_thread_delete"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="private_thread_show"),
     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+)/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+)/hide/$', 'delete.HideReplyView', name="private_post_hide"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="private_post_checkpoint_delete"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="private_post_checkpoint_hide"),
-    url(r'^thread/(?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+)/(?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+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="private_post_info"),
     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/$', '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"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="private_thread_changelog_diff"),

+ 8 - 0
misago/apps/threads/delete.py

@@ -9,6 +9,10 @@ class HideThreadView(HideThreadBaseView, TypeMixin):
     pass
     pass
 
 
 
 
+class ShowThreadView(ShowThreadBaseView, TypeMixin):
+    pass
+
+
 class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
 class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
     pass
     pass
 
 
@@ -17,6 +21,10 @@ class HideReplyView(HideReplyBaseView, TypeMixin):
     pass
     pass
 
 
 
 
+class ShowReplyView(ShowReplyBaseView, TypeMixin):
+    pass
+
+
 class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
 class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
     pass
     pass
 
 

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

@@ -56,10 +56,10 @@ class ThreadsListView(ThreadsListBaseView, ThreadsListModeration, TypeMixin):
                 actions.append(('open', _('Open threads')))
                 actions.append(('open', _('Open threads')))
                 actions.append(('close', _('Close threads')))
                 actions.append(('close', _('Close threads')))
             if acl['can_delete_threads']:
             if acl['can_delete_threads']:
-                actions.append(('undelete', _('Undelete threads')))
-                actions.append(('soft', _('Soft delete threads')))
+                actions.append(('undelete', _('Restore threads')))
+                actions.append(('soft', _('Hide threads')))
             if acl['can_delete_threads'] == 2:
             if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Hard delete threads')))
+                actions.append(('hard', _('Delete threads')))
         except KeyError:
         except KeyError:
             pass
             pass
         return actions
         return actions

+ 6 - 6
misago/apps/threads/thread.py

@@ -19,10 +19,10 @@ class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
                 actions.append(('unprotect', _('Remove posts protection')))
                 actions.append(('unprotect', _('Remove posts protection')))
             if acl['can_delete_posts']:
             if acl['can_delete_posts']:
                 if self.thread.replies_deleted > 0:
                 if self.thread.replies_deleted > 0:
-                    actions.append(('undelete', _('Undelete posts')))
-                actions.append(('soft', _('Soft delete posts')))
+                    actions.append(('undelete', _('Restore posts')))
+                actions.append(('soft', _('Hide posts')))
             if acl['can_delete_posts'] == 2:
             if acl['can_delete_posts'] == 2:
-                actions.append(('hard', _('Hard delete posts')))
+                actions.append(('hard', _('Delete posts')))
         except KeyError:
         except KeyError:
             pass
             pass
         return actions
         return actions
@@ -51,11 +51,11 @@ class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
                     actions.append(('close', _('Close this thread')))
                     actions.append(('close', _('Close this thread')))
             if acl['can_delete_threads']:
             if acl['can_delete_threads']:
                 if self.thread.deleted:
                 if self.thread.deleted:
-                    actions.append(('undelete', _('Undelete this thread')))
+                    actions.append(('undelete', _('Restore this thread')))
                 else:
                 else:
-                    actions.append(('soft', _('Soft delete this thread')))
+                    actions.append(('soft', _('Hide this thread')))
             if acl['can_delete_threads'] == 2:
             if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Hard delete this thread')))
+                actions.append(('hard', _('Delete this thread')))
         except KeyError:
         except KeyError:
             pass
             pass
         return actions
         return actions

+ 2 - 0
misago/apps/threads/urls.py

@@ -24,8 +24,10 @@ urlpatterns = patterns('misago.apps.threads',
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', 'jumps.DownvotePostView', name="post_downvote"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', 'jumps.DownvotePostView', name="post_downvote"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="thread_delete"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="thread_delete"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="thread_hide"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="thread_hide"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="thread_show"),
     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+)/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+)/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+)/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+)/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+)/(?P<post>\d+)/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="post_checkpoint_show"),

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

@@ -55,11 +55,6 @@ class DeleteThreadBaseView(DeleteHideBaseView):
     def set_context(self):
     def set_context(self):
         self.request.acl.threads.allow_delete_thread(self.request.user, self.proxy,
         self.request.acl.threads.allow_delete_thread(self.request.user, self.proxy,
                                                      self.thread, self.thread.start_post, True)
                                                      self.thread, self.thread.start_post, True)
-        # Assert we are not user trying to delete thread with replies
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        if not acl['can_delete_threads']:
-            if self.thread.post_set.exclude(user_id=self.request.user.id).count() > 0:
-                raise ACLError403(_("Somebody has already replied to this thread. You cannot delete it."))
 
 
     def delete(self):
     def delete(self):
         self.thread.delete()
         self.thread.delete()
@@ -102,13 +97,37 @@ class HideThreadBaseView(DeleteHideBaseView):
         return self.threads_list_redirect()
         return self.threads_list_redirect()
 
 
 
 
+class ShowThreadBaseView(DeleteHideBaseView):
+    def set_context(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        if not acl['can_delete_threads']:
+            raise ACLError403(_("You cannot undelete this thread."))
+        if not self.thread.start_post.deleted:
+            raise ACLError403(_('This thread is already visible!'))
+
+    def delete(self):
+        self.thread.start_post.deleted = False
+        self.thread.start_post.save(force_update=True)
+        self.thread.last_post.set_checkpoint(self.request, 'undeleted')
+        self.thread.last_post.save(force_update=True)
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been restored.') % {'thread': self.thread.name}), 'success', 'threads')
+
+    def response(self):
+        if self.request.acl.threads.can_see_deleted_threads(self.thread.forum):
+            return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
+        return self.threads_list_redirect()
+
+
 class DeleteReplyBaseView(DeleteHideBaseView):
 class DeleteReplyBaseView(DeleteHideBaseView):
     def set_context(self):
     def set_context(self):
         self.request.acl.threads.allow_delete_post(self.request.user, self.forum,
         self.request.acl.threads.allow_delete_post(self.request.user, self.forum,
                                                    self.thread, self.post, True)
                                                    self.thread, self.post, True)
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        if not acl['can_delete_posts'] and self.thread.post_set.filter(id__gt=self.post.pk).count() > 0:
-            raise ACLError403(_("Somebody has already replied to this post, you cannot delete it."))
 
 
     def delete(self):
     def delete(self):
         self.post.delete()
         self.post.delete()
@@ -118,7 +137,7 @@ class DeleteReplyBaseView(DeleteHideBaseView):
         self.forum.save(force_update=True)
         self.forum.save(force_update=True)
 
 
     def message(self):
     def message(self):
-        self.request.messages.set_flash(Message(_("Selected Reply has been deleted.")), 'success', 'threads')
+        self.request.messages.set_flash(Message(_("Selected reply has been deleted.")), 'success', 'threads')
 
 
     def response(self):
     def response(self):
         return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
         return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
@@ -145,7 +164,34 @@ class HideReplyBaseView(DeleteHideBaseView):
         self.forum.save(force_update=True)
         self.forum.save(force_update=True)
 
 
     def message(self):
     def message(self):
-        self.request.messages.set_flash(Message(_("Selected Reply has been deleted.")), 'success', 'threads_%s' % self.post.pk)
+        self.request.messages.set_flash(Message(_("Selected reply has been deleted.")), 'success', 'threads_%s' % self.post.pk)
+
+    def response(self):
+        return self.redirect_to_post(self.post)
+
+
+class ShowReplyBaseView(DeleteHideBaseView):
+    def set_context(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        if not acl['can_delete_posts']:
+            raise ACLError403(_("You cannot undelete this reply."))
+        if not self.post.deleted:
+            raise ACLError403(_('This reply is already visible!'))
+
+    def delete(self):
+        self.post.deleted = False
+        self.post.edit_date = timezone.now()
+        self.post.edit_user = self.request.user
+        self.post.edit_user_name = self.request.user.username
+        self.post.edit_user_slug = self.request.user.username_slug
+        self.post.save(force_update=True)
+        self.thread.sync()
+        self.thread.save(force_update=True)
+        self.forum.sync()
+        self.forum.save(force_update=True)
+
+    def message(self):
+        self.request.messages.set_flash(Message(_("Selected reply has been restored.")), 'success', 'threads_%s' % self.post.pk)
 
 
     def response(self):
     def response(self):
         return self.redirect_to_post(self.post)
         return self.redirect_to_post(self.post)
@@ -187,13 +233,15 @@ class HideCheckpointBaseView(DeleteHideBaseView):
 class ShowCheckpointBaseView(DeleteHideBaseView):
 class ShowCheckpointBaseView(DeleteHideBaseView):
     def set_context(self):
     def set_context(self):
         self.request.acl.threads.allow_checkpoint_show(self.forum)
         self.request.acl.threads.allow_checkpoint_show(self.forum)
+        if self.checkpoint.deleted:
+            raise ACLError403(_('This checkpoint is already visible!'))
 
 
     def delete(self):
     def delete(self):
         self.checkpoint.deleted = False
         self.checkpoint.deleted = False
         self.checkpoint.save(force_update=True)
         self.checkpoint.save(force_update=True)
 
 
     def message(self):
     def message(self):
-        self.request.messages.set_flash(Message(_("Selected checkpoint has been made visible.")), 'success', 'threads_%s' % self.post.pk)
+        self.request.messages.set_flash(Message(_("Selected checkpoint has been restored.")), 'success', 'threads_%s' % self.post.pk)
 
 
     def response(self):
     def response(self):
         return self.redirect_to_post(self.post)
         return self.redirect_to_post(self.post)

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

@@ -195,7 +195,7 @@ class ThreadsListModeration(object):
             self.forum.save(force_update=True)
             self.forum.save(force_update=True)
             Post.objects.filter(id__in=last_posts).update(checkpoints=True)
             Post.objects.filter(id__in=last_posts).update(checkpoints=True)
             Thread.objects.filter(id__in=undeleted).update(deleted=False)
             Thread.objects.filter(id__in=undeleted).update(deleted=False)
-            self.request.messages.set_flash(Message(_('Selected threads have been undeleted.')), 'success', 'threads')
+            self.request.messages.set_flash(Message(_('Selected threads have been restored.')), 'success', 'threads')
 
 
     def action_soft(self, ids):
     def action_soft(self, ids):
         deleted = []
         deleted = []
@@ -216,7 +216,7 @@ class ThreadsListModeration(object):
             self.forum.save(force_update=True)
             self.forum.save(force_update=True)
             Post.objects.filter(id__in=last_posts).update(checkpoints=True)
             Post.objects.filter(id__in=last_posts).update(checkpoints=True)
             Thread.objects.filter(id__in=deleted).update(deleted=True)
             Thread.objects.filter(id__in=deleted).update(deleted=True)
-            self.request.messages.set_flash(Message(_('Selected threads have been softly deleted.')), 'success', 'threads')
+            self.request.messages.set_flash(Message(_('Selected threads have been hidden.')), 'success', 'threads')
 
 
     def action_hard(self, ids):
     def action_hard(self, ids):
         deleted = []
         deleted = []

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

@@ -99,7 +99,7 @@ class ThreadModeration(object):
         # Update monitor
         # Update monitor
         self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
         self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
         self.request.monitor['posts'] = int(self.request.monitor['posts']) + self.thread.replies + 1
         self.request.monitor['posts'] = int(self.request.monitor['posts']) + self.thread.replies + 1
-        self.request.messages.set_flash(Message(_('Thread has been undeleted.')), 'success', 'threads')
+        self.request.messages.set_flash(Message(_('Thread has been restored.')), 'success', 'threads')
 
 
     def thread_action_soft(self):
     def thread_action_soft(self):
         # Update thread
         # Update thread
@@ -117,7 +117,7 @@ class ThreadModeration(object):
         # Update monitor
         # Update monitor
         self.request.monitor['threads'] = int(self.request.monitor['threads']) - 1
         self.request.monitor['threads'] = int(self.request.monitor['threads']) - 1
         self.request.monitor['posts'] = int(self.request.monitor['posts']) - self.thread.replies - 1
         self.request.monitor['posts'] = int(self.request.monitor['posts']) - self.thread.replies - 1
-        self.request.messages.set_flash(Message(_('Thread has been deleted.')), 'success', 'threads')
+        self.request.messages.set_flash(Message(_('Thread has been hidden.')), 'success', 'threads')
 
 
     def thread_action_hard(self):
     def thread_action_hard(self):
         # Delete thread
         # Delete thread

+ 1 - 0
static/cranefly/css/cranefly.css

@@ -1129,6 +1129,7 @@ a.btn-link:hover,a.btn-link:active,a.btn-link:focus{opacity:0.9;filter:alpha(opa
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a:first-child{margin-left:0px;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a:first-child{margin-left:0px;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a.post-reply{color:#555555;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a.post-reply:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a.post-reply a:active{color:#049cdb;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a.post-reply{color:#555555;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a.post-reply:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a.post-reply a:active{color:#049cdb;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn{float:right;margin:0px;margin-left:14px;opacity:1;filter:alpha(opacity=100);padding:0px;color:#999999;font-weight:normal;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn:focus{color:#cf402e;text-decoration:underline;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn{float:right;margin:0px;margin-left:14px;opacity:1;filter:alpha(opacity=100);padding:0px;color:#999999;font-weight:normal;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn:focus{color:#cf402e;text-decoration:underline;}
+.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn.btn-hide:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn.btn-hide:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions .btn.btn-hide:focus{color:#f89406;}
 .thread-body .post-wrapper .post-body.post-muted .user-avatar{width:50px;height:50px;opacity:0.75;filter:alpha(opacity=75);}
 .thread-body .post-wrapper .post-body.post-muted .user-avatar{width:50px;height:50px;opacity:0.75;filter:alpha(opacity=75);}
 .thread-body .post-wrapper .post-body.post-muted .post-content{margin-left:71px;min-height:0px;opacity:0.75;filter:alpha(opacity=75);padding:14px;}.thread-body .post-wrapper .post-body.post-muted .post-content .post-header{float:right;margin:0px;margin-top:-7px;margin-right:-14px;}.thread-body .post-wrapper .post-body.post-muted .post-content .post-header .post-header-compact{float:left;margin-right:14px;}
 .thread-body .post-wrapper .post-body.post-muted .post-content{margin-left:71px;min-height:0px;opacity:0.75;filter:alpha(opacity=75);padding:14px;}.thread-body .post-wrapper .post-body.post-muted .post-content .post-header{float:right;margin:0px;margin-top:-7px;margin-right:-14px;}.thread-body .post-wrapper .post-body.post-muted .post-content .post-header .post-header-compact{float:left;margin-right:14px;}
 .thread-body .post-wrapper .post-body.post-muted .post-content .post-message{color:#999999;font-size:17.5px;}.thread-body .post-wrapper .post-body.post-muted .post-content .post-message strong,.thread-body .post-wrapper .post-body.post-muted .post-content .post-message a{color:#333333;font-weight:normal;}
 .thread-body .post-wrapper .post-body.post-muted .post-content .post-message{color:#999999;font-size:17.5px;}.thread-body .post-wrapper .post-body.post-muted .post-content .post-message strong,.thread-body .post-wrapper .post-body.post-muted .post-content .post-message a{color:#333333;font-weight:normal;}

+ 6 - 0
static/cranefly/css/cranefly/thread.less

@@ -306,6 +306,12 @@
                 color: @red;
                 color: @red;
                 text-decoration: underline;
                 text-decoration: underline;
               }
               }
+
+              &.btn-hide {
+                &:hover, &:active, &:focus {
+                  color: @orange;
+                }
+              }
             }
             }
           }
           }
         }
         }

+ 29 - 19
templates/cranefly/private_threads/thread.html

@@ -261,39 +261,49 @@
                 </div>
                 </div>
                 {% if post.pk == thread.start_post_id %}
                 {% if post.pk == thread.start_post_id %}
                 <div class="post-actions">
                 <div class="post-actions">
-                  {% if acl.threads.can_delete_thread(user, forum, thread, post) == 2 %}
-                  <form action="{% url 'private_thread_delete' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+                  {% if acl.threads.can_delete_thread(user, forum, thread, post) %}
+                  {% if post.deleted %}
+                  <form action="{% url 'private_thread_show' thread=thread.pk, slug=thread.slug %}" class="form-inline" method="post">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <span>{% trans %}Delete thread:{% endtrans %}</span>
                     <span>{% trans %}Delete thread:{% endtrans %}</span>
-                    <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this thread for good{% endtrans %}">{% trans %}Hard{% endtrans %}</button>
+                    <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Make this thread visible to other users{% endtrans %}">{% trans %}Restore{% endtrans %}</button>
                   </form>
                   </form>
-                  {% endif %}
-                  {% if not post.deleted and acl.threads.can_delete_thread(user, forum, thread, post) %}
-                  <form action="{% url 'private_thread_hide' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+                  {% else %}
+                  <form action="{% url 'private_thread_hide' thread=thread.pk, slug=thread.slug %}" class="form-inline" method="post">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                    {% if acl.threads.can_delete_thread(user, forum, thread, post) != 2 %}
                     <span>{% trans %}Delete thread:{% endtrans %}</span>
                     <span>{% trans %}Delete thread:{% endtrans %}</span>
-                    {% endif %}
-                    <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Hide this thread from other users{% endtrans %}">{% trans %}Soft{% endtrans %}</button>
+                    <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Hide this thread from other users{% endtrans %}">{% trans %}Hide{% endtrans %}</button>
+                  </form>
+                  {% endif %}
+                  {% endif %}
+                  {% if acl.threads.can_delete_thread(user, forum, thread, post) == 2 %}
+                  <form action="{% url 'private_thread_delete' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+                    <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                    <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this thread for good{% endtrans %}">{% trans %}Delete{% endtrans %}</button>
                   </form>
                   </form>
                   {% endif %}
                   {% endif %}
                 </div>
                 </div>
                 {% elif post.pk != thread.start_post_id and acl.threads.can_delete_post(user, forum, thread, post) %}
                 {% elif post.pk != thread.start_post_id and acl.threads.can_delete_post(user, forum, thread, post) %}
                 <div class="post-actions">
                 <div class="post-actions">
-                  {% if acl.threads.can_delete_post(user, forum, thread, post) == 2 -%}
-                  <form action="{% url 'private_post_delete' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+                  {% if acl.threads.can_delete_post(user, forum, thread, post) %}
+                  {% if post.deleted %}
+                  <form action="{% url 'private_post_show' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline" method="post">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <span>{% trans %}Delete reply:{% endtrans %}</span>
                     <span>{% trans %}Delete reply:{% endtrans %}</span>
-                    <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this reply for good{% endtrans %}">{% trans %}Hard{% endtrans %}</button>
+                    <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Make this reply visible to other users{% endtrans %}">{% trans %}Restore{% endtrans %}</button>
                   </form>
                   </form>
-                  {% endif %}
-                  {% if not post.deleted and acl.threads.can_delete_post(user, forum, thread, post) %}
-                  <form action="{% url 'private_post_hide' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+                  {% else %}
+                  <form action="{% url 'private_post_hide' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline" method="post">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                    {% if acl.threads.can_delete_post(user, forum, thread, post) != 2 %}
                     <span>{% trans %}Delete reply:{% endtrans %}</span>
                     <span>{% trans %}Delete reply:{% endtrans %}</span>
-                    {% endif %}
-                    <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Hide this reply from other users{% endtrans %}">{% trans %}Soft{% endtrans %}</button>
+                    <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Hide this reply from other users{% endtrans %}">{% trans %}Hide{% endtrans %}</button>
+                  </form>
+                  {% endif %}
+                  {% endif %}
+                  {% if acl.threads.can_delete_post(user, forum, thread, post) == 2 -%}
+                  <form action="{% url 'private_post_delete' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+                    <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                    <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this reply for good{% endtrans %}">{% trans %}Delete{% endtrans %}</button>
                   </form>
                   </form>
                   {% endif %}
                   {% endif %}
                 </div>
                 </div>
@@ -334,7 +344,7 @@
               {% if checkpoint.deleted %}
               {% 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, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                <button type="submit" class="btn btn-link btn-show">{% trans %}Show{% endtrans %}</button>
+                <button type="submit" class="btn btn-link btn-show">{% trans %}Restore{% endtrans %}</button>
               </form>
               </form>
               {% else %}
               {% 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, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">

+ 29 - 19
templates/cranefly/threads/thread.html

@@ -294,39 +294,49 @@
             </div>
             </div>
             {% if post.pk == thread.start_post_id %}
             {% if post.pk == thread.start_post_id %}
             <div class="post-actions">
             <div class="post-actions">
-              {% if acl.threads.can_delete_thread(user, forum, thread, post) == 2 %}
-              <form action="{% url 'thread_delete' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+              {% if acl.threads.can_delete_thread(user, forum, thread, post) %}
+              {% if post.deleted %}
+              <form action="{% url 'thread_show' thread=thread.pk, slug=thread.slug %}" class="form-inline" method="post">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <span>{% trans %}Delete thread:{% endtrans %}</span>
                 <span>{% trans %}Delete thread:{% endtrans %}</span>
-                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this thread for good{% endtrans %}">{% trans %}Hard{% endtrans %}</button>
+                <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Make this thread visible to other users{% endtrans %}">{% trans %}Restore{% endtrans %}</button>
               </form>
               </form>
-              {% endif %}
-              {% if not post.deleted and acl.threads.can_delete_thread(user, forum, thread, post) %}
-              <form action="{% url 'thread_hide' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+              {% else %}
+              <form action="{% url 'thread_hide' thread=thread.pk, slug=thread.slug %}" class="form-inline" method="post">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                {% if acl.threads.can_delete_thread(user, forum, thread, post) != 2 %}
                 <span>{% trans %}Delete thread:{% endtrans %}</span>
                 <span>{% trans %}Delete thread:{% endtrans %}</span>
-                {% endif %}
-                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Hide this thread from other users{% endtrans %}">{% trans %}Soft{% endtrans %}</button>
+                <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Hide this thread from other users{% endtrans %}">{% trans %}Hide{% endtrans %}</button>
+              </form>
+              {% endif %}
+              {% endif %}
+              {% if acl.threads.can_delete_thread(user, forum, thread, post) == 2 %}
+              <form action="{% url 'thread_delete' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this thread for good{% endtrans %}">{% trans %}Delete{% endtrans %}</button>
               </form>
               </form>
               {% endif %}
               {% endif %}
             </div>
             </div>
             {% elif post.pk != thread.start_post_id and acl.threads.can_delete_post(user, forum, thread, post) %}
             {% elif post.pk != thread.start_post_id and acl.threads.can_delete_post(user, forum, thread, post) %}
             <div class="post-actions">
             <div class="post-actions">
-              {% if acl.threads.can_delete_post(user, forum, thread, post) == 2 -%}
-              <form action="{% url 'post_delete' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+              {% if acl.threads.can_delete_post(user, forum, thread, post) %}
+              {% if post.deleted %}
+              <form action="{% url 'post_show' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline" method="post">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <span>{% trans %}Delete reply:{% endtrans %}</span>
                 <span>{% trans %}Delete reply:{% endtrans %}</span>
-                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this reply for good{% endtrans %}">{% trans %}Hard{% endtrans %}</button>
+                <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Make this reply visible to other users{% endtrans %}">{% trans %}Restore{% endtrans %}</button>
               </form>
               </form>
-              {% endif %}
-              {% if not post.deleted and acl.threads.can_delete_post(user, forum, thread, post) %}
-              <form action="{% url 'post_hide' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+              {% else %}
+              <form action="{% url 'post_hide' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline" method="post">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                {% if acl.threads.can_delete_post(user, forum, thread, post) != 2 %}
                 <span>{% trans %}Delete reply:{% endtrans %}</span>
                 <span>{% trans %}Delete reply:{% endtrans %}</span>
-                {% endif %}
-                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Hide this reply from other users{% endtrans %}">{% trans %}Soft{% endtrans %}</button>
+                <button type="submit" class="btn btn-link btn-hide tooltip-top" title="{% trans %}Hide this reply from other users{% endtrans %}">{% trans %}Hide{% endtrans %}</button>
+              </form>
+              {% endif %}
+              {% endif %}
+              {% if acl.threads.can_delete_post(user, forum, thread, post) == 2 -%}
+              <form action="{% url 'post_delete' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this reply for good{% endtrans %}">{% trans %}Delete{% endtrans %}</button>
               </form>
               </form>
               {% endif %}
               {% endif %}
             </div>
             </div>
@@ -364,7 +374,7 @@
           {% if checkpoint.deleted %}
           {% 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, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
             <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-            <button type="submit" class="btn btn-link btn-show">{% trans %}Show{% endtrans %}</button>
+            <button type="submit" class="btn btn-link btn-show">{% trans %}Restore{% endtrans %}</button>
           </form>
           </form>
           {% else %}
           {% 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, post=post.pk, checkpoint=checkpoint.pk %}" method="post" class="form-inline">