Browse Source

Sortable threads list

Rafał Pitoń 10 years ago
parent
commit
94b4538ebc

+ 11 - 0
misago/core/templatetags/misago_pagination.py

@@ -1,4 +1,5 @@
 from django import template
 from django import template
+from django.core.urlresolvers import reverse
 from django.template import Context
 from django.template import Context
 from django.template.loader import get_template
 from django.template.loader import get_template
 
 
@@ -18,3 +19,13 @@ def pagination(page, template, link_name, **kwargs):
     })
     })
 
 
     return template.render(Context(context))
     return template.render(Context(context))
+
+
+@register.simple_tag
+def pageurl(link, kwargs, page=None):
+    if page > 1:
+        kwargs['page'] = page
+    else:
+        kwargs.pop('page', None)
+
+    return reverse(link, kwargs=kwargs)

+ 1 - 1
misago/templates/misago/admin/generic/list.html

@@ -18,7 +18,7 @@
   {% if order_by %}
   {% if order_by %}
   <div class="btn-group pull-left">
   <div class="btn-group pull-left">
     <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
     <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
-      {% trans "Sorting:" %} <span class="fa fa-sort-numeric-{{ order.type }}"></span> <strong>{{ order.name }}</strong>
+      {% trans "Sort:" %} <span class="fa fa-sort-numeric-{{ order.type }}"></span> <strong>{{ order.name }}</strong>
     </button>
     </button>
     <ul class="dropdown-menu" role="menu">
     <ul class="dropdown-menu" role="menu">
       <li class="dropdown-title">
       <li class="dropdown-title">

+ 1 - 1
misago/templates/misago/threads/list.html

@@ -2,7 +2,7 @@
 {% load humanize i18n misago_avatars misago_capture misago_stringutils %}
 {% load humanize i18n misago_avatars misago_capture misago_stringutils %}
 
 
 
 
-{% block title %}{{ forum.name }} | {{ block.super }}{% endblock title %}
+{% block title %}{{ forum.name }}{% if page.number > 1 %} ({% blocktrans with page=page.number %}Page {{ page }}{% endblocktrans %}){% endif %} | {{ block.super }}{% endblock title %}
 
 
 
 
 {% block meta-description %}{{ forum.description|striplinebreaks }}{% endblock meta-description %}
 {% block meta-description %}{{ forum.description|striplinebreaks }}{% endblock meta-description %}

+ 26 - 4
misago/templates/misago/threads/list_actions.html

@@ -1,4 +1,4 @@
-{% load i18n misago_capture %}
+{% load i18n misago_capture misago_pagination %}
 <div class="table-actions">
 <div class="table-actions">
 
 
   <ul class="pager pull-left">
   <ul class="pager pull-left">
@@ -15,7 +15,7 @@
     </li>
     </li>
     {% if page.has_previous %}
     {% if page.has_previous %}
       <li>
       <li>
-        <a href="{{ forum.get_absolute_url }}" class="tooltip-top" title="{% trans "Go to first page" %}">
+        <a href="{% pageurl 'misago:forum' links_params %}" class="tooltip-top" title="{% trans "Go to first page" %}">
           {% if page.number > 2 %}
           {% if page.number > 2 %}
           {% trans "First" %}
           {% trans "First" %}
           {% else %}
           {% else %}
@@ -25,7 +25,7 @@
       </li>
       </li>
       {% if page.number > 2 %}
       {% if page.number > 2 %}
       <li>
       <li>
-        <a href="{% url 'misago:forum' forum_slug=forum.slug forum_id=forum.id page=page.previous_page_number %}" class="tooltip-top" title="{% trans "Go to previous page" %}">
+        <a href="{% pageurl 'misago:forum' links_params page.previous_page_number %}" class="tooltip-top" title="{% trans "Go to previous page" %}">
           <span class="glyphicon glyphicon-chevron-left"></span>
           <span class="glyphicon glyphicon-chevron-left"></span>
         </a>
         </a>
       </li>
       </li>
@@ -33,13 +33,35 @@
     {% endif %}
     {% endif %}
     {% if page.has_next %}
     {% if page.has_next %}
       <li>
       <li>
-        <a href="{% url 'misago:forum' forum_slug=forum.slug forum_id=forum.id page=page.next_page_number %}" class="tooltip-top" title="{% trans "Go to next page" %}">
+        <a href="{% pageurl 'misago:forum' links_params page.next_page_number %}" class="tooltip-top" title="{% trans "Go to next page" %}">
           <span class="glyphicon glyphicon-chevron-right"></span>
           <span class="glyphicon glyphicon-chevron-right"></span>
         </a>
         </a>
       </li>
       </li>
     {% endif %}
     {% endif %}
   </ul>
   </ul>
 
 
+  <div class="btn-group pull-left">
+    <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+      {% trans "Sort:" %} <strong>{{ order_name }}</strong>
+    </button>
+    <ul class="dropdown-menu" role="menu">
+      <li class="dropdown-title">
+        {% trans "Change sorting to:" %}
+      </li>
+      {% for order in order_by %}
+      <li>
+        {% if forloop.first %}
+        <a href="{% url 'misago:forum' forum_slug=forum.slug forum_id=forum.id %}">
+        {% else %}
+        <a href="{% url 'misago:forum' forum_slug=forum.slug forum_id=forum.id sort=order.url %}">
+        {% endif %}
+          {{ order.name }}
+        </a>
+      </li>
+      {% endfor %}
+    </ul>
+  </div>
+
   {% if forum.is_closed %}
   {% if forum.is_closed %}
   <span class="btn btn-default disabled pull-right">
   <span class="btn btn-default disabled pull-right">
     <span class="fa fa-lock"></span>
     <span class="fa fa-lock"></span>

+ 9 - 1
misago/threads/migrations/0001_initial.py

@@ -82,7 +82,7 @@ class Migration(migrations.Migration):
                 ('started_on', models.DateTimeField()),
                 ('started_on', models.DateTimeField()),
                 ('starter_name', models.CharField(max_length=255)),
                 ('starter_name', models.CharField(max_length=255)),
                 ('starter_slug', models.CharField(max_length=255)),
                 ('starter_slug', models.CharField(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_name', models.CharField(max_length=255, null=True, blank=True)),
                 ('last_poster_slug', models.CharField(max_length=255, null=True, blank=True)),
                 ('last_poster_slug', models.CharField(max_length=255, null=True, blank=True)),
                 ('is_poll', models.BooleanField(default=False)),
                 ('is_poll', models.BooleanField(default=False)),
@@ -151,4 +151,12 @@ class Migration(migrations.Migration):
             field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True),
             field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True),
             preserve_default=True,
             preserve_default=True,
         ),
         ),
+        migrations.AlterIndexTogether(
+            name='thread',
+            index_together=set([
+                ('forum', 'weight', 'last_post'),
+                ('forum', 'weight', 'id'),
+                ('forum', 'weight', 'replies')
+            ]),
+        ),
 ]
 ]

+ 8 - 1
misago/threads/models/thread.py

@@ -33,7 +33,7 @@ class Thread(models.Model):
                                 on_delete=models.SET_NULL)
                                 on_delete=models.SET_NULL)
     starter_name = models.CharField(max_length=255)
     starter_name = models.CharField(max_length=255)
     starter_slug = models.CharField(max_length=255)
     starter_slug = models.CharField(max_length=255)
-    last_post_on = models.DateTimeField()
+    last_post_on = models.DateTimeField(db_index=True)
     last_post = models.ForeignKey('misago_threads.Post', related_name='+',
     last_post = models.ForeignKey('misago_threads.Post', related_name='+',
                                   null=True, blank=True,
                                   null=True, blank=True,
                                   on_delete=models.SET_NULL)
                                   on_delete=models.SET_NULL)
@@ -47,6 +47,13 @@ class Thread(models.Model):
     is_hidden = models.BooleanField(default=False)
     is_hidden = models.BooleanField(default=False)
     is_closed = models.BooleanField(default=False)
     is_closed = models.BooleanField(default=False)
 
 
+    class Meta:
+        index_together = [
+            ['forum', 'weight', 'id'],
+            ['forum', 'weight', 'last_post'],
+            ['forum', 'weight', 'replies'],
+        ]
+
     @property
     @property
     def is_announcement(self):
     def is_announcement(self):
         return self.weight == ANNOUNCEMENT
         return self.weight == ANNOUNCEMENT

+ 6 - 0
misago/threads/urls.py

@@ -7,6 +7,12 @@ from misago.threads.views.threads import (ForumView, ThreadView, StartThreadView
 urlpatterns = patterns('',
 urlpatterns = patterns('',
     url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/$', ForumView.as_view(), name='forum'),
     url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/$', ForumView.as_view(), name='forum'),
     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+)/(?P<page>\d+)/$', ForumView.as_view(), name='forum'),
+    url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/sort-(?P<sort>[\w-]+)/$', ForumView.as_view(), name='forum'),
+    url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/sort-(?P<sort>[\w-]+)/(?P<page>\d+)/$', ForumView.as_view(), name='forum'),
+    url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/show-(?P<show>[\w-]+)/$', ForumView.as_view(), name='forum'),
+    url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/show-(?P<show>[\w-]+)/(?P<page>\d+)/$', ForumView.as_view(), name='forum'),
+    url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/sort-(?P<sort>[\w-]+)/show-(?P<show>[\w-]+)/$', ForumView.as_view(), name='forum'),
+    url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/sort-(?P<sort>[\w-]+)/show-(?P<show>[\w-]+)/(?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'),
     url(r'^forum/(?P<forum_slug>[\w\d-]+)-(?P<forum_id>\d+)/start-thread/$', StartThreadView.as_view(), name='start_thread'),
 )
 )
 
 

+ 94 - 17
misago/threads/views/generic.py

@@ -5,6 +5,7 @@ from django.contrib import messages
 from django.db.models import Q
 from django.db.models import Q
 from django.db.transaction import atomic
 from django.db.transaction import atomic
 from django.shortcuts import redirect, render
 from django.shortcuts import redirect, render
+from django.utils.translation import ugettext_lazy, ugettext as _
 from django.views.generic import View
 from django.views.generic import View
 
 
 from misago.acl import add_acl
 from misago.acl import add_acl
@@ -96,6 +97,40 @@ class ViewBase(ForumMixin, ThreadMixin, PostMixin, View):
         return render(request, template, context)
         return render(request, template, context)
 
 
 
 
+class OrderThreadsMixin(object):
+    order_by = (
+        ('recently-replied', ugettext_lazy("Recently replied")),
+        ('last-replied', ugettext_lazy("Last replied")),
+        ('most-replied', ugettext_lazy("Most replied")),
+        ('least-replied', ugettext_lazy("Least replied")),
+        ('newest', ugettext_lazy("Newest")),
+        ('oldest', ugettext_lazy("Oldest")),
+    )
+
+    def get_ordering(self, kwargs):
+        if kwargs.get('sort') in [o[0] for o in self.order_by]:
+            return kwargs.get('sort')
+        else:
+            return self.order_by[0][0]
+
+    def is_ordering_default(self, order_by):
+        return self.order_by[0][0] == order_by
+
+    def get_ordering_name(self, order_by):
+        for ordering in self.order_by:
+            if ordering[0] == order_by:
+                return ordering[1]
+
+    def get_orderings_dicts(self):
+        dicts = []
+        for ordering in self.order_by:
+            dicts.append({
+                'url': ordering[0],
+                'name': ordering[1],
+            })
+        return dicts
+
+
 class ThreadsView(ViewBase):
 class ThreadsView(ViewBase):
     def get_threads(self, request, kwargs):
     def get_threads(self, request, kwargs):
         queryset = self.get_threads_queryset(request, forum)
         queryset = self.get_threads_queryset(request, forum)
@@ -128,13 +163,13 @@ class ThreadsView(ViewBase):
             thread.is_new = random.choice((True, False))
             thread.is_new = random.choice((True, False))
 
 
 
 
-class ForumView(ThreadsView):
+class ForumView(OrderThreadsMixin, ThreadsView):
     """
     """
     Basic view for threads lists
     Basic view for threads lists
     """
     """
     template = 'list.html'
     template = 'list.html'
 
 
-    def get_threads(self, request, forum, kwargs):
+    def get_threads(self, request, forum, kwargs, order_by=None, limit=None):
         queryset = self.get_threads_queryset(request, forum)
         queryset = self.get_threads_queryset(request, forum)
         queryset = self.filter_all_querysets(request, forum, queryset)
         queryset = self.filter_all_querysets(request, forum, queryset)
 
 
@@ -145,7 +180,9 @@ class ForumView(ThreadsView):
             request, forum, announcements_qs)
             request, forum, announcements_qs)
         threads_qs = self.filter_threads_queryset(request, forum, threads_qs)
         threads_qs = self.filter_threads_queryset(request, forum, threads_qs)
 
 
-        threads_qs = threads_qs.order_by('-weight', '-last_post_id')
+        threads_qs, announcements_qs = self.order_querysets(
+            order_by, threads_qs, announcements_qs)
+
         page = paginate(threads_qs, kwargs.get('page', 0), 20, 10)
         page = paginate(threads_qs, kwargs.get('page', 0), 20, 10)
         threads = []
         threads = []
 
 
@@ -159,20 +196,48 @@ class ForumView(ThreadsView):
 
 
         return page, threads
         return page, threads
 
 
+    def order_querysets(self, order_by, threads, announcements):
+        if order_by == 'recently-replied':
+            threads = threads.order_by('-weight', '-last_post')
+            announcements = announcements.order_by('-last_post')
+        if order_by == 'last-replied':
+            threads = threads.order_by('weight', 'last_post')
+            announcements = announcements.order_by('last_post')
+        if order_by == 'most-replied':
+            threads = threads.order_by('-weight', '-replies')
+            announcements = announcements.order_by('-replies')
+        if order_by == 'least-replied':
+            threads = threads.order_by('weight', 'replies')
+            announcements = announcements.order_by('replies')
+        if order_by == 'newest':
+            threads = threads.order_by('-weight', '-id')
+            announcements = announcements.order_by('-id')
+        if order_by == 'oldest':
+            threads = threads.order_by('weight', 'id')
+            announcements = announcements.order_by('id')
+
+        return threads, announcements
+
     def filter_all_querysets(self, request, forum, queryset):
     def filter_all_querysets(self, request, forum, queryset):
-        if not forum.acl['can_review_moderated_content']:
-            if request.user.is_authenticated():
+        if request.user.is_authenticated():
+            condition_author = Q(starter_id=request.user.id)
+
+            can_mod = forum.acl['can_review_moderated_content']
+            can_hide = forum.acl['can_hide_threads']
+
+            if not can_mod and not can_hide:
+                condition = Q(is_moderated=False) & Q(is_hidden=False)
+                queryset = queryset.filter(condition_author | condition)
+            elif not can_mod:
                 condition = Q(is_moderated=False)
                 condition = Q(is_moderated=False)
-                condition = condition | Q(starter_id=request.user.id)
-                queryset = queryset.filter(condition)
-            else:
-                queryset = queryset.filter(is_moderated=False)
-        if not forum.acl['can_hide_threads']:
-            if request.user.is_authenticated():
+                queryset = queryset.filter(condition_author | condition)
+            elif not can_hide:
                 condition = Q(is_hidden=False)
                 condition = Q(is_hidden=False)
-                condition = condition | Q(starter_id=request.user.id)
-                queryset = queryset.filter(condition)
-            else:
+                queryset = queryset.filter(condition_author | condition)
+        else:
+            if not forum.acl['can_review_moderated_content']:
+                queryset = queryset.filter(is_moderated=False)
+            if not forum.acl['can_hide_threads']:
                 queryset = queryset.filter(is_hidden=False)
                 queryset = queryset.filter(is_hidden=False)
 
 
         return queryset
         return queryset
@@ -194,9 +259,18 @@ class ForumView(ThreadsView):
 
 
     def dispatch(self, request, *args, **kwargs):
     def dispatch(self, request, *args, **kwargs):
         forum = self.get_forum(request, **kwargs)
         forum = self.get_forum(request, **kwargs)
+        links_params = {'forum_slug': forum.slug, 'forum_id': forum.id}
+
         forum.subforums = get_forums_list(request.user, forum)
         forum.subforums = get_forums_list(request.user, forum)
 
 
-        page, threads = self.get_threads(request, forum, kwargs)
+        order_by = self.get_ordering(kwargs)
+        if self.is_ordering_default(kwargs.get('sort')):
+            kwargs.pop('sort')
+            return redirect('misago:forum', **kwargs)
+        else:
+            links_params['sort'] = order_by
+
+        page, threads = self.get_threads(request, forum, kwargs, order_by)
         self.add_threads_reads(request, threads)
         self.add_threads_reads(request, threads)
 
 
         return self.render(request, {
         return self.render(request, {
@@ -204,7 +278,10 @@ class ForumView(ThreadsView):
             'path': get_forum_path(forum),
             'path': get_forum_path(forum),
             'page': page,
             'page': page,
             'paginator': page.paginator,
             'paginator': page.paginator,
-            'threads': threads
+            'threads': threads,
+            'links_params': links_params,
+            'order_name': self.get_ordering_name(order_by),
+            'order_by': self.get_orderings_dicts(),
         })
         })
 
 
 
 
@@ -332,5 +409,5 @@ class EditorView(ViewBase):
             'path': get_forum_path(forum),
             'path': get_forum_path(forum),
             'thread': thread,
             'thread': thread,
             'post': post,
             'post': post,
-            'quote': quote
+            'quote': quote,
         })
         })