Browse Source

refactored activity ranking generation to be cron-based

Rafał Pitoń 9 years ago
parent
commit
3d537e40b0

+ 24 - 26
misago/users/activepostersranking.py

@@ -6,43 +6,41 @@ from django.db.models import Count
 from django.utils import timezone
 
 from misago.categories.models import Category
-from misago.core.cache import cache
 
-
-ACTIVE_POSTERS_CACHE = 'misago_active_posters_ranking'
+from misago.users.models import ActivityRanking
 
 
 def get_active_posters_ranking():
-    ranking = cache.get(ACTIVE_POSTERS_CACHE, 'nada')
-    if ranking == 'nada':
-        ranking = get_real_active_posts_ranking()
-        cache.set(ACTIVE_POSTERS_CACHE, ranking, 18*3600)
-    return ranking
+    users = []
+
+    queryset = ActivityRanking.objects.select_related('user', 'user__rank')
+    for ranking in queryset.order_by('-score'):
+        ranking.user.score = ranking.score
+        users.append(ranking.user)
+
+    return {
+        'users': users,
+        'users_count': len(users),
+    }
 
 
-def get_real_active_posts_ranking():
+def build_active_posters_ranking():
     tracked_period = settings.MISAGO_RANKING_LENGTH
     tracked_since = timezone.now() - timedelta(days=tracked_period)
 
+    ActivityRanking.objects.all().delete()
+
     ranked_categories = []
     for category in Category.objects.all_categories():
         ranked_categories.append(category.pk)
 
-    User = get_user_model()
-    queryset = User.objects.filter(posts__gt=0)
-    queryset = queryset.filter(post__posted_on__gte=tracked_since,
-                               post__category__in=ranked_categories)
-    queryset = queryset.annotate(score=Count('post'))
-    queryset = queryset.select_related('rank')
-    queryset = queryset.order_by('-score')
-
-    users = list(queryset[:settings.MISAGO_RANKING_SIZE])
-
-    return {
-        'users': users,
-        'users_count': len(users),
-    }
-
+    queryset = get_user_model().objects.filter(posts__gt=0).filter(
+        post__posted_on__gte=tracked_since,
+        post__category__in=ranked_categories
+    ).annotate(score=Count('post'))
 
-def clear_active_posters_ranking():
-    cache.delete(ACTIVE_POSTERS_CACHE)
+    for ranking in queryset[:settings.MISAGO_RANKING_SIZE].iterator():
+        ActivityRanking.objects.create(
+            user=ranking,
+            score=ranking.score
+        )

+ 12 - 0
misago/users/management/commands/buildactivepostersranking.py

@@ -0,0 +1,12 @@
+from django.core.management.base import BaseCommand
+from misago.core.management.progressbar import show_progress
+from misago.users.activepostersranking import build_active_posters_ranking
+
+
+class Command(BaseCommand):
+    help = 'Builds active posters ranking'
+
+    def handle(self, *args, **options):
+        self.stdout.write('\n\nBuilding active posters ranking...')
+        build_active_posters_ranking()
+        self.stdout.write('Done!')

+ 11 - 0
misago/users/migrations/0001_initial.py

@@ -146,6 +146,17 @@ class Migration(migrations.Migration):
             preserve_default=True,
         ),
         migrations.CreateModel(
+            name='ActivityRanking',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('user', models.ForeignKey(related_name='+', to=settings.AUTH_USER_MODEL)),
+                ('score', models.PositiveIntegerField(default=0, db_index=True)),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+        migrations.CreateModel(
             name='Ban',
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),

+ 1 - 0
misago/users/models/__init__.py

@@ -1,5 +1,6 @@
 # flake8: noqa
 from misago.users.models.rank import *
 from misago.users.models.user import *
+from misago.users.models.activityranking import *
 from misago.users.models.ban import *
 from misago.users.models.warnings import *

+ 7 - 0
misago/users/models/activityranking.py

@@ -0,0 +1,7 @@
+from django.conf import settings
+from django.db import models
+
+
+class ActivityRanking(models.Model):
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+')
+    score = models.PositiveIntegerField(default=0, db_index=True)

+ 0 - 12
misago/users/signals.py

@@ -18,18 +18,6 @@ def handle_name_change(sender, **kwargs):
                                     canceler_slug=sender.slug)
 
 
-from django.db.models.signals import pre_delete
-@receiver(pre_delete, sender=get_user_model())
-def recache_active_users_list_on_active_user_delete(sender, **kwargs):
-    from misago.core.cache import cache
-    from misago.users.activepostersranking import (get_active_posters_ranking,
-                                                   clear_active_posters_ranking)
-
-    for user in get_active_posters_ranking()['users']:
-        if user == kwargs['instance']:
-            clear_active_posters_ranking()
-
-
 from misago.core.signals import secret_key_changed
 @receiver(secret_key_changed)
 def update_signatures_checksums(sender, **kwargs):

+ 10 - 36
misago/users/tests/test_activepostersranking.py

@@ -6,9 +6,8 @@ from misago.core.cache import cache
 from misago.threads.testutils import post_thread
 
 from misago.users.testutils import AuthenticatedUserTestCase
-from misago.users.activepostersranking import (get_active_posters_ranking,
-                                               get_real_active_posts_ranking,
-                                               clear_active_posters_ranking)
+from misago.users.activepostersranking import (
+    get_active_posters_ranking, build_active_posters_ranking)
 
 
 class TestActivePostersRanking(AuthenticatedUserTestCase):
@@ -26,10 +25,10 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         cache.clear()
         threadstore.clear()
 
-    def test_get_real_active_posts_ranking(self):
-        """get_real_active_posts_ranking returns list of active posters"""
+    def test_get_active_posters_ranking(self):
+        """get_active_posters_ranking returns list of active posters"""
         # no posts, empty tanking
-        empty_ranking = get_real_active_posts_ranking()
+        empty_ranking = get_active_posters_ranking()
 
         self.assertEqual(empty_ranking['users'], [])
         self.assertEqual(empty_ranking['users_count'], 0)
@@ -44,7 +43,8 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
 
         post_thread(self.category, poster=other_user)
 
-        ranking = get_real_active_posts_ranking()
+        build_active_posters_ranking()
+        ranking = get_active_posters_ranking()
 
         self.assertEqual(ranking['users'], [other_user])
         self.assertEqual(ranking['users_count'], 1)
@@ -56,37 +56,11 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         self.user.posts = 2
         self.user.save()
 
-        ranking = get_real_active_posts_ranking()
+        build_active_posters_ranking()
+        ranking = get_active_posters_ranking()
 
         self.assertEqual(ranking['users'], [self.user, other_user])
         self.assertEqual(ranking['users_count'], 2)
 
         self.assertEqual(ranking['users'][0].score, 2)
-        self.assertEqual(ranking['users'][1].score, 1)
-
-    def test_get_active_posters_ranking(self):
-        """get_active_posters_ranking returns cached list of active posters"""
-        ranking = get_active_posters_ranking()
-
-        self.assertEqual(ranking['users'], [])
-        self.assertEqual(ranking['users_count'], 0)
-
-        # post something
-        post_thread(self.category, poster=self.user)
-        post_thread(self.category, poster=self.user)
-
-        self.user.posts = 2
-        self.user.save()
-
-        # cache returns results
-        ranking = get_active_posters_ranking()
-
-        self.assertEqual(ranking['users'], [])
-        self.assertEqual(ranking['users_count'], 0)
-
-        # cache clear works
-        clear_active_posters_ranking()
-        ranking = get_active_posters_ranking()
-
-        self.assertEqual(ranking['users'], [self.user])
-        self.assertEqual(ranking['users_count'], 1)
+        self.assertEqual(ranking['users'][1].score, 1)

+ 4 - 0
misago/users/tests/test_users_api.py

@@ -11,6 +11,7 @@ from misago.core.cache import cache
 from misago.threads.models import Thread, Post
 from misago.threads.testutils import post_thread
 
+from misago.users.activepostersranking import build_active_posters_ranking
 from misago.users.models import Ban, BAN_USERNAME, Rank
 from misago.users.testutils import AuthenticatedUserTestCase
 
@@ -45,6 +46,8 @@ class ActivePostersListTests(AuthenticatedUserTestCase):
         self.user.posts = 1
         self.user.save()
 
+        build_active_posters_ranking()
+
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
         self.assertIn(self.user.username, response.content)
@@ -52,6 +55,7 @@ class ActivePostersListTests(AuthenticatedUserTestCase):
         self.assertIn('"is_offline":false', response.content)
 
         self.logout_user()
+        build_active_posters_ranking()
 
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)