Browse Source

Leave private thread.

Ralfp 12 years ago
parent
commit
4ee9470328

+ 1 - 1
misago/apps/admin/users/views.py

@@ -315,7 +315,7 @@ class Edit(FormWidget):
         # Do signature mumbo-jumbo
         if form.cleaned_data['signature']:
             target.signature = form.cleaned_data['signature']
-            target.signature_preparsed = signature_markdown(target.get_acl(self.request),
+            target.signature_preparsed = signature_markdown(target.acl(self.request),
                                                             form.cleaned_data['signature'])
         else:
             target.signature = None

+ 31 - 1
misago/apps/privatethreads/jumps.py

@@ -1,3 +1,6 @@
+from django.utils.translation import ugettext as _
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.models import User
 from misago.apps.threadtype.jumps import *
 from misago.apps.privatethreads.mixins import TypeMixin
 
@@ -48,4 +51,31 @@ class InviteUserView(JumpView, TypeMixin):
 
 class RemoveUserView(JumpView, TypeMixin):
     def make_jump(self):
-        print 'ZOMG REMOVING USER'
+        target_user = int(self.request.POST.get('user', 0))
+        if (not (self.request.user.pk == self.thread.start_poster_id or
+                self.request.acl.private_threads.is_mod()) and
+                target_user != self.request.user.pk):
+            raise ACLError403(_("You don't have permission to remove discussion participants."))
+        try:
+            user = self.thread.participants.get(id=target_user)
+            self.thread.participants.remove(user)
+            self.thread.threadread_set.filter(id=user.pk).delete()
+            self.thread.watchedthread_set.filter(id=user.pk).delete()
+            # If there are no more participants in thread, remove it
+            if self.thread.participants.count() == 0:
+                self.thread.delete()
+                self.request.messages.set_flash(Message(_('Thread has been deleted because last participant left it.')), 'info', 'threads')
+                return self.threads_list_redirect()
+            # Nope, see if we removed ourselves
+            if user.pk == self.request.user.pk:
+                self.thread.last_post.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
+            self.thread.last_post.set_checkpoint(self.request, 'removed', user)
+            self.thread.last_post.save(force_update=True)
+            self.request.messages.set_flash(Message(_('Selected participant was removed from thread.')), 'info', 'threads')
+            return self.retreat_redirect()
+        except User.DoesNotExist:
+            self.request.messages.set_flash(Message(_('Requested thread participant does not exist.')), 'error', 'threads')
+            return self.retreat_redirect()

+ 2 - 0
misago/apps/privatethreads/mixins.py

@@ -1,3 +1,5 @@
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
 from misago.acl.exceptions import ACLError404
 
 class TypeMixin(object):

+ 20 - 18
misago/apps/privatethreads/urls.py

@@ -10,22 +10,24 @@ urlpatterns = patterns('misago.apps.privatethreads',
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="private_thread_reply"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="private_thread_reply"),
     url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="private_post_edit"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="private_thread"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>\d+)/$', 'thread.ThreadView', name="private_thread"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="private_thread_last"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="private_thread_find"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="private_thread_new"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="private_thread_show_hidden"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="private_thread_watch"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="private_thread_watch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="private_thread_unwatch"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="private_thread_unwatch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="private_thread_delete", kwargs={'mode': 'delete_thread'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide", kwargs={'mode': 'hide_thread'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="private_post_delete", kwargs={'mode': 'delete_post'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="private_post_hide", kwargs={'mode': 'hide_post'}),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="private_post_info"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="private_thread_changelog"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="private_thread_changelog_diff"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="private_thread_changelog_revert"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="private_thread"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>\d+)/$', 'thread.ThreadView', name="private_thread"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="private_thread_last"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="private_thread_find"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="private_thread_new"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="private_thread_show_hidden"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="private_thread_watch"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="private_thread_watch_email"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="private_thread_unwatch"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="private_thread_unwatch_email"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/invite/$', 'jumps.InviteUserView', name="private_thread_invite_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", kwargs={'mode': 'delete_thread'}),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide", kwargs={'mode': 'hide_thread'}),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="private_post_delete", kwargs={'mode': 'delete_post'}),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="private_post_hide", kwargs={'mode': 'hide_post'}),
+    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"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="private_thread_changelog_revert"),
 )

+ 5 - 0
misago/apps/threadtype/base.py

@@ -49,3 +49,8 @@ class ViewBase(object):
 
     def template_vars(self, context):
         return context
+
+    def retreat_redirect(self):
+        if self.request.POST.get('retreat'):
+            return redirect(self.request.POST.get('retreat'))
+        return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))

+ 1 - 1
misago/auth.py

@@ -108,7 +108,7 @@ def auth_admin(request, email, password):
     Admin auth - check ACP permissions
     """
     user = get_user(email, password, True)
-    if not user.is_god() and not user.get_acl(request).admin.is_admin():
+    if not user.is_god() and not user.acl(request).special.is_admin():
         raise AuthException(NOT_ADMIN, _("Your account does not have admin privileges."))
     return user;
 

+ 4 - 4
misago/migrations/0001_initial.py

@@ -61,8 +61,8 @@ class Migration(SchemaMigration):
             ('user_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
             ('user_slug', self.gf('django.db.models.fields.CharField')(max_length=255)),
             ('target_user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='+', null=True, on_delete=models.SET_NULL, to=orm['misago.User'])),
-            ('target_user_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
-            ('target_user_slug', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('target_user_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
+            ('target_user_slug', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
             ('date', self.gf('django.db.models.fields.DateTimeField')()),
             ('ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39)),
             ('agent', self.gf('django.db.models.fields.CharField')(max_length=255)),
@@ -619,8 +619,8 @@ class Migration(SchemaMigration):
             'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
             'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
             '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'}),
-            'target_user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            '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'}),

+ 2 - 2
misago/models/checkpointmodel.py

@@ -11,8 +11,8 @@ class Checkpoint(models.Model):
     user_name = models.CharField(max_length=255)
     user_slug = models.CharField(max_length=255)
     target_user = models.ForeignKey('User', null=True, blank=True, on_delete=models.SET_NULL, related_name='+')
-    target_user_name = models.CharField(max_length=255)
-    target_user_slug = models.CharField(max_length=255)
+    target_user_name = models.CharField(max_length=255, null=True, blank=True)
+    target_user_slug = models.CharField(max_length=255, null=True, blank=True)
     date = models.DateTimeField()
     ip = models.GenericIPAddressField()
     agent = models.CharField(max_length=255)

+ 4 - 1
misago/models/postmodel.py

@@ -55,7 +55,7 @@ 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):
+    def set_checkpoint(self, request, action, user=None):
         if request.user.is_authenticated():
             self.checkpoints = True
             self.checkpoint_set.create(
@@ -69,6 +69,9 @@ class Post(models.Model):
                                        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),
                                        )
             
     def notify_mentioned(self, request, thread_type, users):

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

@@ -28,7 +28,9 @@
         {% trans %}No replies{% endtrans %}
       {%- endif %}</li>
       <li class="stats-form">
-        <form action="" method="post">
+        <form class="leave-form" action="{% url 'private_thread_remove_user' thread=thread.pk, slug=thread.slug %}" method="post">
+          <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+          <input type="hidden" name="user" value="{{ user.pk }}">
           <button type="submit" class="btn"><i class="icon-remove"></i> Leave Thread</button>
         </form>
       </li>
@@ -319,8 +321,10 @@
               <i class="icon-trash"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} deleted this thread {{ date }}{% endtrans %}
               {%- elif checkpoint.action == 'undeleted' -%}
               <i class="icon-trash"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} restored this thread {{ date }}{% endtrans %}
-              {%- elif checkpoint.action == 'invited' -%}
-              <i class="icon-trash"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} restored this thread {{ date }}{% endtrans %}
+              {%- elif checkpoint.action == 'removed' -%}
+              <i class="icon-remove-sign"></i> {% trans user=checkpoint_user(checkpoint), removed=checkpoint_target(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} removed {{ removed }} from thread {{ date }}{% endtrans %}
+              {%- elif checkpoint.action == 'left' -%}
+              <i class="icon-remove-sign"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} left thread {{ date }}{% endtrans %}
               {%- endif -%}
             </span>
           </div>
@@ -383,9 +387,10 @@
           <li>
             <img src="{{ participant.get_avatar(24) }}" alt="" class="avatar-small">
             <a href="{% url 'user' username=participant.username_slug, user=participant.pk %}">{{ participant.username }}</a>
-            {% if user.pk == thread.start_poster_id %}
-            <form class="form-inline tooltip-left" action="" method="post" title="{% if participant.pk == user.pk %}{% trans %}Leave this thread{% endtrans %}{% else %}{% trans %}Remove from this thread{% endtrans %}{% endif %}">
+            {% if user.pk == thread.start_poster_id or acl.private_threads.is_mod() %}
+            <form class="form-inline {% if user.pk == thread.start_poster_id %}leave-form{% else %}kick-form{% endif %} tooltip-left" action="{% url 'private_thread_remove_user' thread=thread.pk, slug=thread.slug %}" method="post" title="{% if participant.pk == user.pk %}{% trans %}Leave this thread{% endtrans %}{% else %}{% trans %}Remove from this thread{% endtrans %}{% endif %}">
               <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+              <input type="hidden" name="retreat" value="{{ request_path }}">
               <input type="hidden" name="user" value="{{ participant.pk }}">
               <button type="submit" class="btn btn-{% if participant.pk == user.pk %}danger{% else %}inverse{% endif %} btn-small"><i class="icon-remove"></i></button>
             </form>
@@ -424,6 +429,18 @@
     hljs.initHighlightingOnLoad();
     EnhancePostsMD();
     $(function () {
+      $('.leave-form').submit(function() {
+        var decision = confirm("{% if participants|length == 1 -%}
+          {% trans %}Are you sure you want to leave this thread? It will be deleted after you leave!{% endtrans %}
+          {%- else -%}
+          {% trans %}Are you sure you want to leave this thread?{% endtrans %}
+          {%- endif %}");
+        return decision;
+      });
+      $('.kick-form').submit(function() {
+        var decision = confirm("{% trans %}Are you sure you want to remove this member from this thread?{% endtrans %}");
+        return decision;
+      });
       $('#thread_form').submit(function() {
         if ($('#id_thread_action').val() == 'hard') {
           var decision = confirm("{% trans %}Are you sure you want to delete this thread? This action is not reversible!{% endtrans %}");
@@ -498,7 +515,7 @@
 {%- endmacro %}
 
 
-{% macro checkpoint_invited(checkpoint) -%}
+{% macro checkpoint_target(checkpoint) -%}
 {%- if checkpoint.target_user_id -%}
 <a href="{{ 'user'|url(user=checkpoint.target_user_id, username=checkpoint.target_user_slug) }}">{{ checkpoint.target_user_name }}</a>
 {%- else -%}