Browse Source

Popular Threads ranking

Ralfp 12 years ago
parent
commit
4382d402b3

+ 7 - 0
misago/threads/acl.py

@@ -152,6 +152,13 @@ class ThreadsACL(BaseACL):
         except KeyError:
         except KeyError:
             raise ACLError403(_("You don't have permission to read threads in this forum."))
             raise ACLError403(_("You don't have permission to read threads in this forum."))
     
     
+    def get_readable_forums(self, acl):
+        readable = []
+        for forum in self.acl:
+            if acl.forums.can_browse(forum) and self.acl[forum]['can_read_threads']:
+                readable.append(forum)
+        return readable
+    
     def filter_threads(self, request, forum, queryset):
     def filter_threads(self, request, forum, queryset):
         try:
         try:
             forum_role = self.acl[forum.pk]
             forum_role = self.acl[forum.pk]

+ 41 - 0
misago/threads/fixtures.py

@@ -40,6 +40,47 @@ settings_fixtures = (
                 'name':         _("Display avatars on threads list"),
                 'name':         _("Display avatars on threads list"),
                 'description':  _("Unlike basic user data, avatars are not cached - turning this option on will cause one extra query on threads lists."),
                 'description':  _("Unlike basic user data, avatars are not cached - turning this option on will cause one extra query on threads lists."),
             }),
             }),
+            ('thread_ranking_size', {
+                'value':        10,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0,'max': 30},
+                'separator':    _("Thread Popularity Ranking"),
+                'name':         _('Threads on "Popular Threads" list'),
+                'description':  _('Enter number of threads to be displayed on "Popular Threads" list on board index or 0 to don\'t display any threads there.'),
+            }),
+            ('thread_ranking_refresh', {
+                'value':        60,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _('Ranking Update Frequency'),
+                'description':  _('Enter minimum of number of minutes between ranking updates or zero to update ranking on every request - strongly discouraged for active forums.'),
+            }),
+            ('thread_ranking_initial_score', {
+                'value':        30,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _('Initial Thread Score'),
+                'description':  _("Initial Thread Score helps new threads overtake old threads in ranking."),
+            }),
+            ('thread_ranking_reply_score', {
+                'value':        5,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _('New Reply Score'),
+                'description':  _("Only replies visible to all members increase thread inflation."),
+            }),
+            ('thread_ranking_inflation', {
+                'value':        20,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0, 'max': 99},
+                'name':         _('Score inflation'),
+                'description':  _("Thread popularity system requires inflation to be defined in order to be effective. updatethreadranking task will lower thread scores by percent defined here on every launch. For example, yf you enter 5, thread scores will be lowered by 5% on every update. Enter zero to disable inflation."),
+            }),
             ('post_length_min', {
             ('post_length_min', {
                 'value':        5,
                 'value':        5,
                 'type':         "integer",
                 'type':         "integer",

+ 0 - 0
misago/threads/management/__init__.py


+ 0 - 0
misago/threads/management/commands/__init__.py


+ 18 - 0
misago/threads/management/commands/updatethreadranking.py

@@ -0,0 +1,18 @@
+from django.core.management.base import BaseCommand
+from django.db.models import F
+from misago.settings.settings import Settings
+from misago.threads.models import Thread
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few days to update thread popularity ranking
+    """
+    help = 'Updates Popular Threads ranking'
+    def handle(self, *args, **options):
+        settings = Settings()
+        if settings['thread_ranking_inflation'] > 0:
+            inflation = float(100 - settings['thread_ranking_inflation']) / 100
+            Thread.objects.all().update(score=F('score') * inflation)
+            self.stdout.write('Thread ranking has been updated.\n')
+        else:
+            self.stdout.write('Thread ranking inflation is disabled.\n')

+ 11 - 9
misago/threads/views/posting.py

@@ -93,6 +93,7 @@ class PostingView(BaseView):
                                                    start=now,
                                                    start=now,
                                                    last=now,
                                                    last=now,
                                                    moderated=moderation,
                                                    moderated=moderation,
+                                                   score=request.settings['thread_ranking_initial_score'],
                                                    )
                                                    )
                     if moderation:
                     if moderation:
                         thread.replies_moderated += 1
                         thread.replies_moderated += 1
@@ -122,25 +123,26 @@ class PostingView(BaseView):
                     if request.user.rank and request.user.rank.style:
                     if request.user.rank and request.user.rank.style:
                         thread.start_poster_style = request.user.rank.style
                         thread.start_poster_style = request.user.rank.style
                 
                 
-                if not moderation:
-                    thread.last = now
-                    thread.last_post = post
-                    thread.last_poster = request.user
-                    thread.last_poster_name = request.user.username
-                    thread.last_poster_slug = request.user.username_slug
-                    if request.user.rank and request.user.rank.style:
-                        thread.last_poster_style = request.user.rank.style
                 if self.mode in ['new_post', 'new_post_quick']:
                 if self.mode in ['new_post', 'new_post_quick']:
                     if moderation:
                     if moderation:
                         thread.replies_moderated += 1
                         thread.replies_moderated += 1
                     else:
                     else:
                         thread.replies += 1
                         thread.replies += 1
-                        thread.score += 5
+                        if thread.last_poster_id != request.user.pk:
+                            thread.score += request.settings['thread_ranking_reply_score']
                         if (self.request.settings.thread_length > 0
                         if (self.request.settings.thread_length > 0
                             and not thread.closed
                             and not thread.closed
                             and thread.replies >= self.request.settings.thread_length):
                             and thread.replies >= self.request.settings.thread_length):
                             thread.closed = True
                             thread.closed = True
                             post.set_checkpoint(self.request, 'limit')
                             post.set_checkpoint(self.request, 'limit')
+                if not moderation:
+                    thread.last = now
+                    thread.last_post = post
+                    thread.last_poster = request.user
+                    thread.last_poster_name = request.user.username
+                    thread.last_poster_slug = request.user.username_slug
+                    if request.user.rank and request.user.rank.style:
+                        thread.last_poster_style = request.user.rank.style
                 thread.save(force_update=True)
                 thread.save(force_update=True)
                 
                 
                 # Update forum and monitor
                 # Update forum and monitor

+ 1 - 1
misago/users/views.py

@@ -339,7 +339,7 @@ class Delete(ButtonWidget):
         target.delete()
         target.delete()
         User.objects.resync_monitor(self.request.monitor)
         User.objects.resync_monitor(self.request.monitor)
         return Message(_('User "%(name)s" has been deleted.') % {'name': target.username}, 'success'), False
         return Message(_('User "%(name)s" has been deleted.') % {'name': target.username}, 'success'), False
-    
+
 
 
 def inactive(request):
 def inactive(request):
     token = 'list_filter_misago.users.models.User'
     token = 'list_filter_misago.users.models.User'

+ 12 - 0
misago/views.py

@@ -1,3 +1,4 @@
+from django.core.cache import cache
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
@@ -5,19 +6,30 @@ from django.utils.translation import ugettext as _
 from misago.forums.models import Forum
 from misago.forums.models import Forum
 from misago.readstracker.trackers import ForumsTracker
 from misago.readstracker.trackers import ForumsTracker
 from misago.sessions.models import Session
 from misago.sessions.models import Session
+from misago.threads.models import Thread
 
 
 def home(request):
 def home(request):
+    # Threads ranking
+    popular_threads = cache.get('thread_ranking_%s' % request.user.make_acl_key(), 'nada')
+    if popular_threads == 'nada' and request.settings['thread_ranking_size'] > 0:
+        popular_threads = []
+        for thread in Thread.objects.filter(moderated=False).filter(deleted=False).filter(forum__in=request.acl.threads.get_readable_forums(request.acl)).prefetch_related('forum').order_by('-score')[:request.settings['thread_ranking_size']]:
+            popular_threads.append(thread)
+        cache.set('thread_ranking_%s' % request.user.make_acl_key(), popular_threads, request.settings['thread_ranking_refresh'])  
+    # Team online
     team_online = []
     team_online = []
     team_pks = []
     team_pks = []
     for session in Session.objects.filter(team=1).filter(admin=0).filter(user__isnull=False).order_by('-start').select_related('user', 'user__rank'):
     for session in Session.objects.filter(team=1).filter(admin=0).filter(user__isnull=False).order_by('-start').select_related('user', 'user__rank'):
         if session.user.pk not in team_pks:
         if session.user.pk not in team_pks:
             team_pks.append(session.user.pk)
             team_pks.append(session.user.pk)
             team_online.append(session.user)
             team_online.append(session.user)
+    # Render page with forums list
     reads_tracker = ForumsTracker(request.user)
     reads_tracker = ForumsTracker(request.user)
     return request.theme.render_to_response('index.html',
     return request.theme.render_to_response('index.html',
                                             {
                                             {
                                              'forums_list': Forum.objects.treelist(request.acl.forums, tracker=reads_tracker),
                                              'forums_list': Forum.objects.treelist(request.acl.forums, tracker=reads_tracker),
                                              'team_online': team_online,
                                              'team_online': team_online,
+                                             'popular_threads': popular_threads,
                                              },
                                              },
                                             context_instance=RequestContext(request));
                                             context_instance=RequestContext(request));
 
 

+ 6 - 0
templates/sora/index.html

@@ -31,8 +31,14 @@
     {% endfor %}
     {% endfor %}
     {% endif %}
     {% endif %}
     
     
+    {% if popular_threads %}
     <h3>{% trans %}Popular Threads{% endtrans %}</h3>
     <h3>{% trans %}Popular Threads{% endtrans %}</h3>
+    {% for thread in popular_threads %}
+    <a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}" class="lead">{{ thread.name }}</a><br />
+    <a href="{% url 'forum' forum=thread.forum.pk, slug=thread.forum.slug %}">{{ thread.forum.name }}</a> - {{ thread.last|reltimesince }}
     <hr>
     <hr>
+    {% endfor %}
+    {% endif %}
     
     
     <h3>{% trans %}Forum Stats{% endtrans %}</h3>
     <h3>{% trans %}Forum Stats{% endtrans %}</h3>
     <p class="lead board-stat">{{ monitor.posts|int|intcomma }} <small>{% trans %}Posts{% endtrans %}</small></p>
     <p class="lead board-stat">{{ monitor.posts|int|intcomma }} <small>{% trans %}Posts{% endtrans %}</small></p>