Browse Source

Added post-specific reports cache, changed monitor implementation

Ralfp 12 years ago
parent
commit
e3fdb090f9

+ 2 - 2
misago/acl/builder.py

@@ -51,7 +51,7 @@ def acl(request, user):
     acl_key = user.make_acl_key()
     acl_key = user.make_acl_key()
     try:
     try:
         user_acl = cache.get(acl_key)
         user_acl = cache.get(acl_key)
-        if user_acl.version != monitor.acl_version:
+        if user_acl.version != monitor['acl_version']:
             raise InvalidCacheBackendError()
             raise InvalidCacheBackendError()
     except (AttributeError, InvalidCacheBackendError):
     except (AttributeError, InvalidCacheBackendError):
         user_acl = build_acl(request, user.get_roles())
         user_acl = build_acl(request, user.get_roles())
@@ -60,7 +60,7 @@ def acl(request, user):
 
 
 
 
 def build_acl(request, roles):
 def build_acl(request, roles):
-    new_acl = ACL(monitor.acl_version)
+    new_acl = ACL(monitor['acl_version'])
     forums = Forum.objects.get(special='root').get_descendants().order_by('lft')
     forums = Forum.objects.get(special='root').get_descendants().order_by('lft')
     perms = []
     perms = []
     forum_roles = {}
     forum_roles = {}

+ 4 - 4
misago/apps/admin/index.py

@@ -6,10 +6,10 @@ from misago.shortcuts import render_to_response
 def index(request):
 def index(request):
     return render_to_response('index.html',
     return render_to_response('index.html',
                               {
                               {
-                               'users': monitor.users,
-                               'users_inactive': monitor.users_inactive,
-                               'threads': monitor.threads,
-                               'posts': monitor.posts,
+                               'users': monitor['users'],
+                               'users_inactive': monitor['users_inactive'],
+                               'threads': monitor['threads'],
+                               'posts': monitor['posts'],
                                'admins': Session.objects.filter(user__isnull=False).filter(admin=1).order_by('user__username_slug').select_related('user'),
                                'admins': Session.objects.filter(user__isnull=False).filter(admin=1).order_by('user__username_slug').select_related('user'),
                               },
                               },
                               context_instance=RequestContext(request));
                               context_instance=RequestContext(request));

+ 5 - 3
misago/apps/reports/list.py

@@ -49,9 +49,9 @@ class ThreadsListView(ThreadsListBaseView, ThreadsListModeration, TypeMixin):
                 thread.report_forum = Forum.objects.forums_tree.get(thread.report_for.forum_id)
                 thread.report_forum = Forum.objects.forums_tree.get(thread.report_for.forum_id)
             self.threads.append(thread)
             self.threads.append(thread)
 
 
-        if int(monitor.reported_posts) != unresolved_count:
+        if monitor['reported_posts'] != unresolved_count:
             with UpdatingMonitor() as cm:
             with UpdatingMonitor() as cm:
-                monitor.reported_posts = unresolved_count
+                monitor['reported_posts'] = unresolved_count
 
 
     def threads_actions(self):
     def threads_actions(self):
         acl = self.request.acl.threads.get_role(self.forum)
         acl = self.request.acl.threads.get_role(self.forum)
@@ -83,7 +83,9 @@ class ThreadsListView(ThreadsListBaseView, ThreadsListModeration, TypeMixin):
                     reported_threads.append(thread.report_for.thread_id)
                     reported_threads.append(thread.report_for.thread_id)
         if reported_threads:
         if reported_threads:
             Thread.objects.filter(id__in=reported_threads).update(replies_reported=F('replies_reported') - 1)
             Thread.objects.filter(id__in=reported_threads).update(replies_reported=F('replies_reported') - 1)
-            Post.objects.filter(id__in=reported_posts).update(reported=False)
+            Post.objects.filter(id__in=reported_posts).update(reported=False, reports=None)
+            with UpdatingMonitor() as cm:
+                monitor.decrease('reported_posts', len(reported_threads))
 
 
     def action_sticky(self, ids):
     def action_sticky(self, ids):
         if self._action_sticky(ids):
         if self._action_sticky(ids):

+ 3 - 0
misago/apps/threadtype/jumps.py

@@ -268,6 +268,8 @@ class ReportPostBaseView(JumpView):
                         report.checkpoint_set.get(user=request.user, action="reported")
                         report.checkpoint_set.get(user=request.user, action="reported")
                     except Checkpoint.DoesNotExist:
                     except Checkpoint.DoesNotExist:
                         report.set_checkpoint(self.request, 'reported', user)
                         report.set_checkpoint(self.request, 'reported', user)
+                        self.post.add_reporter(self.request.user)
+                        self.post.save(force_update=True)
                     made_report = True
                     made_report = True
 
 
             if not report:
             if not report:
@@ -329,6 +331,7 @@ Member @%(reporter)s has reported following post by @%(reported)s:
                     reason.mentions.add(m)
                     reason.mentions.add(m)
 
 
                 self.post.reported = True
                 self.post.reported = True
+                self.post.add_reporter(self.request.user)
                 self.post.save(force_update=True)
                 self.post.save(force_update=True)
                 self.thread.replies_reported += 1
                 self.thread.replies_reported += 1
                 self.thread.save(force_update=True)
                 self.thread.save(force_update=True)

+ 2 - 2
misago/apps/threadtype/list/moderation.py

@@ -38,8 +38,8 @@ class ThreadsListModeration(object):
                     users.append(thread.start_post.user)
                     users.append(thread.start_post.user)
         if accepted:
         if accepted:
             with UpdatingMonitor() as cm:
             with UpdatingMonitor() as cm:
-                monitor.threads = int(monitor.threads) + accepted
-                monitor.posts = int(monitor.posts) + accepted
+                monitor.increase('threads', accepted)
+                monitor.increase('posts', accepted)
             self.forum.sync()
             self.forum.sync()
             self.forum.save(force_update=True)
             self.forum.save(force_update=True)
             for user in users:
             for user in users:

+ 1 - 1
misago/apps/threadtype/list/views.py

@@ -84,7 +84,7 @@ class ThreadsListBaseView(ViewBase):
                 if 'list_action' in self.form.errors:
                 if 'list_action' in self.form.errors:
                     self.message = Message(_("Requested action is incorrect."), 'error')
                     self.message = Message(_("Requested action is incorrect."), 'error')
                 else:
                 else:
-                    self.message = Message(form.non_field_errors()[0], 'error')
+                    self.message = Message(self.form.non_field_errors()[0], 'error')
         else:
         else:
             self.form = self.form(request=self.request)
             self.form = self.form(request=self.request)
 
 

+ 2 - 2
misago/management/commands/pruneforums.py

@@ -40,6 +40,6 @@ class Command(BaseCommand):
             forum.save(force_update=True)
             forum.save(force_update=True)
 
 
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
-            monitor.threads = Thread.objects.count()
-            monitor.posts = Post.objects.count()
+            monitor['threads'] = Thread.objects.count()
+            monitor['posts'] = Post.objects.count()
         self.stdout.write('Forums were pruned.\n')
         self.stdout.write('Forums were pruned.\n')

+ 397 - 0
misago/migrations/0019_auto__add_field_post_reports.py

@@ -0,0 +1,397 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'Post.reports'
+        db.add_column(u'misago_post', 'reports',
+                      self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'Post.reports'
+        db.delete_column(u'misago_post', 'reports')
+
+
+    models = {
+        'misago.alert': {
+            'Meta': {'object_name': 'Alert'},
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"}),
+            'variables': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'misago.ban': {
+            'Meta': {'object_name': 'Ban'},
+            'ban': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'test': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.change': {
+            'Meta': {'object_name': 'Change'},
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'change': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
+            'post_content': ('django.db.models.fields.TextField', [], {}),
+            'reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'thread_name_new': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'thread_name_old': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.checkpoint': {
+            'Meta': {'object_name': 'Checkpoint'},
+            'action': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'old_forum': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['misago.Forum']"}),
+            'old_forum_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'old_forum_slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'target_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'target_user_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'target_user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.fixture': {
+            'Meta': {'object_name': 'Fixture'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.forum': {
+            'Meta': {'object_name': 'Forum'},
+            'attrs': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description_preparsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_poster': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'last_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_thread': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Thread']"}),
+            'last_thread_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_thread_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_thread_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['misago.Forum']"}),
+            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'posts_delta': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'prune_last': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'prune_start': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'pruned_archive': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Forum']"}),
+            'redirect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'redirects': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'redirects_delta': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'show_details': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
+            'special': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'threads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'threads_delta': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
+        },
+        'misago.forumread': {
+            'Meta': {'object_name': 'ForumRead'},
+            'cleared': ('django.db.models.fields.DateTimeField', [], {}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
+        },
+        'misago.forumrole': {
+            'Meta': {'object_name': 'ForumRole'},
+            '_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'permissions'", 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.karma': {
+            'Meta': {'object_name': 'Karma'},
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.monitoritem': {
+            'Meta': {'object_name': 'MonitorItem'},
+            '_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'value'", 'blank': 'True'}),
+            'id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'default': "'int'", 'max_length': '255'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'misago.newsletter': {
+            'Meta': {'object_name': 'Newsletter'},
+            'content_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'content_plain': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ignore_subscriptions': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'progress': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'ranks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Rank']", 'symmetrical': 'False'}),
+            'step_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '32'})
+        },
+        'misago.post': {
+            'Meta': {'object_name': 'Post'},
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'current_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'delete_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'downvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'edit_reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'edit_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'edit_user_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'edit_user_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'edits': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'mentions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'mention_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'post': ('django.db.models.fields.TextField', [], {}),
+            'post_preparsed': ('django.db.models.fields.TextField', [], {}),
+            'protected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'reported': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'reports': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'upvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.pruningpolicy': {
+            'Meta': {'object_name': 'PruningPolicy'},
+            'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_visit': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'registered': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.rank': {
+            'Meta': {'object_name': 'Rank'},
+            'as_tab': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'criteria': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'on_index': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'roles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Role']", 'symmetrical': 'False'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'special': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
+        },
+        'misago.role': {
+            'Meta': {'object_name': 'Role'},
+            '_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'permissions'", 'blank': 'True'}),
+            '_special': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_column': "'special'", 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'protected': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'misago.session': {
+            'Meta': {'object_name': 'Session'},
+            'admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'crawler': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'data': ('django.db.models.fields.TextField', [], {'db_column': "'session_data'"}),
+            'id': ('django.db.models.fields.CharField', [], {'max_length': '42', 'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'last': ('django.db.models.fields.DateTimeField', [], {}),
+            'matched': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'rank': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Rank']"}),
+            'start': ('django.db.models.fields.DateTimeField', [], {}),
+            'team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"})
+        },
+        'misago.setting': {
+            'Meta': {'object_name': 'Setting'},
+            '_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'value'", 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'extra': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'field': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.SettingsGroup']", 'to_field': "'key'"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'normalize_to': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'separator': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
+            'value_default': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'misago.settingsgroup': {
+            'Meta': {'object_name': 'SettingsGroup'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'misago.signinattempt': {
+            'Meta': {'object_name': 'SignInAttempt'},
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
+        },
+        'misago.thread': {
+            'Meta': {'object_name': 'Thread'},
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'downvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last': ('django.db.models.fields.DateTimeField', [], {}),
+            'last_post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Post']"}),
+            'last_poster': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
+            'last_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'last_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'participants': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'private_thread_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'replies': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'replies_deleted': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'replies_moderated': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'replies_reported': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'report_for': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'report_set'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Post']"}),
+            'score': ('django.db.models.fields.PositiveIntegerField', [], {'default': '30'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
+            'start': ('django.db.models.fields.DateTimeField', [], {}),
+            'start_post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Post']"}),
+            'start_poster': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'start_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'start_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
+            'start_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'upvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'weight': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.threadread': {
+            'Meta': {'object_name': 'ThreadRead'},
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
+        },
+        'misago.token': {
+            'Meta': {'object_name': 'Token'},
+            'accessed': ('django.db.models.fields.DateTimeField', [], {}),
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.CharField', [], {'max_length': '42', 'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'signin_tokens'", 'to': "orm['misago.User']"})
+        },
+        'misago.user': {
+            'Meta': {'object_name': 'User'},
+            'acl_key': ('django.db.models.fields.CharField', [], {'max_length': '12', 'null': 'True', 'blank': 'True'}),
+            'activation': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'alerts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'alerts_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'allow_pds': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'avatar_ban': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'avatar_ban_reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'avatar_ban_reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'avatar_image': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'avatar_original': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'avatar_temp': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'avatar_type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}),
+            'email_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
+            'followers': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'following': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'follows': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'follows_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'hide_activity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ignores': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ignores_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
+            'is_team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'join_agent': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'join_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'join_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'karma_given_n': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'karma_given_p': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'karma_n': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'karma_p': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'last_agent': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'last_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
+            'last_post': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_search': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'password_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'rank': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Rank']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'ranking': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'receive_newsletters': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'roles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Role']", 'symmetrical': 'False'}),
+            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'signature_ban': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'signature_ban_reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'signature_ban_reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'signature_preparsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'subscribe_reply': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'subscribe_start': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'sync_pds': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'threads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'timezone': ('django.db.models.fields.CharField', [], {'default': "'utc'", 'max_length': '255'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '12', 'null': 'True', 'blank': 'True'}),
+            'unread_pds': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username_slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
+            'votes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+        },
+        'misago.usernamechange': {
+            'Meta': {'object_name': 'UsernameChange'},
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'old_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'namechanges'", 'to': "orm['misago.User']"})
+        },
+        'misago.watchedthread': {
+            'Meta': {'object_name': 'WatchedThread'},
+            'email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_read': ('django.db.models.fields.DateTimeField', [], {}),
+            'starter': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['misago.User']"}),
+            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
+        }
+    }
+
+    complete_apps = ['misago']

+ 2 - 2
misago/models/banmodel.py

@@ -59,9 +59,9 @@ class BanCache(object):
         self.version = 0
         self.version = 0
 
 
     def check_for_updates(self, request):
     def check_for_updates(self, request):
-        if (self.version < monitor.bans_version
+        if (self.version < monitor['bans_version']
                 or (self.expires != None and self.expires < timezone.now())):
                 or (self.expires != None and self.expires < timezone.now())):
-            self.version = monitor.bans_version
+            self.version = monitor['bans_version']
 
 
             # Check Ban
             # Check Ban
             if request.user.is_authenticated():
             if request.user.is_authenticated():

+ 14 - 0
misago/models/postmodel.py

@@ -35,6 +35,7 @@ class Post(models.Model):
     edit_user_slug = models.SlugField(max_length=255, null=True, blank=True)
     edit_user_slug = models.SlugField(max_length=255, null=True, blank=True)
     delete_date = models.DateTimeField(null=True, blank=True)
     delete_date = models.DateTimeField(null=True, blank=True)
     reported = models.BooleanField(default=False, db_index=True)
     reported = models.BooleanField(default=False, db_index=True)
+    reports = models.CharField(max_length=255, null=True, blank=True)
     moderated = models.BooleanField(default=False)
     moderated = models.BooleanField(default=False)
     deleted = models.BooleanField(default=False)
     deleted = models.BooleanField(default=False)
     protected = models.BooleanField(default=False)
     protected = models.BooleanField(default=False)
@@ -114,6 +115,19 @@ class Post(models.Model):
         except IndexError:
         except IndexError:
             return None
             return None
 
 
+    def add_reporter(self, user):
+        if not self.reports:
+            self.reports = ','
+        self.reports += '%s,' % user.pk
+
+    def reported_by(self, user):
+        if not self.reports:
+            return False
+        try:
+            return ',%s,' % user.pk in self.reports
+        except AttributeError:
+            return ',%s,' % user in self.reports
+
 
 
 def rename_user_handler(sender, **kwargs):
 def rename_user_handler(sender, **kwargs):
     Post.objects.filter(user=sender).update(
     Post.objects.filter(user=sender).update(

+ 2 - 0
misago/models/threadmodel.py

@@ -243,6 +243,7 @@ def report_update_handler(sender, **kwargs):
             reported_post = thread.report_for
             reported_post = thread.report_for
             if reported_post.reported:
             if reported_post.reported:
                 reported_post.reported = False
                 reported_post.reported = False
+                reported_post.reports = None
                 reported_post.save(force_update=True)
                 reported_post.save(force_update=True)
                 reported_post.thread.replies_reported -= 1
                 reported_post.thread.replies_reported -= 1
                 reported_post.thread.save(force_update=True)
                 reported_post.thread.save(force_update=True)
@@ -257,6 +258,7 @@ def report_delete_handler(sender, **kwargs):
             reported_post = thread.report_for
             reported_post = thread.report_for
             if reported_post.reported:
             if reported_post.reported:
                 reported_post.reported = False
                 reported_post.reported = False
+                reported_post.reports = None
                 reported_post.save(force_update=True)
                 reported_post.save(force_update=True)
                 reported_post.thread.replies_reported -= 1
                 reported_post.thread.replies_reported -= 1
                 reported_post.thread.save(force_update=True)
                 reported_post.thread.save(force_update=True)

+ 8 - 8
misago/models/usermodel.py

@@ -34,12 +34,12 @@ class UserManager(models.Manager):
 
 
     def resync_monitor(self):
     def resync_monitor(self):
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
-            monitor.users = self.filter(activation=0).count()
-            monitor.users_inactive = self.filter(activation__gt=0).count()
+            monitor['users'] = self.filter(activation=0).count()
+            monitor['users_inactive'] = self.filter(activation__gt=0).count()
             last_user = self.filter(activation=0).latest('id')
             last_user = self.filter(activation=0).latest('id')
-            monitor.last_user = last_user.pk
-            monitor.last_user_name = last_user.username
-            monitor.last_user_slug = last_user.username_slug
+            monitor['last_user'] = last_user.pk
+            monitor['last_user_name'] = last_user.username
+            monitor['last_user_slug'] = last_user.username_slug
 
 
     def create_user(self, username, email, password, timezone=False, ip='127.0.0.1', agent='', no_roles=False, activation=0, request=False):
     def create_user(self, username, email, password, timezone=False, ip='127.0.0.1', agent='', no_roles=False, activation=0, request=False):
         token = ''
         token = ''
@@ -89,9 +89,9 @@ class UserManager(models.Manager):
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
             if activation == 0:
             if activation == 0:
                 monitor.increase('users')
                 monitor.increase('users')
-                monitor.last_user = new_user.pk
-                monitor.last_user_name = new_user.username
-                monitor.last_user_slug = new_user.username_slug
+                monitor['last_user'] = new_user.pk
+                monitor['last_user_name'] = new_user.username
+                monitor['last_user_slug'] = new_user.username_slug
             else:
             else:
                 monitor.increase('users_inactive')
                 monitor.increase('users_inactive')
 
 

+ 8 - 14
misago/monitor.py

@@ -20,15 +20,12 @@ def refresh_monitor():
 
 
 
 
 class Monitor(object):
 class Monitor(object):
-    def __init__(self, local):
-        self.thread = local
-
     def monitor(self):
     def monitor(self):
         try:
         try:
-            return self.thread.monitor
+            return _thread_local.monitor
         except AttributeError:
         except AttributeError:
-            self.thread.monitor = load_monitor()
-            return self.thread.monitor
+            _thread_local.monitor = load_monitor()
+            return _thread_local.monitor
 
 
     def entry(self, key):
     def entry(self, key):
         try:
         try:
@@ -39,28 +36,25 @@ class Monitor(object):
     def __contains__(self, key):
     def __contains__(self, key):
         return key in self.monitor()
         return key in self.monitor()
 
 
-    def __getattr__(self, key):
-        return self.entry(key)[0]
-
     def __getitem__(self, key):
     def __getitem__(self, key):
         return self.entry(key)[0]
         return self.entry(key)[0]
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
-        self.thread.monitor_update.append((key, value))
+        _thread_local.monitor_update.append((key, value))
         return value
         return value
 
 
     def increase(self, key, i=1):
     def increase(self, key, i=1):
-        self.thread.monitor_update.append((key, self[key] + i))
+        _thread_local.monitor_update.append((key, self[key] + i))
 
 
     def decrease(self, key, i=1):
     def decrease(self, key, i=1):
-        self.thread.monitor_update.append((key, self[key] - i))
+        _thread_local.monitor_update.append((key, self[key] - i))
 
 
     def get(self, key, default=None):
     def get(self, key, default=None):
         if not key in self.monitor():
         if not key in self.monitor():
             return default
             return default
         return self.entry(key)[0]
         return self.entry(key)[0]
 
 
-    def get_updated(self, key):
+    def updated(self, key):
         if key in self.monitor():
         if key in self.monitor():
             return self.entry(key)[1]
             return self.entry(key)[1]
         return None
         return None
@@ -103,4 +97,4 @@ class UpdatingMonitor(object):
             _thread_local.monitor_update = None
             _thread_local.monitor_update = None
 
 
 
 
-monitor = Monitor(_thread_local)
+monitor = Monitor()

+ 4 - 4
misago/onlines.py

@@ -8,8 +8,8 @@ class MembersOnline(object):
     def __init__(self, mode, frequency=180):
     def __init__(self, mode, frequency=180):
         self.frequency = frequency
         self.frequency = frequency
         self._mode = mode
         self._mode = mode
-        self._members = int(monitor.online_members)
-        self._all = int(monitor.online_all)
+        self._members = monitor['online_members']
+        self._all = monitor['online_all']
         self._om = self._members
         self._om = self._members
         self._oa = self._all
         self._oa = self._all
         if (self._mode != 'no' or monitor.expired('online_all', frequency) or
         if (self._mode != 'no' or monitor.expired('online_all', frequency) or
@@ -44,9 +44,9 @@ class MembersOnline(object):
         if self._mode == 'snap':
         if self._mode == 'snap':
             with UpdatingMonitor() as cm:
             with UpdatingMonitor() as cm:
                 if self._members != self._om:
                 if self._members != self._om:
-                    monitor.online_members = self._members
+                    monitor['online_members'] = self._members
                 if self._all != self._oa:
                 if self._all != self._oa:
-                    monitor.online_all = self._all
+                    monitor['online_all'] = self._all
 
 
     def stats(self, request):
     def stats(self, request):
         stat = {
         stat = {

+ 20 - 20
misago/tests/user_manager_create_user.py

@@ -19,11 +19,11 @@ class UserManagerCreateUserTestCase(TestCase):
                 raise AssertionError("User A was not saved in database!")
                 raise AssertionError("User A was not saved in database!")
 
 
         refresh_monitor()
         refresh_monitor()
-        self.assertEqual(int(monitor.users), 1)
-        self.assertEqual(int(monitor.users_inactive), 0)
-        self.assertEqual(int(monitor.last_user), user_a.pk)
-        self.assertEqual(monitor.last_user_name, user_a.username)
-        self.assertEqual(monitor.last_user_slug, user_a.username_slug)
+        self.assertEqual(int(monitor['users']), 1)
+        self.assertEqual(int(monitor['users_inactive']), 0)
+        self.assertEqual(int(monitor['last_user']), user_a.pk)
+        self.assertEqual(monitor['last_user_name'], user_a.username)
+        self.assertEqual(monitor['last_user_slug'], user_a.username_slug)
 
 
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
             user_b = User.objects.create_user('InactiveTest', 'lemsm@sp.com', '123pass', activation=User.ACTIVATION_USER)
             user_b = User.objects.create_user('InactiveTest', 'lemsm@sp.com', '123pass', activation=User.ACTIVATION_USER)
@@ -35,11 +35,11 @@ class UserManagerCreateUserTestCase(TestCase):
                 raise AssertionError("User B was not saved in database!")
                 raise AssertionError("User B was not saved in database!")
 
 
         refresh_monitor()
         refresh_monitor()
-        self.assertEqual(int(monitor.users), 1)
-        self.assertEqual(int(monitor.users_inactive), 1)
-        self.assertEqual(int(monitor.last_user), user_a.pk)
-        self.assertEqual(monitor.last_user_name, user_a.username)
-        self.assertEqual(monitor.last_user_slug, user_a.username_slug)
+        self.assertEqual(int(monitor['users']), 1)
+        self.assertEqual(int(monitor['users_inactive']), 1)
+        self.assertEqual(int(monitor['last_user']), user_a.pk)
+        self.assertEqual(monitor['last_user_name'], user_a.username)
+        self.assertEqual(monitor['last_user_slug'], user_a.username_slug)
 
 
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
             try:
             try:
@@ -49,11 +49,11 @@ class UserManagerCreateUserTestCase(TestCase):
                 pass
                 pass
 
 
         refresh_monitor()
         refresh_monitor()
-        self.assertEqual(int(monitor.users), 1)
-        self.assertEqual(int(monitor.users_inactive), 1)
-        self.assertEqual(int(monitor.last_user), user_a.pk)
-        self.assertEqual(monitor.last_user_name, user_a.username)
-        self.assertEqual(monitor.last_user_slug, user_a.username_slug)
+        self.assertEqual(int(monitor['users']), 1)
+        self.assertEqual(int(monitor['users_inactive']), 1)
+        self.assertEqual(int(monitor['last_user']), user_a.pk)
+        self.assertEqual(monitor['last_user_name'], user_a.username)
+        self.assertEqual(monitor['last_user_slug'], user_a.username_slug)
 
 
         with UpdatingMonitor() as cm:
         with UpdatingMonitor() as cm:
             try:
             try:
@@ -63,8 +63,8 @@ class UserManagerCreateUserTestCase(TestCase):
                 pass
                 pass
 
 
         refresh_monitor()
         refresh_monitor()
-        self.assertEqual(int(monitor.users), 1)
-        self.assertEqual(int(monitor.users_inactive), 1)
-        self.assertEqual(int(monitor.last_user), user_a.pk)
-        self.assertEqual(monitor.last_user_name, user_a.username)
-        self.assertEqual(monitor.last_user_slug, user_a.username_slug)
+        self.assertEqual(int(monitor['users']), 1)
+        self.assertEqual(int(monitor['users_inactive']), 1)
+        self.assertEqual(int(monitor['last_user']), user_a.pk)
+        self.assertEqual(monitor['last_user_name'], user_a.username)
+        self.assertEqual(monitor['last_user_slug'], user_a.username_slug)

+ 4 - 0
templates/cranefly/private_threads/thread.html

@@ -242,11 +242,15 @@
                   <a href="{{ url('private_post_report_show', thread=thread.pk, slug=thread.slug, post=post.pk) }}">{% trans %}Show report{% endtrans %}</a>
                   <a href="{{ url('private_post_report_show', thread=thread.pk, slug=thread.slug, post=post.pk) }}">{% trans %}Show report{% endtrans %}</a>
                   {% endif %}
                   {% endif %}
                   {% if acl.reports.can_report() %}
                   {% if acl.reports.can_report() %}
+                  {% if post.reported_by(user) %}
+                  <a href="{{ url('private_post_report', thread=thread.pk, slug=thread.slug, post=post.pk) }}" class="tooltip-top" title="{% trans %}You have already reported this post.{% endtrans %}" disabled="disabled">{% trans %}Reported{% endtrans %}</a>
+                  {% else %}
                   <form action="{{ url('private_post_report', thread=thread.pk, slug=thread.slug, post=post.pk) }}" class="form-inline form-report" method="post" autocomplete="off">
                   <form action="{{ url('private_post_report', thread=thread.pk, slug=thread.slug, post=post.pk) }}" class="form-inline form-report" method="post" autocomplete="off">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                     <button type="submit" class="btn btn-link btn-report tooltip-top" title="{% trans %}Bring this post to moderator attention.{% endtrans %}">{% trans %}Report{% endtrans %}</button>
                     <button type="submit" class="btn btn-link btn-report tooltip-top" title="{% trans %}Bring this post to moderator attention.{% endtrans %}">{% trans %}Report{% endtrans %}</button>
                   </form>
                   </form>
                   {% endif %}
                   {% endif %}
+                  {% endif %}
                   {% if acl.threads.can_edit_thread(user, forum, thread, post) and thread.start_post_id == post.pk %}
                   {% if acl.threads.can_edit_thread(user, forum, thread, post) and thread.start_post_id == post.pk %}
                   <a href="{{ url('private_thread_edit', thread=thread.pk, slug=thread.slug) }}" class="post-edit">{% trans %}Edit{% endtrans %}</a>
                   <a href="{{ url('private_thread_edit', thread=thread.pk, slug=thread.slug) }}" class="post-edit">{% trans %}Edit{% endtrans %}</a>
                   {% elif acl.threads.can_edit_reply(user, forum, thread, post) %}
                   {% elif acl.threads.can_edit_reply(user, forum, thread, post) %}

+ 4 - 0
templates/cranefly/threads/thread.html

@@ -265,11 +265,15 @@
               <a href="{{ url('post_report_show', thread=thread.pk, slug=thread.slug, post=post.pk) }}">{% trans %}Show report{% endtrans %}</a>
               <a href="{{ url('post_report_show', thread=thread.pk, slug=thread.slug, post=post.pk) }}">{% trans %}Show report{% endtrans %}</a>
               {% endif %}
               {% endif %}
               {% if acl.reports.can_report() %}
               {% if acl.reports.can_report() %}
+              {% if post.reported_by(user) %}
+              <a href="{{ url('post_report', thread=thread.pk, slug=thread.slug, post=post.pk) }}" class="tooltip-top" title="{% trans %}You have already reported this post.{% endtrans %}" disabled="disabled">{% trans %}Reported{% endtrans %}</a>
+              {% else %}
               <form action="{{ url('post_report', thread=thread.pk, slug=thread.slug, post=post.pk) }}" class="form-inline form-report" method="post" autocomplete="off">
               <form action="{{ url('post_report', thread=thread.pk, slug=thread.slug, post=post.pk) }}" class="form-inline form-report" method="post" autocomplete="off">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
                 <button type="submit" class="btn btn-link btn-report tooltip-top" title="{% trans %}Bring this post to moderator attention.{% endtrans %}">{% trans %}Report{% endtrans %}</button>
                 <button type="submit" class="btn btn-link btn-report tooltip-top" title="{% trans %}Bring this post to moderator attention.{% endtrans %}">{% trans %}Report{% endtrans %}</button>
               </form>
               </form>
               {% endif %}
               {% endif %}
+              {% endif %}
               {% if acl.threads.can_edit_thread(user, forum, thread, post) and thread.start_post_id == post.pk %}
               {% if acl.threads.can_edit_thread(user, forum, thread, post) and thread.start_post_id == post.pk %}
               <a href="{{ url('thread_edit', thread=thread.pk, slug=thread.slug) }}" class="post-edit">{% trans %}Edit{% endtrans %}</a>
               <a href="{{ url('thread_edit', thread=thread.pk, slug=thread.slug) }}" class="post-edit">{% trans %}Edit{% endtrans %}</a>
               {% elif acl.threads.can_edit_reply(user, forum, thread, post) %}
               {% elif acl.threads.can_edit_reply(user, forum, thread, post) %}