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

+ 66 - 2
misago/threads/forms.py

@@ -1,8 +1,11 @@
 from django import forms
+from django.conf import settings
 from django.utils.translation import ungettext, ugettext_lazy as _
 from mptt.forms import TreeNodeChoiceField
+from misago.acl.utils import ACLError403, ACLError404
 from misago.forms import Form
 from misago.forums.models import Forum
+from misago.threads.models import Thread
 from misago.utils import slugify
 from misago.utils.validators import validate_sluggable
 
@@ -71,8 +74,69 @@ class PostForm(Form, ThreadNameMixin):
         
         
 
-class SplitThreadForm(Form, ThreadNameMixin):
-    pass
+class SplitThreadForm(Form, ThreadNameMixin):        
+    def finalize_form(self):
+        self.layout = [
+                       [
+                        None,
+                        [
+                         ('thread_name', {'label': _("New Thread Name")}),
+                         ('thread_forum', {'label': _("New Thread Forum")}),
+                         ],
+                        ],
+                       ]
+    
+        self.fields['thread_name'] = forms.CharField(
+                                                     max_length=self.request.settings['thread_name_max'],
+                                                     validators=[validate_sluggable(
+                                                                                    _("Thread name must contain at least one alpha-numeric character."),
+                                                                                    _("Thread name is too long. Try shorter name.")
+                                                                                    )])
+        self.fields['thread_forum'] = TreeNodeChoiceField(queryset=Forum.tree.get(token='root').get_descendants().filter(pk__in=self.request.acl.forums.acl['can_browse']),level_indicator=u'- - ')
+            
+    def clean_thread_forum(self):
+        new_forum = self.cleaned_data['thread_forum']
+        # Assert its forum and its not current forum
+        if new_forum.type != 'forum':
+            raise forms.ValidationError(_("This is not a forum."))
+        return new_forum
+
+
+class MovePostsForm(Form, ThreadNameMixin):  
+    error_source = 'thread_url'
+
+    def __init__(self, data=None, request=None, thread=None, *args, **kwargs):
+        self.thread = thread
+        super(MovePostsForm, self).__init__(data, request=request, *args, **kwargs)
+          
+    def finalize_form(self):
+        self.layout = [
+                       [
+                        None,
+                        [
+                         ('thread_url', {'label': _("New Thread Link"), 'help_text': _("To select new thread, simply copy and paste here its link.")}),
+                         ],
+                        ],
+                       ]
+    
+        self.fields['thread_url'] = forms.CharField()
+            
+    def clean_thread_url(self):
+        from django.core.urlresolvers import resolve
+        from django.http import Http404
+        thread_url = self.cleaned_data['thread_url']
+        try:
+            thread_url = thread_url[len(settings.BOARD_ADDRESS):]
+            match = resolve(thread_url)
+            thread = Thread.objects.get(pk=match.kwargs['thread'])
+            self.request.acl.threads.allow_thread_view(self.request.user, thread)
+            if thread.pk == self.thread.pk:
+                raise forms.ValidationError(_("New thread is same as current one."))
+            return thread
+        except (Http404, KeyError):
+            raise forms.ValidationError(_("This is not a correct thread URL."))
+        except (Thread.DoesNotExist, ACLError403, ACLError404):
+            raise forms.ValidationError(_("Thread could not be found."))
 
 
 class QuickReplyForm(Form):

+ 133 - 17
misago/threads/views/thread.py

@@ -1,8 +1,10 @@
 from django.core.urlresolvers import reverse
 from django import forms
+from django.db.models import F
 from django.forms import ValidationError
 from django.shortcuts import redirect
 from django.template import RequestContext
+from django.utils import timezone
 from django.utils.translation import ugettext as _
 from misago.acl.utils import ACLError403, ACLError404
 from misago.forms import Form, FormLayout, FormFields
@@ -10,11 +12,11 @@ from misago.forums.models import Forum
 from misago.markdown import post_markdown
 from misago.messages import Message
 from misago.readstracker.trackers import ThreadsTracker
-from misago.threads.forms import MoveThreadsForm, SplitThreadForm, QuickReplyForm
-from misago.threads.models import Thread, Post
+from misago.threads.forms import MoveThreadsForm, SplitThreadForm, MovePostsForm, QuickReplyForm
+from misago.threads.models import Thread, Post, Change, Checkpoint
 from misago.threads.views.base import BaseView
 from misago.views import error403, error404
-from misago.utils import make_pagination
+from misago.utils import make_pagination, slugify
 
 class ThreadView(BaseView):
     def fetch_thread(self, thread):
@@ -121,16 +123,7 @@ class ThreadView(BaseView):
             self.thread.post_set.filter(id__in=ids).update(moderated=False)
             self.thread.sync()
             self.thread.save(force_update=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been accepted and made visible to other members.')), 'success', 'threads')
-            
-    def post_action_protect(self, ids):
-        protected = 0
-        for post in self.posts:
-            if post.pk in ids and not post.protected:
-                protected += 1
-        if protected:
-            self.thread.post_set.filter(id__in=ids).update(protected=True)
-            self.request.messages.set_flash(Message(_('Selected posts have been protected from edition.')), 'success', 'threads')            
+            self.request.messages.set_flash(Message(_('Selected posts have been accepted and made visible to other members.')), 'success', 'threads')           
             
     def post_action_merge(self, ids):
         users = []
@@ -159,14 +152,44 @@ class ThreadView(BaseView):
         self.request.messages.set_flash(Message(_('Selected posts have been merged into one message.')), 'success', 'threads')
                     
     def post_action_split(self, ids):
+        for id in ids:
+            if id == self.thread.start_post_id:
+                raise forms.ValidationError(_("You cannot split first post from thread."))
         message = None
         if self.request.POST.get('do') == 'split':
             form = SplitThreadForm(self.request.POST,request=self.request)
             if form.is_valid():
-                return None
+                new_thread = Thread()
+                new_thread.forum = form.cleaned_data['thread_forum']
+                new_thread.name = form.cleaned_data['thread_name']
+                new_thread.slug = slugify(form.cleaned_data['thread_name'])
+                new_thread.start = timezone.now()
+                new_thread.last = timezone.now()
+                new_thread.start_poster_name = 'n'
+                new_thread.start_poster_slug = 'n'
+                new_thread.last_poster_name = 'n'
+                new_thread.last_poster_slug = 'n'
+                new_thread.save(force_insert=True)
+                self.thread.post_set.filter(id__in=ids).update(thread=new_thread, forum=new_thread.forum)
+                Change.objects.filter(post__in=ids).update(thread=new_thread, forum=new_thread.forum)
+                Checkpoint.objects.filter(post__in=ids).update(thread=new_thread, forum=new_thread.forum)
+                new_thread.sync()
+                new_thread.save(force_update=True)
+                self.thread.sync()
+                self.thread.save(force_update=True)
+                self.forum.sync()
+                self.forum.save(force_update=True)
+                if new_thread.forum != self.forum:
+                    new_thread.forum.sync()
+                    new_thread.forum.save(force_update=True)
+                self.request.messages.set_flash(Message(_("Selected posts have been split to new thread.")), 'success', 'threads')
+                return redirect(reverse('thread', kwargs={'thread': new_thread.pk, 'slug': new_thread.slug}))
             message = Message(form.non_field_errors()[0], 'error')
         else:
-            form = SplitThreadForm(request=self.request)
+            form = SplitThreadForm(request=self.request, initial={
+                                                                  'thread_name': _('[Split] %s') % self.thread.name,
+                                                                  'thread_forum': self.forum,
+                                                                  })
         return self.request.theme.render_to_response('threads/split.html',
                                                      {
                                                       'message': message,
@@ -177,7 +200,66 @@ class ThreadView(BaseView):
                                                       'form': FormLayout(form),
                                                       },
                                                      context_instance=RequestContext(self.request));
-        
+    
+    def post_action_move(self, ids):
+        message = None
+        if self.request.POST.get('do') == 'move':
+            form = MovePostsForm(self.request.POST,request=self.request,thread=self.thread)
+            if form.is_valid():
+                thread = form.cleaned_data['thread_url']
+                self.thread.post_set.filter(id__in=ids).update(thread=thread, forum=thread.forum, merge=F('merge') + thread.merges + 1)
+                Change.objects.filter(post__in=ids).update(thread=thread, forum=thread.forum)
+                Checkpoint.objects.filter(post__in=ids).update(thread=thread, forum=thread.forum)
+                if self.thread.post_set.count() == 0:
+                    self.thread.delete()
+                else:
+                    self.thread.sync()
+                    self.thread.save(force_update=True)
+                thread.sync()
+                thread.save(force_update=True)
+                thread.forum.sync()
+                thread.forum.save(force_update=True)
+                if self.forum.pk != thread.forum.pk:
+                    self.forum.sync()
+                    self.forum.save(force_update=True)
+                self.request.messages.set_flash(Message(_("Selected posts have been moved to new thread.")), 'success', 'threads')
+                return redirect(reverse('thread', kwargs={'thread': thread.pk, 'slug': thread.slug}))
+            message = Message(form.non_field_errors()[0], 'error')
+        else:
+            form = MovePostsForm(request=self.request)
+        return self.request.theme.render_to_response('threads/move.html',
+                                                     {
+                                                      'message': message,
+                                                      'forum': self.forum,
+                                                      'parents': self.parents,
+                                                      'thread': self.thread,
+                                                      'posts': ids,
+                                                      'form': FormLayout(form),
+                                                      },
+                                                     context_instance=RequestContext(self.request));
+    
+    def post_action_undelete(self, ids):
+        undeleted = []
+        for post in self.posts:
+            if post.pk in ids and post.deleted:
+                undeleted.append(post.pk)
+        if undeleted:
+            self.thread.post_set.filter(id__in=undeleted).update(deleted=False)
+            self.thread.sync()
+            self.thread.save(force_update=True)
+            self.forum.sync()
+            self.forum.save(force_update=True)
+            self.request.messages.set_flash(Message(_('Selected posts have been restored.')), 'success', 'threads')
+    
+    def post_action_protect(self, ids):
+        protected = 0
+        for post in self.posts:
+            if post.pk in ids and not post.protected:
+                protected += 1
+        if protected:
+            self.thread.post_set.filter(id__in=ids).update(protected=True)
+            self.request.messages.set_flash(Message(_('Selected posts have been protected from edition.')), 'success', 'threads')
+      
     def post_action_unprotect(self, ids):
         unprotected = 0
         for post in self.posts:
@@ -186,7 +268,41 @@ class ThreadView(BaseView):
         if unprotected:
             self.thread.post_set.filter(id__in=ids).update(protected=False)
             self.request.messages.set_flash(Message(_('Protection from editions has been removed from selected posts.')), 'success', 'threads')
-                
+    
+    def post_action_soft(self, ids):
+        deleted = []
+        for post in self.posts:
+            if post.pk in ids and not post.deleted:
+                if post.pk == self.thread.start_post_id:
+                    raise forms.ValidationError(_("You cannot delete first post of thread using this action. If you want to delete thread, use thread moderation instead."))
+                deleted.append(post.pk)
+        if deleted:
+            self.thread.post_set.filter(id__in=deleted).update(deleted=True)
+            self.thread.sync()
+            self.thread.save(force_update=True)
+            self.forum.sync()
+            self.forum.save(force_update=True)
+            self.request.messages.set_flash(Message(_('Selected posts have been deleted.')), 'success', 'threads')
+    
+    def post_action_hard(self, ids):
+        deleted = []
+        for post in self.posts:
+            if post.pk in ids and not post.deleted:
+                if post.pk == self.thread.start_post_id:
+                    raise forms.ValidationError(_("You cannot delete first post of thread using this action. If you want to delete thread, use thread moderation instead."))
+                deleted.append(post.pk)
+        if deleted:
+            for post in deleted:
+                post.delete()
+            self.thread.post_set.filter(id__in=deleted).delete()
+            Change.objects.d(post__in=ids).delete()
+            Checkpoint.objects.filter(post__in=ids).delete()
+            self.thread.sync()
+            self.thread.save(force_update=True)
+            self.forum.sync()
+            self.forum.save(force_update=True)
+            self.request.messages.set_flash(Message(_('Selected posts have been deleted.')), 'success', 'threads')
+               
     def get_thread_actions(self):
         acl = self.request.acl.threads.get_role(self.thread.forum_id)
         actions = []

+ 10 - 36
templates/sora/threads/move.html

@@ -4,23 +4,14 @@
 {% import "_forms.html" as form_theme with context %}
 {% import "sora/macros.html" as macros with context %}
 
-{% block title %}{% if thread is defined -%}
-{{ macros.page_title(title=_("Move Thread"),parent=thread.name) }}
-{%- else -%}
-{{ macros.page_title(title=_("Move Threads"),parent=forum.name) }}
-{%- endif %}{% endblock %}
+{% block title %}{{ macros.page_title(title=_("Move Posts"),parent=thread.name) }}{% endblock %}
 
 {% block breadcrumb %}{{ super() }} <span class="divider">/</span></li>
 {% for parent in parents %}
 <li class="first"><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
 {% endfor %}
-<li><a href="{% url 'forum' forum=forum.pk, slug=forum.slug %}">{{ forum.name }}</a> <span class="divider">/</span></li>
-{% if thread is defined %}
 <li><a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider">/</span></li>
-<li class="active">{% trans %}Move Thread{% endtrans %}
-{% else %}
-<li class="active">{% trans %}Move Threads{% endtrans %}
-{% endif %}
+<li class="active">{% trans %}Move Posts{% endtrans %}
 {%- endblock %}
 
 {% block content %}
@@ -28,40 +19,23 @@
   <ul class="breadcrumb">
     {{ self.breadcrumb() }}</li>
   </ul>
-  {% if thread is defined %}
-  <h1>{% trans %}Move Thread{% endtrans %} <small>{{ thread.name }}</small></h1>
-  {% else %}
-  <h1>{% trans %}Move Threads{% endtrans %} <small>{{ forum.name }}</small></h1>
-  {% endif %}
+  <h1>{% trans %}Move Posts{% endtrans %} <small>{{ thread.name }}</small></h1>
 </div>
 <div class="row">
   <div class="span8 offset2">
     {% if message %}{{ macros.draw_message(message) }}{% endif %}
-    <form action="{% if thread is defined %}
-    {% url 'thread' thread=thread.pk, slug=thread.slug %}
-    {%- else -%}
-    {% url 'forum' forum=forum.pk, slug=forum.slug %}
-    {%- endif %}" method="post">
+    <form action="{% url 'thread' thread=thread.pk, slug=thread.slug %}" method="post">
       <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-      {% if thread is defined %}
-      <input type="hidden" name="origin" value="thread_form">
-      <input type="hidden" name="thread_action" value="move">
-      <input type="hidden" name="do" value="move">
-      {% else %}
-      <input type="hidden" name="origin" value="move_form">
+      <input type="hidden" name="origin" value="posts_form">
       <input type="hidden" name="list_action" value="move">
-      {% for thread in threads -%}
-      <input type="hidden" name="list_items" value="{{ thread.pk }}">
+      <input type="hidden" name="do" value="move">
+      {% for post in posts -%}
+      <input type="hidden" name="list_items" value="{{ post }}">
       {% endfor %}
-      {% endif %}
       {{ form_theme.form_widget(form, width=8) }}
       <div class="form-actions">
-        <button name="save" type="submit" class="btn btn-primary">{% if thread is defined %}{% trans %}Move Thread{% endtrans %}{% else %}{% trans %}Move Threads{% endtrans %}{% endif %}</button>
-        <a href="{% if thread is defined -%}
-    {% url 'thread' thread=thread.pk, slug=thread.slug %}
-    {%- else -%}
-    {% url 'forum' forum=forum.pk, slug=forum.slug %}
-    {%- endif %}" class="btn">{% trans %}Cancel{% endtrans %}</a>
+        <button name="save" type="submit" class="btn btn-primary">{% trans %}Move Posts{% endtrans %}</button>
+        <a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}" class="btn">{% trans %}Cancel{% endtrans %}</a>
       </div>
     </form>
   </div>

+ 43 - 1
templates/sora/threads/split.html

@@ -1 +1,43 @@
-SPLIT THREAD!
+{% extends "sora/layout.html" %}
+{% load i18n %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% import "sora/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=_("Split Thread"),parent=thread.name) }}{% endblock %}
+
+{% block breadcrumb %}{{ super() }} <span class="divider">/</span></li>
+{% for parent in parents %}
+<li class="first"><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
+{% endfor %}
+<li><a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider">/</span></li>
+<li class="active">{% trans %}Split Thread{% endtrans %}
+{%- endblock %}
+
+{% block content %}
+<div class="page-header">
+  <ul class="breadcrumb">
+    {{ self.breadcrumb() }}</li>
+  </ul>
+  <h1>{% trans %}Split Thread{% endtrans %} <small>{{ thread.name }}</small></h1>
+</div>
+<div class="row">
+  <div class="span8 offset2">
+    {% if message %}{{ macros.draw_message(message) }}{% endif %}
+    <form action="{% url 'thread' thread=thread.pk, slug=thread.slug %}" method="post">
+      <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+      <input type="hidden" name="origin" value="posts_form">
+      <input type="hidden" name="list_action" value="split">
+      <input type="hidden" name="do" value="split">
+      {% for post in posts -%}
+      <input type="hidden" name="list_items" value="{{ post }}">
+      {% endfor %}
+      {{ form_theme.form_widget(form, width=8) }}
+      <div class="form-actions">
+        <button name="save" type="submit" class="btn btn-primary">{% trans %}Split Thread{% endtrans %}</button>
+        <a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}" class="btn">{% trans %}Cancel{% endtrans %}</a>
+      </div>
+    </form>
+  </div>
+</div>
+{% endblock %}