Browse Source

WIP threads list

Rafał Pitoń 10 years ago
parent
commit
8f3ddd7b6e

+ 1 - 0
misago/conf/defaults.py

@@ -173,6 +173,7 @@ MISAGO_MARKUP_EXTENSIONS = ()
 
 MISAGO_POSTING_MIDDLEWARE = (
     'misago.threads.posting.reply.ReplyFormMiddleware',
+    'misago.threads.posting.threadstate.ThreadStateFormMiddleware',
     'misago.threads.posting.recordedit.RecordEditMiddleware',
     'misago.threads.posting.updatestats.UpdateStatsMiddleware',
     'misago.threads.posting.savechanges.SaveChangesMiddleware',

+ 75 - 0
misago/static/misago/css/misago/threadslists.less

@@ -1,3 +1,78 @@
 //
 // Threads lists
 // --------------------------------------------------
+
+
+// Block style
+//
+//==
+.threads-list {
+  .table-panel {
+    border: none;
+
+    .list-group {
+      margin: 0px;
+    }
+  }
+}
+
+
+// Thread icon
+//
+//==
+.threads-list {
+  .table-panel {
+    .list-group {
+      .list-group-item {
+        &.new {
+          .thread-icon {
+            color: @state-active;
+          }
+        }
+
+        .thread-icon {
+          position: relative;
+          right: 5px;
+          bottom: 2px;
+
+          color: @state-default;
+        }
+      }
+    }
+  }
+}
+
+
+// Thread title
+//
+//==
+.threads-list {
+  .table-panel {
+    .list-group {
+      .list-group-item {
+        .item-title {
+          font-size: @font-size-large;
+        }
+      }
+    }
+  }
+}
+
+
+// Empty list message
+//
+//==
+.threads-list {
+  .table-panel {
+    .list-group {
+      .list-group-item {
+        &.message-row {
+          padding-top: @line-height-computed;
+          padding-bottom: @line-height-computed;
+
+          font-size: @font-size-large;
+        }
+      }
+    }
+  }
+}

+ 13 - 3
misago/templates/misago/threads/editor.html

@@ -35,11 +35,11 @@
       {% csrf_token %}
 
       <div class="row">
-      {% if supporting_forms %}
+        {% if supporting_forms %}
         <div class="col-md-9">
-      {% else %}
+        {% else %}
         <div class="col-md-10 col-md-offset-1">
-      {% endif %}
+        {% endif %}
 
           <div class="form-panel">
             {% for form in main_forms %}
@@ -60,6 +60,16 @@
           </div>
 
         </div>
+
+        {% if supporting_forms %}
+        <div class="col-md-3">
+
+          {% for form in supporting_forms %}
+          {% include form.template %}
+          {% endfor %}
+
+        </div>
+        {% endif %}
       </div><!-- /.row -->
 
     </form>

+ 35 - 7
misago/templates/misago/threads/list.html

@@ -65,13 +65,41 @@
       </div>
 
       <div class="table-panel">
-        <table class="table">
-          <tr class="message-row">
-            <td>
-              {% trans "No threads were posted in this forum, or you don't have permission to see them." %}
-            </td>
-          </tr>
-        </table>
+        <ul class="list-group">
+          {% for thread in threads %}
+          <li class="list-group-item{% if thread.is_new %} new{% endif %}">
+
+            {% if thread.is_announcement %}
+              {% if thread.is_new %}
+              <span class="thread-icon tooltip-top fa fa-star fa-lg fa-fw" title="{% trans "Announcement, unread" %}"></span>
+              {% else %}
+              <span class="thread-icon tooltip-top fa fa-star-o fa-lg fa-fw" title="{% trans "Announcement, read" %}"></span>
+              {% endif %}
+            {% elif thread.is_pinned %}
+              {% if thread.is_new %}
+              <span class="thread-icon tooltip-top fa fa-bookmark fa-lg fa-fw" title="{% trans "Pinned, unread" %}"></span>
+              {% else %}
+              <span class="thread-icon tooltip-top fa fa-bookmark-o fa-lg fa-fw" title="{% trans "Pinned, read" %}"></span>
+              {% endif %}
+            {% else %}
+              {% if thread.is_new %}
+              <span class="thread-icon tooltip-top fa fa-circle fa-lg fa-fw" title="{% trans "Unread posts" %}"></span>
+              {% else %}
+              <span class="thread-icon tooltip-top fa fa-circle-thin fa-lg fa-fw" title="{% trans "Read posts" %}"></span>
+              {% endif %}
+            {% endif %}
+
+            <a href="#" class="item-title">
+              {{ thread.title }}
+            </a>
+
+          </li>
+          {% empty %}
+          <li class="list-group-item message-row">
+            {% trans "No threads were posted in this forum, or you don't have permission to see them." %}
+          </li>
+          {% endfor %}
+        </ul>
       </div>
 
       <div class="table-actions">

+ 14 - 0
misago/templates/misago/threads/threadstate.html

@@ -0,0 +1,14 @@
+{% load i18n misago_forms %}
+<div class="form-panel">
+  <div class="form-header">
+    <h3>{{ form.legend }}</h3>
+  </div>
+  <div class="form-body no-fieldsets">
+    {% if form.weight %}
+      {% form_row form.weight %}
+    {% endif %}
+    {% if form.is_closed %}
+      {% form_row form.is_closed %}
+    {% endif %}
+  </div>
+</div>

+ 23 - 0
misago/threads/forms/reply.py

@@ -115,3 +115,26 @@ class ThreadForm(ReplyForm):
 
 class PrefixedThreadForm(ThreadForm):
     pass
+
+
+class FullThreadStateForm(forms.Form):
+    is_supporting = True
+    legend = _("Thread options")
+    template = "misago/threads/threadstate.html"
+
+    weight = forms.TypedChoiceField(label=_("Thread weight"), initial=0,
+                                    widget=forms.RadioSelect,
+                                    choices=(
+                                        (0, _("Standard")),
+                                        (1, _("Pinned")),
+                                        (2, _("Announcement")),
+                                    ))
+    is_closed = forms.YesNoSwitch(label=_("Close thread"), initial=0)
+
+
+class ThreadWeightForm(FullThreadStateForm):
+    is_closed = None
+
+
+class CloseThreadForm(FullThreadStateForm):
+    Weight = None

+ 2 - 2
misago/threads/migrations/0001_initial.py

@@ -67,10 +67,10 @@ class Migration(migrations.Migration):
                 ('has_reported_posts', models.BooleanField(default=False)),
                 ('has_moderated_posts', models.BooleanField(default=False)),
                 ('has_hidden_posts', models.BooleanField(default=False)),
-                ('started_on', models.DateTimeField()),
+                ('started_on', models.DateTimeField(db_index=True)),
                 ('starter_name', models.CharField(max_length=255)),
                 ('starter_slug', models.SlugField(max_length=255)),
-                ('last_post_on', models.DateTimeField()),
+                ('last_post_on', models.DateTimeField(db_index=True)),
                 ('last_poster_name', models.CharField(max_length=255, null=True, blank=True)),
                 ('last_poster_slug', models.SlugField(max_length=255, null=True, blank=True)),
                 ('is_poll', models.BooleanField(default=False)),

+ 3 - 3
misago/threads/models/__init__.py

@@ -1,4 +1,4 @@
 # flake8: noqa
-from misago.threads.models.post import Post
-from misago.threads.models.prefix import Prefix
-from misago.threads.models.thread import Thread
+from misago.threads.models.post import *
+from misago.threads.models.prefix import *
+from misago.threads.models.thread import *

+ 22 - 0
misago/threads/models/thread.py

@@ -4,6 +4,13 @@ from misago.conf import settings
 from misago.core.utils import slugify
 
 
+__all__ = ['ANNOUNCEMENT', 'PINNED', 'Thread']
+
+
+ANNOUNCEMENT = 2
+PINNED = 1
+
+
 class Thread(models.Model):
     forum = models.ForeignKey('misago_forums.Forum')
     weight = models.PositiveIntegerField(default=0)
@@ -39,6 +46,21 @@ class Thread(models.Model):
     is_hidden = models.BooleanField(default=False)
     is_closed = models.BooleanField(default=False)
 
+    def is_announcement(self):
+        return self.weight == ANNOUNCEMENT
+
+    def is_pinned(self):
+        return self.weight == PINNED
+
+    def get_absolute_url(self):
+        pass
+
+    def get_new_reply_url(self):
+        pass
+
+    def get_last_reply_url(self):
+        pass
+
     def set_title(self, title):
         self.title = title
         self.slug = slugify(title)

+ 26 - 4
misago/threads/permissions.py

@@ -38,6 +38,18 @@ class PermissionsForm(forms.Form):
         coerce=int,
         initial=0,
         choices=((0, _("No")), (1, _("Own replies")), (2, _("All replies"))))
+    can_change_threads_weight = forms.TypedChoiceField(
+        label=_("Can change threads weight"), coerce=int, initial=0,
+        choices=(
+            (0, _("No")),
+            (1, _("Pin threads")),
+            (2, _("Make announcements")),
+        ))
+    can_close_threads = forms.TypedChoiceField(
+        label=_("Can close threads"),
+        coerce=int,
+        initial=0,
+        choices=((0, _("No")), (1, _("Own threads")), (2, _("All threads"))))
 
 
 def change_permissions_form(role):
@@ -67,12 +79,16 @@ def build_forum_acl(acl, forum, forums_roles, key_name):
     final_acl = {
         'can_see_all_threads': 0,
         'can_start_threads': 0,
+        'can_change_threads_weight': 0,
+        'can_close_threads': 0,
     }
     final_acl.update(acl)
 
     algebra.sum_acls(final_acl, roles=forum_roles, key=key_name,
         can_see_all_threads=algebra.greater,
-        can_start_threads=algebra.greater
+        can_start_threads=algebra.greater,
+        can_change_threads_weight=algebra.greater,
+        can_close_threads=algebra.greater,
     )
 
     return final_acl
@@ -94,11 +110,17 @@ def add_acl_to_forum(user, forum):
     forum_acl = user.acl['forums'].get(forum.pk, {})
 
     forum.acl['can_see_all_threads'] = forum_acl.get('can_see_all_threads', 0)
+    forum.acl.update({
+        'can_start_threads': 0,
+        'can_change_threads_weight': 0,
+    })
 
     if user.is_authenticated():
-        forum.acl['can_start_threads'] = forum_acl.get('can_start_threads', 0)
-    else:
-        forum.acl['can_start_threads'] = 0
+        algebra.sum_acls(forum.acl, acls=[forum_acl],
+            can_see_all_threads=algebra.greater,
+            can_start_threads=algebra.greater,
+            can_change_threads_weight=algebra.greater,
+        )
 
 
 def add_acl_to_thread(user, thread):

+ 0 - 1
misago/threads/posting/reply.py

@@ -61,4 +61,3 @@ class ReplyFormMiddleware(PostingMiddleware):
         self.post.poster_name = self.user.username
         self.post.poster_ip = self.request._misago_real_ip
         self.post.posted_on = self.datetime
-

+ 54 - 0
misago/threads/posting/threadstate.py

@@ -0,0 +1,54 @@
+from misago.threads.forms.reply import (FullThreadStateForm, ThreadWeightForm,
+                                        CloseThreadForm)
+from misago.threads.posting import PostingMiddleware, START, REPLY, EDIT
+
+
+class ThreadStateFormMiddleware(PostingMiddleware):
+    def __init__(self, **kwargs):
+        super(ThreadStateFormMiddleware, self).__init__(**kwargs)
+
+        self.thread_weight = self.thread.weight
+        self.thread_is_closed = self.thread.is_closed
+
+        forum_acl = self.user.acl['forums'].get(self.forum.pk, {
+            'can_change_threads_weight': 0,
+            'can_close_threads': 0,
+        })
+
+        self.can_change_threads_weight = forum_acl['can_change_threads_weight']
+        self.can_close_threads = forum_acl['can_close_threads']
+
+    def make_form(self):
+        StateFormType = None
+        initial = {
+            'weight': self.thread_weight,
+            'is_closed': self.thread_is_closed,
+        }
+
+        if self.can_change_threads_weight and self.can_close_threads:
+            StateFormType = FullThreadStateForm
+        elif self.can_change_threads_weight:
+            StateFormType = ThreadWeightForm
+        elif self.can_close_threads:
+            StateFormType = CloseThreadForm
+
+        if StateFormType:
+            if self.request.method == 'POST':
+                return StateFormType(self.request.POST, prefix=self.prefix)
+            else:
+                return StateFormType(prefix=self.prefix, initial=initial)
+        else:
+            return False
+
+    def pre_save(self, form):
+        if self.can_change_threads_weight:
+            if self.thread_weight != form.cleaned_data.get('weight'):
+                self.thread.weight = form.cleaned_data.get('weight')
+                self.thread.update_fields.append('weight')
+        if self.can_close_threads:
+            if self.thread_is_closed != form.cleaned_data.get('is_closed'):
+                self.thread.is_closed = form.cleaned_data.get('is_closed')
+                self.thread.update_fields.append('is_closed')
+
+    def save(self, form):
+        pass

+ 5 - 0
misago/threads/urls.py

@@ -9,3 +9,8 @@ urlpatterns = patterns('',
     url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/(?P<page>\d+)/$', ForumView.as_view(), name='forum'),
     url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/start-thread/$', StartThreadView.as_view(), name='start_thread'),
 )
+
+urlpatterns += patterns('',
+    url(r'^thread/(?P<thread_slug>[\w\d-]+)-(?P<thread_id>\d+)/$', ThreadView.as_view(), name='thread'),
+    url(r'^thread/(?P<thread_slug>[\w\d-]+)-(?P<thread_id>\d+)/(?P<page>\d+)/$', ThreadView.as_view(), name='thread'),
+)

+ 33 - 5
misago/threads/views/generic.py

@@ -14,7 +14,7 @@ from misago.forums.permissions import allow_see_forum, allow_browse_forum
 
 from misago.threads.posting import (InterruptChanges, EditorFormset,
                                     START, REPLY, EDIT)
-from misago.threads.models import Thread, Post
+from misago.threads.models import ANNOUNCEMENT, Thread, Post
 from misago.threads.permissions import allow_see_thread, allow_start_thread
 
 
@@ -95,17 +95,45 @@ class ForumView(ViewBase):
     """
     template = 'list.html'
 
-    def get_threads(self, request, forum, **kwargs):
-        return forum.thread_set
+    def get_threads(self, request, forum, kwargs):
+        queryset = self.get_threads_queryset(request, forum)
+
+        threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
+        threads_qs = threads_qs.order_by('-weight', '-last_post_on')
+
+        page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
+        threads = []
+
+        for announcement in queryset.filter(weight=ANNOUNCEMENT):
+            threads.append(announcement)
+        for thread in page.object_list:
+            threads.append(thread)
+
+        return page, threads
+
+    def get_threads_queryset(self, request, forum):
+        return forum.thread_set.all().order_by('-last_post_on')
+
+    def add_threads_reads(self, request, forum, threads):
+        for thread in threads:
+            thread.is_new = False
+
+        import random
+        for thread in threads:
+            thread.is_new = random.choice((True, False))
 
     def dispatch(self, request, *args, **kwargs):
         forum = self.get_forum(request, **kwargs)
         forum.subforums = get_forums_list(request.user, forum)
-        threads = self.get_threads(request, forum, **kwargs)
+
+        page, threads = self.get_threads(request, forum, kwargs)
+        self.add_threads_reads(request, forum, threads)
 
         return self.render(request, {
             'forum': forum,
             'path': get_forum_path(forum),
+            'page': page,
+            'threads': threads
         })
 
 
@@ -201,7 +229,7 @@ class EditorView(ViewBase):
             if 'submit' in request.POST and formset.is_valid():
                 try:
                     formset.save()
-                    return redirect('misago:index')
+                    return redirect(forum.get_absolute_url())
                 except InterruptChanges as e:
                     messages.error(request, e.message)
             else: