Rafał Pitoń 9 лет назад
Родитель
Сommit
4d35854110
105 измененных файлов с 1457 добавлено и 1425 удалено
  1. 6 5
      misago/conf/defaults.py
  2. 1 1
      misago/core/tests/test_common_middleware_redirect.py
  3. 11 9
      misago/faker/management/commands/createfakethreads.py
  4. 108 0
      misago/readtracker/categoriestracker.py
  5. 2 3
      misago/readtracker/dates.py
  6. 0 97
      misago/readtracker/forumstracker.py
  7. 3 3
      misago/readtracker/migrations/0001_initial.py
  8. 3 3
      misago/readtracker/models.py
  9. 5 5
      misago/readtracker/signals.py
  10. 8 6
      misago/readtracker/tests/test_dates.py
  11. 92 89
      misago/readtracker/tests/test_readtracker.py
  12. 25 23
      misago/readtracker/tests/test_views.py
  13. 30 26
      misago/readtracker/threadstracker.py
  14. 1 1
      misago/readtracker/urls.py
  15. 7 7
      misago/readtracker/views.py
  16. 6 7
      misago/threads/admin.py
  17. 3 2
      misago/threads/counts.py
  18. 2 2
      misago/threads/events.py
  19. 5 5
      misago/threads/forms/admin.py
  20. 28 27
      misago/threads/forms/moderation.py
  21. 10 10
      misago/threads/migrations/0001_initial.py
  22. 2 2
      misago/threads/models/event.py
  23. 7 7
      misago/threads/models/label.py
  24. 3 3
      misago/threads/models/post.py
  25. 1 1
      misago/threads/models/report.py
  26. 7 7
      misago/threads/models/thread.py
  27. 6 6
      misago/threads/moderation/threads.py
  28. 6 6
      misago/threads/permissions/privatethreads.py
  29. 153 150
      misago/threads/permissions/threads.py
  30. 1 1
      misago/threads/posting/__init__.py
  31. 3 4
      misago/threads/posting/savechanges.py
  32. 1 1
      misago/threads/posting/threadclose.py
  33. 4 3
      misago/threads/posting/threadlabel.py
  34. 1 1
      misago/threads/posting/threadpin.py
  35. 6 7
      misago/threads/posting/updatestats.py
  36. 1 1
      misago/threads/reports.py
  37. 28 27
      misago/threads/signals.py
  38. 59 58
      misago/threads/tests/-test_editpost_view.py
  39. 156 155
      misago/threads/tests/-test_forumthreads_view.py
  40. 5 5
      misago/threads/tests/-test_goto_views.py
  41. 10 10
      misago/threads/tests/-test_gotolists_views.py
  42. 12 12
      misago/threads/tests/-test_moderatedcontent_view.py
  43. 5 5
      misago/threads/tests/-test_newthreads_view.py
  44. 10 10
      misago/threads/tests/-test_post_views.py
  45. 3 3
      misago/threads/tests/-test_privatethread_view.py
  46. 7 7
      misago/threads/tests/-test_privatethreads_view.py
  47. 38 38
      misago/threads/tests/-test_replythread_view.py
  48. 38 38
      misago/threads/tests/-test_startthread_view.py
  49. 34 32
      misago/threads/tests/-test_thread_view.py
  50. 3 3
      misago/threads/tests/-test_threadparticipants_views.py
  51. 3 3
      misago/threads/tests/-test_unreadthreads_view.py
  52. 12 12
      misago/threads/tests/test_counters.py
  53. 4 4
      misago/threads/tests/test_event_model.py
  54. 4 4
      misago/threads/tests/test_events.py
  55. 8 8
      misago/threads/tests/test_events_view.py
  56. 5 5
      misago/threads/tests/test_goto.py
  57. 12 14
      misago/threads/tests/test_label_model.py
  58. 23 23
      misago/threads/tests/test_labelsadmin_views.py
  59. 5 6
      misago/threads/tests/test_participants.py
  60. 10 10
      misago/threads/tests/test_post_model.py
  61. 3 3
      misago/threads/tests/test_posts_moderation.py
  62. 4 4
      misago/threads/tests/test_synchronizethreads.py
  63. 18 18
      misago/threads/tests/test_thread_model.py
  64. 4 4
      misago/threads/tests/test_threadparticipant_model.py
  65. 13 13
      misago/threads/tests/test_threads_moderation.py
  66. 3 3
      misago/threads/tests/test_threadslist_view.py
  67. 7 7
      misago/threads/testutils.py
  68. 2 2
      misago/threads/threadtypes/__init__.py
  69. 9 9
      misago/threads/threadtypes/forumthread.py
  70. 4 4
      misago/threads/threadtypes/privatethread.py
  71. 1 1
      misago/threads/threadtypes/report.py
  72. 13 13
      misago/threads/urls/threads.py
  73. 1 1
      misago/threads/views/generic/__init__.py
  74. 38 37
      misago/threads/views/generic/base.py
  75. 6 6
      misago/threads/views/generic/events.py
  76. 4 4
      misago/threads/views/generic/forum/__init__.py
  77. 43 43
      misago/threads/views/generic/forum/actions.py
  78. 9 9
      misago/threads/views/generic/forum/filtering.py
  79. 10 10
      misago/threads/views/generic/forum/threads.py
  80. 18 18
      misago/threads/views/generic/forum/view.py
  81. 10 9
      misago/threads/views/generic/goto.py
  82. 5 5
      misago/threads/views/generic/gotopostslist.py
  83. 10 10
      misago/threads/views/generic/post.py
  84. 19 19
      misago/threads/views/generic/posting.py
  85. 24 24
      misago/threads/views/generic/thread/postsactions.py
  86. 29 29
      misago/threads/views/generic/thread/threadactions.py
  87. 13 13
      misago/threads/views/generic/thread/view.py
  88. 4 4
      misago/threads/views/labelsadmin.py
  89. 1 1
      misago/threads/views/moderatedcontent.py
  90. 1 1
      misago/threads/views/newthreads.py
  91. 16 16
      misago/threads/views/privatethreads.py
  92. 1 1
      misago/threads/views/threads.py
  93. 1 1
      misago/threads/views/unreadthreads.py
  94. 1 3
      misago/urls.py
  95. 5 3
      misago/users/activepostersranking.py
  96. 4 4
      misago/users/api/auth.py
  97. 2 2
      misago/users/api/userendpoints/changeemail.py
  98. 2 2
      misago/users/api/userendpoints/changepassword.py
  99. 2 2
      misago/users/api/userendpoints/create.py
  100. 0 1
      misago/users/api/userendpoints/list.py
  101. 10 8
      misago/users/api/users.py
  102. 7 7
      misago/users/tests/test_activepostersranking.py
  103. 5 5
      misago/users/tests/test_useradmin_views.py
  104. 8 8
      misago/users/tests/test_users_api.py
  105. 15 15
      misago/users/views/admin/users.py

+ 6 - 5
misago/conf/defaults.py

@@ -105,7 +105,7 @@ INSTALLED_APPS = (
     'misago.markup',
     'misago.notifications',
     'misago.legal',
-    'misago.forums',
+    'misago.categories',
     'misago.threads',
     'misago.readtracker',
     'misago.faker',
@@ -159,7 +159,7 @@ MISAGO_ACL_EXTENSIONS = (
     'misago.users.permissions.warnings',
     'misago.users.permissions.moderation',
     'misago.users.permissions.delete',
-    'misago.forums.permissions',
+    'misago.categories.permissions',
     'misago.threads.permissions.threads',
     'misago.threads.permissions.privatethreads',
 )
@@ -182,9 +182,10 @@ MISAGO_POSTING_MIDDLEWARES = (
 
 MISAGO_THREAD_TYPES = (
     # category and redirect types
-    'misago.forums.forumtypes.RootCategory',
-    'misago.forums.forumtypes.Category',
-    'misago.forums.forumtypes.Redirect',
+    'misago.categories.forumtypes.RootCategory',
+    'misago.categories.forumtypes.Category',
+    'misago.categories.forumtypes.Redirect',
+
     # real thread types
     'misago.threads.threadtypes.forumthread.ForumThread',
     'misago.threads.threadtypes.privatethread.PrivateThread',

+ 1 - 1
misago/core/tests/test_common_middleware_redirect.py

@@ -1,7 +1,7 @@
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
-class ChangeForumOptionsTests(AuthenticatedUserTestCase):
+class CommonMiddlewareRedirectTests(AuthenticatedUserTestCase):
     def test_slashless_redirect(self):
         """
         Regression test for https://github.com/rafalp/Misago/issues/450

+ 11 - 9
misago/faker/management/commands/createfakethreads.py

@@ -10,7 +10,7 @@ from django.template.defaultfilters import linebreaks_filter
 from django.utils import timezone
 
 from misago.core.management.progressbar import show_progress
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.threads.checksums import update_post_checksum
 from misago.threads.models import Thread, Post
 
@@ -27,7 +27,9 @@ class Command(BaseCommand):
             self.stderr.write("\nOptional argument should be integer.")
             sys.exit(1)
 
-        forums = [f for f in Forum.objects.all_forums().filter(role='forum')]
+        categories = []
+        for category in Category.objects.all_categories().filter(role='forum'):
+            categories.append(category)
 
         fake = Factory.create()
 
@@ -44,7 +46,7 @@ class Command(BaseCommand):
         for i in xrange(fake_threads_to_create):
             with atomic():
                 datetime = timezone.now()
-                forum = random.choice(forums)
+                category = random.choice(categories)
                 user = User.objects.order_by('?')[:1][0]
 
                 thread_is_moderated = random.randint(0, 100) > 90
@@ -52,7 +54,7 @@ class Command(BaseCommand):
                 thread_is_closed = random.randint(0, 100) > 90
 
                 thread = Thread(
-                    forum=forum,
+                    category=category,
                     started_on=datetime,
                     starter_name='-',
                     starter_slug='-',
@@ -68,7 +70,7 @@ class Command(BaseCommand):
 
                 fake_message = "\n\n".join(fake.paragraphs())
                 post = Post.objects.create(
-                    forum=forum,
+                    category=category,
                     thread=thread,
                     poster=user,
                     poster_name=user.username,
@@ -108,7 +110,7 @@ class Command(BaseCommand):
                         is_hidden = False
 
                     post = Post.objects.create(
-                        forum=forum,
+                        category=category,
                         thread=thread,
                         poster=user,
                         poster_name=user.username,
@@ -139,8 +141,8 @@ class Command(BaseCommand):
             thread.is_pinned = True
             thread.save()
 
-        for forum in forums:
-            forum.synchronize()
-            forum.save()
+        for category in categories:
+            category.synchronize()
+            category.save()
 
         self.stdout.write(message % created_threads)

+ 108 - 0
misago/readtracker/categoriestracker.py

@@ -0,0 +1,108 @@
+from django.db.models import F
+from django.utils import timezone
+
+from misago.threads.permissions import exclude_invisible_threads
+
+from misago.readtracker import signals
+from misago.readtracker.dates import is_date_tracked
+from misago.readtracker.models import CategoryRead
+
+
+__all__ = ['make_read_aware', 'sync_record']
+
+
+def make_read_aware(user, categories):
+    if not hasattr(categories, '__iter__'):
+        categories = [categories]
+
+    if user.is_anonymous():
+        make_read(categories)
+        return None
+
+    categories_dict = {}
+    for category in categories:
+        category.last_read_on = user.reads_cutoff
+        category.is_read = not is_date_tracked(category.last_post_on, user)
+        if not category.is_read:
+            categories_dict[category.pk] = category
+
+    if categories_dict:
+        categories_records = user.categoryread_set.filter(
+            category__in=categories_dict.keys())
+
+        for record in categories_records:
+            category = categories_dict[record.category_id]
+            category.last_read_on = record.last_read_on
+            category.is_read = category.last_read_on >= category.last_post_on
+
+
+def make_read(categories):
+    now = timezone.now()
+    for category in categories:
+        category.last_read_on = now
+        category.is_read = True
+
+
+def start_record(user, category):
+    user.categoryread_set.create(
+        category=category,
+        last_read_on=user.reads_cutoff,
+    )
+
+
+def sync_record(user, category):
+    cutoff_date = user.reads_cutoff
+
+    try:
+        category_record = user.categoryread_set.get(category=category)
+        if category_record.last_read_on > cutoff_date:
+            cutoff_date = category_record.last_read_on
+    except CategoryRead.DoesNotExist:
+        category_record = None
+
+    recorded_threads = category.thread_set.filter(last_post_on__gt=cutoff_date)
+    recorded_threads = exclude_invisible_threads(
+        recorded_threads, user, category)
+
+    all_threads_count = recorded_threads.count()
+
+    read_threads = user.threadread_set.filter(
+        category=category, last_read_on__gt=cutoff_date)
+    read_threads_count = read_threads.filter(
+        thread__last_post_on__lte=F("last_read_on")).count()
+
+    category_is_read = read_threads_count == all_threads_count
+
+    if category_is_read:
+        signals.category_read.send(sender=user, category=category)
+
+    if category_record:
+        if category_is_read:
+            category_record.last_read_on = category_record.last_read_on
+        else:
+            category_record.last_read_on = cutoff_date
+        category_record.save(update_fields=['last_read_on'])
+    else:
+        if category_is_read:
+            last_read_on = timezone.now()
+        else:
+            last_read_on = cutoff_date
+
+        category_record = user.categoryread_set.create(
+            category=category,
+            last_read_on=last_read_on)
+
+
+def read_category(user, category):
+    try:
+        category_record = user.categoryread_set.get(category=category)
+        category_record.last_read_on = timezone.now()
+        category_record.save(update_fields=['last_read_on'])
+    except CategoryRead.DoesNotExist:
+        user.categoryread_set.create(
+            category=category,
+            last_read_on=timezone.now(),
+        )
+
+    signals.category_read.send(sender=user, category=category)
+

+ 2 - 3
misago/readtracker/dates.py

@@ -1,12 +1,11 @@
 from datetime import timedelta
-
 from django.conf import settings
 from django.utils import timezone
 
 
-def is_date_tracked(date, user, forum_read_cutoff=None):
+def is_date_tracked(date, user, category_read_cutoff=None):
     if date:
-        if forum_read_cutoff and forum_read_cutoff > date:
+        if category_read_cutoff and category_read_cutoff > date:
             return False
         else:
             return date > user.reads_cutoff

+ 0 - 97
misago/readtracker/forumstracker.py

@@ -1,97 +0,0 @@
-from django.db.models import F
-from django.utils import timezone
-
-from misago.threads.permissions import exclude_invisible_threads
-
-from misago.readtracker import signals
-from misago.readtracker.dates import is_date_tracked
-from misago.readtracker.models import ForumRead
-
-
-__all__ = ['make_read_aware', 'sync_record']
-
-
-def make_read_aware(user, forums):
-    if not hasattr(forums, '__iter__'):
-        forums = [forums]
-
-    if user.is_anonymous():
-        make_read(forums)
-        return None
-
-    forums_dict = {}
-    for forum in forums:
-        forum.last_read_on = user.reads_cutoff
-        forum.is_read = not is_date_tracked(forum.last_post_on, user)
-        if not forum.is_read:
-            forums_dict[forum.pk] = forum
-
-    if forums_dict:
-        for record in user.forumread_set.filter(forum__in=forums_dict.keys()):
-            forum = forums_dict[record.forum_id]
-            forum.last_read_on = record.last_read_on
-            forum.is_read = forum.last_read_on >= forum.last_post_on
-
-
-def make_read(forums):
-    now = timezone.now()
-    for forum in forums:
-        forum.last_read_on = now
-        forum.is_read = True
-
-
-def start_record(user, forum):
-    user.forumread_set.create(forum=forum, last_read_on=user.reads_cutoff)
-
-
-def sync_record(user, forum):
-    cutoff_date = user.reads_cutoff
-
-    try:
-        forum_record = user.forumread_set.get(forum=forum)
-        if forum_record.last_read_on > cutoff_date:
-            cutoff_date = forum_record.last_read_on
-    except ForumRead.DoesNotExist:
-        forum_record = None
-
-    recorded_threads = forum.thread_set.filter(last_post_on__gt=cutoff_date)
-    recorded_threads = exclude_invisible_threads(recorded_threads, user, forum)
-
-    all_threads_count = recorded_threads.count()
-
-    read_threads = user.threadread_set.filter(
-        forum=forum, last_read_on__gt=cutoff_date)
-    read_threads_count = read_threads.filter(
-        thread__last_post_on__lte=F("last_read_on")).count()
-
-    forum_is_read = read_threads_count == all_threads_count
-
-    if forum_is_read:
-        signals.forum_read.send(sender=user, forum=forum)
-
-    if forum_record:
-        if forum_is_read:
-            forum_record.last_read_on = forum_record.last_read_on
-        else:
-            forum_record.last_read_on = cutoff_date
-        forum_record.save(update_fields=['last_read_on'])
-    else:
-        if forum_is_read:
-            last_read_on = timezone.now()
-        else:
-            last_read_on = cutoff_date
-
-        forum_record = user.forumread_set.create(
-            forum=forum,
-            last_read_on=last_read_on)
-
-
-def read_forum(user, forum):
-    try:
-        forum_record = user.forumread_set.get(forum=forum)
-        forum_record.last_read_on = timezone.now()
-        forum_record.save(update_fields=['last_read_on'])
-    except ForumRead.DoesNotExist:
-        user.forumread_set.create(forum=forum, last_read_on=timezone.now())
-    signals.forum_read.send(sender=user, forum=forum)
-

+ 3 - 3
misago/readtracker/migrations/0001_initial.py

@@ -14,11 +14,11 @@ class Migration(migrations.Migration):
 
     operations = [
         migrations.CreateModel(
-            name='ForumRead',
+            name='CategoryRead',
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('last_read_on', models.DateTimeField()),
-                ('forum', models.ForeignKey(to='misago_forums.Forum')),
+                ('category', models.ForeignKey(to='misago_categories.Category')),
                 ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
             ],
             options={
@@ -31,7 +31,7 @@ class Migration(migrations.Migration):
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('read_replies', models.PositiveIntegerField(default=0)),
                 ('last_read_on', models.DateTimeField()),
-                ('forum', models.ForeignKey(to='misago_forums.Forum')),
+                ('category', models.ForeignKey(to='misago_categories.Category')),
                 ('thread', models.ForeignKey(to='misago_threads.Thread')),
                 ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
             ],

+ 3 - 3
misago/readtracker/models.py

@@ -5,15 +5,15 @@ from django.db import models
 from django.utils import timezone
 
 
-class ForumRead(models.Model):
+class CategoryRead(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
-    forum = models.ForeignKey('misago_forums.Forum')
+    category = models.ForeignKey('misago_categories.Category')
     last_read_on = models.DateTimeField()
 
 
 class ThreadRead(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
-    forum = models.ForeignKey('misago_forums.Forum')
+    category = models.ForeignKey('misago_categories.Category')
     thread = models.ForeignKey('misago_threads.Thread')
     read_replies =  models.PositiveIntegerField(default=0)
     last_read_on = models.DateTimeField()

+ 5 - 5
misago/readtracker/signals.py

@@ -1,11 +1,11 @@
 from django.dispatch import receiver, Signal
 
-from misago.forums.signals import move_forum_content
+from misago.categorues.signals import move_category_content
 from misago.threads.signals import move_thread, remove_thread_participant
 
 
 all_read = Signal()
-forum_read = Signal(providing_args=["forum"])
+category_read = Signal(providing_args=["category"])
 thread_tracked = Signal(providing_args=["thread"])
 thread_read = Signal(providing_args=["thread"])
 
@@ -13,9 +13,9 @@ thread_read = Signal(providing_args=["thread"])
 """
 Signal handlers
 """
-@receiver(move_forum_content)
-def delete_forum_tracker(sender, **kwargs):
-    sender.forumread_set.all().delete()
+@receiver(move_category_content)
+def delete_category_tracker(sender, **kwargs):
+    sender.categoryread_set.all().delete()
     sender.threadread_set.all().delete()
 
 

+ 8 - 6
misago/readtracker/tests/test_dates.py

@@ -22,13 +22,15 @@ class ReadTrackerDatesTests(TestCase):
         future_date = timezone.now() + timedelta(minutes=10)
         self.assertTrue(is_date_tracked(future_date, MockUser()))
 
-    def test_is_date_tracked_with_forum_cutoff(self):
-        """is_date_tracked validates dates using forum cutoff"""
+    def test_is_date_tracked_with_category_cutoff(self):
+        """is_date_tracked validates dates using category cutoff"""
         self.assertFalse(is_date_tracked(None, MockUser()))
         past_date = timezone.now() + timedelta(minutes=10)
 
-        forum_cutoff = timezone.now() + timedelta(minutes=20)
-        self.assertFalse(is_date_tracked(past_date, MockUser(), forum_cutoff))
+        category_cutoff = timezone.now() + timedelta(minutes=20)
+        self.assertFalse(
+            is_date_tracked(past_date, MockUser(), category_cutoff))
 
-        forum_cutoff = timezone.now() - timedelta(minutes=20)
-        self.assertTrue(is_date_tracked(past_date, MockUser(), forum_cutoff))
+        category_cutoff = timezone.now() - timedelta(minutes=20)
+        self.assertTrue(
+            is_date_tracked(past_date, MockUser(), category_cutoff))

+ 92 - 89
misago/readtracker/tests/test_readtracker.py

@@ -5,149 +5,152 @@ from django.test import TestCase
 from django.utils import timezone
 
 from misago.acl import add_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.threads import testutils
 from misago.users.models import AnonymousUser
 
-from misago.readtracker import forumstracker, threadstracker
+from misago.readtracker import categoriestracker, threadstracker
 
 
 class ReadTrackerTests(TestCase):
     def setUp(self):
-        self.forums = [f for f in Forum.objects.filter(role="forum")[:1]]
-        self.forum = self.forums[0]
+        self.categories = [f for f in Category.objects.filter(role='forum')[:1]]
+        self.category = self.categories[0]
 
         User = get_user_model()
         self.user = User.objects.create_user("Bob", "bob@test.com", "Pass.123")
         self.anon = AnonymousUser()
 
     def post_thread(self, datetime):
-        return testutils.post_thread(forum=self.forum, started_on=datetime)
+        return testutils.post_thread(
+            category=self.category,
+            started_on=datetime
+        )
 
 
-class ForumsTrackerTests(ReadTrackerTests):
-    def test_anon_empty_forum_read(self):
+class CategorysTrackerTests(ReadTrackerTests):
+    def test_anon_empty_category_read(self):
         """anon users content is always read"""
-        forumstracker.make_read_aware(self.anon, self.forums)
-        self.assertIsNone(self.forum.last_post_on)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.anon, self.categories)
+        self.assertIsNone(self.category.last_post_on)
+        self.assertTrue(self.category.is_read)
 
-    def test_anon_forum_with_recent_reply_read(self):
+    def test_anon_category_with_recent_reply_read(self):
         """anon users content is always read"""
-        forumstracker.make_read_aware(self.anon, self.forums)
-        self.forum.last_post_on = timezone.now()
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.anon, self.categories)
+        self.category.last_post_on = timezone.now()
+        self.assertTrue(self.category.is_read)
 
-    def test_empty_forum_is_read(self):
-        """empty forum is read for signed in user"""
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+    def test_empty_category_is_read(self):
+        """empty category is read for signed in user"""
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
-    def test_make_read_aware_sets_read_flag_for_empty_forum(self):
-        """make_read_aware sets read flag on empty forum"""
-        forumstracker.make_read_aware(self.anon, self.forums)
-        self.assertTrue(self.forum.is_read)
+    def test_make_read_aware_sets_read_flag_for_empty_category(self):
+        """make_read_aware sets read flag on empty category"""
+        categoriestracker.make_read_aware(self.anon, self.categories)
+        self.assertTrue(self.category.is_read)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
-    def test_make_read_aware_sets_read_flag_for_forum_with_old_thread(self):
-        """make_read_aware sets read flag on forum with old thread"""
-        self.forum.last_post_on = self.user.reads_cutoff - timedelta(days=1)
+    def test_make_read_aware_sets_read_flag_for_category_with_old_thread(self):
+        """make_read_aware sets read flag on category with old thread"""
+        self.category.last_post_on = self.user.reads_cutoff - timedelta(days=1)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
-    def test_make_read_aware_sets_unread_flag_for_forum_with_new_thread(self):
-        """make_read_aware sets unread flag on forum with new thread"""
-        self.forum.last_post_on = self.user.reads_cutoff + timedelta(days=1)
+    def test_make_read_aware_sets_unread_flag_for_category_with_new_thread(self):
+        """make_read_aware sets unread flag on category with new thread"""
+        self.category.last_post_on = self.user.reads_cutoff + timedelta(days=1)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
-    def test_sync_record_for_empty_forum(self):
-        """sync_record sets read flag on empty forum"""
-        add_acl(self.user, self.forums)
-        forumstracker.sync_record(self.user, self.forum)
-        self.user.forumread_set.get(forum=self.forum)
+    def test_sync_record_for_empty_category(self):
+        """sync_record sets read flag on empty category"""
+        add_acl(self.user, self.categories)
+        categoriestracker.sync_record(self.user, self.category)
+        self.user.categoryread_set.get(category=self.category)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
-    def test_sync_record_for_forum_with_old_thread_and_reply(self):
+    def test_sync_record_for_category_with_old_thread_and_reply(self):
         """
-        sync_record sets read flag on forum with old thread,
+        sync_record sets read flag on category with old thread,
         then changes flag to unread when new reply is posted
         """
         self.post_thread(self.user.reads_cutoff - timedelta(days=1))
 
-        add_acl(self.user, self.forums)
-        forumstracker.sync_record(self.user, self.forum)
-        self.user.forumread_set.get(forum=self.forum)
+        add_acl(self.user, self.categories)
+        categoriestracker.sync_record(self.user, self.category)
+        self.user.categoryread_set.get(category=self.category)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
         thread = self.post_thread(self.user.reads_cutoff + timedelta(days=1))
-        forumstracker.sync_record(self.user, self.forum)
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.sync_record(self.user, self.category)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
-    def test_sync_record_for_forum_with_new_thread(self):
+    def test_sync_record_for_category_with_new_thread(self):
         """
-        sync_record sets read flag on forum with old thread,
+        sync_record sets read flag on category with old thread,
         then keeps flag to unread when new reply is posted
         """
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
 
-        add_acl(self.user, self.forums)
-        forumstracker.sync_record(self.user, self.forum)
-        self.user.forumread_set.get(forum=self.forum)
+        add_acl(self.user, self.categories)
+        categoriestracker.sync_record(self.user, self.category)
+        self.user.categoryread_set.get(category=self.category)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
-        forumstracker.sync_record(self.user, self.forum)
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.sync_record(self.user, self.category)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
-    def test_sync_record_for_forum_with_deleted_threads(self):
-        """unread forum reverts to read after its emptied"""
+    def test_sync_record_for_category_with_deleted_threads(self):
+        """unread category reverts to read after its emptied"""
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
 
-        add_acl(self.user, self.forums)
-        forumstracker.sync_record(self.user, self.forum)
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        add_acl(self.user, self.categories)
+        categoriestracker.sync_record(self.user, self.category)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
-        self.forum.thread_set.all().delete()
-        self.forum.synchronize()
-        self.forum.save()
+        self.category.thread_set.all().delete()
+        self.category.synchronize()
+        self.category.save()
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
-    def test_sync_record_for_forum_with_many_threads(self):
-        """sync_record sets unread flag on forum with many threads"""
+    def test_sync_record_for_category_with_many_threads(self):
+        """sync_record sets unread flag on category with many threads"""
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
         self.post_thread(self.user.reads_cutoff - timedelta(days=1))
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
         self.post_thread(self.user.reads_cutoff - timedelta(days=1))
 
-        add_acl(self.user, self.forums)
-        forumstracker.sync_record(self.user, self.forum)
-        self.user.forumread_set.get(forum=self.forum)
+        add_acl(self.user, self.categories)
+        categoriestracker.sync_record(self.user, self.category)
+        self.user.categoryread_set.get(category=self.category)
 
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
         self.post_thread(self.user.reads_cutoff + timedelta(days=1))
-        forumstracker.sync_record(self.user, self.forum)
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.sync_record(self.user, self.category)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
 
 class ThreadsTrackerTests(ReadTrackerTests):
@@ -189,26 +192,26 @@ class ThreadsTrackerTests(ReadTrackerTests):
         """thread read flag is set for user, then its set as unread by reply"""
         self.reply_thread(self.thread)
 
-        add_acl(self.user, self.forums)
+        add_acl(self.user, self.categories)
         threadstracker.make_read_aware(self.user, self.thread)
         self.assertFalse(self.thread.is_read)
 
         threadstracker.read_thread(self.user, self.thread, self.post)
         threadstracker.make_read_aware(self.user, self.thread)
         self.assertTrue(self.thread.is_read)
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertTrue(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertTrue(self.category.is_read)
 
         self.thread.last_post_on = timezone.now()
         self.thread.save()
-        self.forum.synchronize()
-        self.forum.save()
+        self.category.synchronize()
+        self.category.save()
 
         self.reply_thread()
         threadstracker.make_read_aware(self.user, self.thread)
         self.assertFalse(self.thread.is_read)
-        forumstracker.make_read_aware(self.user, self.forums)
-        self.assertFalse(self.forum.is_read)
+        categoriestracker.make_read_aware(self.user, self.categories)
+        self.assertFalse(self.category.is_read)
 
         posts = [post for post in self.thread.post_set.order_by('id')]
         threadstracker.make_posts_read_aware(self.user, self.thread, posts)

+ 25 - 23
misago/readtracker/tests/test_views.py

@@ -2,49 +2,51 @@ from django.contrib.auth import get_user_model
 from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext as _
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.threads import testutils
 
-from misago.readtracker.forumstracker import make_read_aware
+from misago.readtracker.categoriestracker import make_read_aware
 
 
 class AuthenticatedTests(AuthenticatedUserTestCase):
     def test_read_all_threads(self):
         """read_all view updates reads cutoff on user model"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        threads = [testutils.post_thread(forum) for t in xrange(10)]
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        threads = [testutils.post_thread(category) for t in xrange(10)]
 
-        forum = Forum.objects.get(id=forum.id)
-        make_read_aware(self.user, [forum])
-        self.assertFalse(forum.is_read)
+        category = Category.objects.get(id=category.id)
+        make_read_aware(self.user, [category])
+        self.assertFalse(category.is_read)
 
         response = self.client.post(reverse('misago:read_all'))
         self.assertEqual(response.status_code, 302)
 
-        forum = Forum.objects.get(id=forum.id)
+        category = Category.objects.get(id=category.id)
         user = get_user_model().objects.get(id=self.user.id)
 
-        make_read_aware(user, [forum])
-        self.assertTrue(forum.is_read)
+        make_read_aware(user, [category])
+        self.assertTrue(category.is_read)
 
-    def test_read_forum(self):
-        """read_forum view updates reads cutoff on forum tracker"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        threads = [testutils.post_thread(forum) for t in xrange(10)]
+    def test_read_category(self):
+        """read_category view updates reads cutoff on category tracker"""
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        threads = [testutils.post_thread(category) for t in xrange(10)]
 
-        forum = Forum.objects.get(id=forum.id)
-        make_read_aware(self.user, [forum])
-        self.assertFalse(forum.is_read)
+        category = Category.objects.get(id=category.id)
+        make_read_aware(self.user, [category])
+        self.assertFalse(category.is_read)
+
+        response = self.client.post(everse('misago:read_category', kwargs={
+            'category_id': category.id
+        }))
 
-        response = self.client.post(
-            reverse('misago:read_forum', kwargs={'forum_id': forum.id}))
         self.assertEqual(response.status_code, 302)
         self.assertTrue(
-            response['location'].endswith(forum.get_absolute_url()))
+            response['location'].endswith(category.get_absolute_url()))
 
-        forum = Forum.objects.get(id=forum.id)
+        category = Category.objects.get(id=category.id)
         user = get_user_model().objects.get(id=self.user.id)
 
-        make_read_aware(user, [forum])
-        self.assertTrue(forum.is_read)
+        make_read_aware(user, [category])
+        self.assertTrue(category.is_read)

+ 30 - 26
misago/readtracker/threadstracker.py

@@ -3,9 +3,9 @@ from django.utils import timezone
 
 from misago.notifications import read_user_notifications
 
-from misago.readtracker import forumstracker, signals
+from misago.readtracker import categoriestracker, signals
 from misago.readtracker.dates import is_date_tracked
-from misago.readtracker.models import ForumRead, ThreadRead
+from misago.readtracker.models import CategoyRead, ThreadRead
 
 
 __all__ = ['make_read_aware', 'read_thread']
@@ -18,7 +18,7 @@ def make_read_aware(user, target):
         make_thread_read_aware(user, target)
 
 
-def make_threads_read_aware(user, threads, forum=None):
+def make_threads_read_aware(user, threads, category=None):
     if not threads:
         return None
 
@@ -26,10 +26,10 @@ def make_threads_read_aware(user, threads, forum=None):
         make_read(threads)
         return None
 
-    if forum:
-        make_forum_threads_read_aware(user, forum, threads)
+    if category:
+        make_category_threads_read_aware(user, category, threads)
     else:
-        make_forums_threads_read_aware(user, threads)
+        make_categories_threads_read_aware(user, threads)
 
 
 def make_read(threads):
@@ -39,14 +39,14 @@ def make_read(threads):
         thread.is_new = False
 
 
-def make_forum_threads_read_aware(user, forum, threads):
-    if forum.is_read:
+def make_category_threads_read_aware(user, category, threads):
+    if category.is_read:
         make_read(threads)
     else:
         threads_dict = {}
         for thread in threads:
             thread.is_read = not is_date_tracked(
-                thread.last_post_on, user, forum.last_read_on)
+                thread.last_post_on, user, category.last_read_on)
             thread.is_new = True
             if thread.is_read:
                 thread.unread_replies = 0
@@ -58,14 +58,16 @@ def make_forum_threads_read_aware(user, forum, threads):
             make_threads_dict_read_aware(user, threads_dict)
 
 
-def make_forums_threads_read_aware(user, threads):
-    forums_cutoffs = fetch_forums_cutoffs_for_threads(user, threads)
+def make_categories_threads_read_aware(user, threads):
+    categories_cutoffs = fetch_categories_cutoffs_for_threads(user, threads)
 
     threads_dict = {}
     for thread in threads:
+        category_cutoff = categories_cutoffs.get(thread.category_id)
         thread.is_read = not is_date_tracked(
-            thread.last_post_on, user, forums_cutoffs.get(thread.forum_id))
+            thread.last_post_on, user, category_cutoff)
         thread.is_new = True
+
         if thread.is_read:
             thread.unread_replies = 0
         else:
@@ -76,16 +78,16 @@ def make_forums_threads_read_aware(user, threads):
         make_threads_dict_read_aware(user, threads_dict)
 
 
-def fetch_forums_cutoffs_for_threads(user, threads):
-    forums = []
+def fetch_categories_cutoffs_for_threads(user, threads):
+    categories = []
     for thread in threads:
-        if thread.forum_id not in forums:
-            forums.append(thread.forum_id)
+        if thread.category_id not in categories:
+            categories.append(thread.category_id)
 
-    forums_dict = {}
-    for record in user.forumread_set.filter(forum__in=forums):
-        forums_dict[record.forum_id] = record.last_read_on
-    return forums_dict
+    categories_dict = {}
+    for record in user.categoriesread_set.filter(category__in=categories):
+        categories_dict[record.category_id] = record.last_read_on
+    return categories_dict
 
 
 def make_threads_dict_read_aware(user, threads_dict):
@@ -117,8 +119,10 @@ def make_thread_read_aware(user, thread):
         thread.is_new = True
 
         try:
-            forum_record = user.forumread_set.get(forum_id=thread.forum_id)
-            if thread.last_post_on > forum_record.last_read_on:
+            category_record = user.categoriesread_set.get(
+                category_id=thread.category_id)
+
+            if thread.last_post_on > category_record.last_read_on:
                 try:
                     thread_record = user.threadread_set.get(thread=thread)
                     thread.last_read_on = thread_record.last_read_on
@@ -131,8 +135,8 @@ def make_thread_read_aware(user, thread):
             else:
                 thread.is_read = True
                 thread.is_new = False
-        except ForumRead.DoesNotExist:
-            forumstracker.start_record(user, thread.forum)
+        except CategoyRead.DoesNotExist:
+            categoriestracker.start_record(user, thread.category)
 
 
 def make_posts_read_aware(user, thread, posts):
@@ -170,7 +174,7 @@ def sync_record(user, thread, last_read_reply):
         thread.read_record.save(update_fields=['read_replies', 'last_read_on'])
     else:
         user.threadread_set.create(
-            forum=thread.forum,
+            category=thread.category,
             thread=thread,
             read_replies=read_replies,
             last_read_on=last_read_reply.posted_on)
@@ -179,7 +183,7 @@ def sync_record(user, thread, last_read_reply):
 
     if last_read_reply.posted_on == thread.last_post_on:
         signals.thread_read.send(sender=user, thread=thread)
-        forumstracker.sync_record(user, thread.forum)
+        categoriestracker.sync_record(user, thread.category)
 
     read_user_notifications(user, notification_triggers, False)
 

+ 1 - 1
misago/readtracker/urls.py

@@ -3,5 +3,5 @@ from django.conf.urls import include, patterns, url
 
 urlpatterns = patterns('misago.readtracker.views',
     url(r'^read-all/$', 'read_all', name='read_all'),
-    url(r'^read-forum/(?P<forum_id>\d+)/$', 'read_forum', name='read_forum'),
+    url(r'^read-category/(?P<category_id>\d+)/$', 'read_category', name='read_category'),
 )

+ 7 - 7
misago/readtracker/views.py

@@ -6,11 +6,11 @@ from django.utils.translation import ugettext as _
 from django.views.decorators.cache import never_cache
 from django.views.decorators.csrf import csrf_protect
 
+from misago.categories.models import Category
 from misago.core.decorators import require_POST
-from misago.forums.models import Forum
 from misago.users.decorators import deny_guests
 
-from misago.readtracker import forumstracker
+from misago.readtracker import categoriestracker
 from misago.readtracker.signals import all_read
 
 
@@ -32,14 +32,14 @@ def read_all(request):
 
     all_read.send(sender=request.user)
 
-    messages.info(request, _("All forums and threads were marked as read."))
+    messages.info(request, _("All categories and threads were marked as read."))
     return redirect('misago:index')
 
 
 @read_view
-def read_forum(request, forum_id):
-    forum = get_object_or_404(Forum.objects, id=forum_id)
-    forumstracker.read_forum(request.user, forum)
+def read_category(request, category_id):
+    category = get_object_or_404(Category.objects, id=category_id)
+    categoriestracker.read_category(request.user, category)
 
     messages.info(request, _("Threads were marked as read."))
-    return redirect(forum.get_absolute_url())
+    return redirect(category.get_absolute_url())

+ 6 - 7
misago/threads/admin.py

@@ -1,6 +1,5 @@
 from django.conf.urls import url
 from django.utils.translation import ugettext_lazy as _
-
 from misago.threads.views.labelsadmin import (LabelsList, NewLabel,
                                               EditLabel, DeleteLabel)
 
@@ -8,8 +7,8 @@ from misago.threads.views.labelsadmin import (LabelsList, NewLabel,
 class MisagoAdminExtension(object):
     def register_urlpatterns(self, urlpatterns):
         # Threads Labels
-        urlpatterns.namespace(r'^labels/', 'labels', 'forums')
-        urlpatterns.patterns('forums:labels',
+        urlpatterns.namespace(r'^labels/', 'labels', 'categories')
+        urlpatterns.patterns('categories:labels',
             url(r'^$', LabelsList.as_view(), name='index'),
             url(r'^new/$', NewLabel.as_view(), name='new'),
             url(r'^edit/(?P<label_id>\d+)/$', EditLabel.as_view(), name='edit'),
@@ -19,7 +18,7 @@ class MisagoAdminExtension(object):
     def register_navigation_nodes(self, site):
         site.add_node(name=_("Thread labels"),
                       icon='fa fa-tags',
-                      parent='misago:admin:forums',
-                      after='misago:admin:forums:nodes:index',
-                      namespace='misago:admin:forums:labels',
-                      link='misago:admin:forums:labels:index')
+                      parent='misago:admin:categories',
+                      after='misago:admin:categories:nodes:index',
+                      namespace='misago:admin:categories:labels',
+                      link='misago:admin:categories:labels:index')

+ 3 - 2
misago/threads/counts.py

@@ -4,7 +4,7 @@ from django.conf import settings
 from django.db.models import F
 from django.dispatch import receiver
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.views.moderatedcontent import ModeratedContent
 from misago.threads.views.newthreads import NewThreads
@@ -98,7 +98,8 @@ def sync_user_unread_private_threads_count(user):
 
     all_threads_count = threads_qs.count()
 
-    read_qs = user.threadread_set.filter(forum=Forum.objects.private_threads())
+    read_qs = user.threadread_set.filter(
+        category=Category.objects.private_threads())
     read_qs = read_qs.filter(last_read_on__gte=F('thread__last_post_on'))
     read_threads_count = read_qs.count()
 

+ 2 - 2
misago/threads/events.py

@@ -41,7 +41,7 @@ def format_message(message, links):
 
 def record_event(user, thread, icon, message, links=None):
     event = Event.objects.create(
-        forum=thread.forum,
+        category=thread.category,
         thread=thread,
         author=user,
         author_name=user.username,
@@ -71,7 +71,7 @@ def real_add_events_to_posts(user, thread, posts, delimeter=None):
         events_queryset = events_queryset.filter(occured_on__lt=delimeter)
     events_queryset = events_queryset.order_by('id')
 
-    acl = user.acl['forums'].get(thread.forum_id, {})
+    acl = user.acl['categories'].get(thread.category_id, {})
     if not acl.get('can_hide_events'):
         events_queryset = events_queryset.filter(is_hidden=False)
 

+ 5 - 5
misago/threads/forms/admin.py

@@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy as _
 
 from misago.core import forms
 from misago.core.validators import validate_sluggable
-from misago.forums.forms import AdminForumMultipleChoiceField
+from misago.categories.forms import AdminCategoryMultipleChoiceField
 
 from misago.threads.models import Label
 
@@ -13,14 +13,14 @@ class LabelForm(forms.ModelForm):
     css_class = forms.CharField(
         label=_("CSS class"), required=False,
         help_text=_("Optional CSS clas used to style this label."))
-    forums = AdminForumMultipleChoiceField(
-        label=_('Forums'), required=False, include_root=False,
+    categories = AdminCategoryMultipleChoiceField(
+        label=_('Categories'), required=False, include_root=False,
         widget=forms.CheckboxSelectMultiple(),
-        help_text=_('Select forums this label will be available in.'))
+        help_text=_('Select categories this label will be available in.'))
 
     class Meta:
         model = Label
-        fields = ['name', 'css_class', 'forums']
+        fields = ['name', 'css_class', 'categories']
 
     def clean_name(self):
         data = self.cleaned_data['name']

+ 28 - 27
misago/threads/forms/moderation.py

@@ -5,9 +5,10 @@ from django.http import Http404
 from django.utils.translation import ugettext_lazy as _
 
 from misago.acl import add_acl
+from misago.categories.forms import CategoryChoiceField
+from misago.categories.permissions import (allow_see_category,
+                                           allow_browse_category)
 from misago.core import forms
-from misago.forums.forms import ForumChoiceField
-from misago.forums.permissions import allow_see_forum, allow_browse_forum
 
 from misago.threads.models import Thread
 from misago.threads.permissions import allow_see_thread
@@ -31,39 +32,39 @@ class MergeThreadsForm(forms.Form):
 
 
 class MoveThreadsForm(forms.Form):
-    new_forum = ForumChoiceField(label=_("Move threads to forum"),
-                                 empty_label=None)
+    new_category = CategoryChoiceField(label=_("Move threads to category"),
+                                       empty_label=None)
 
     def __init__(self, *args, **kwargs):
-        self.forum = kwargs.pop('forum')
+        self.category = kwargs.pop('category')
         acl = kwargs.pop('acl')
 
         super(MoveThreadsForm, self).__init__(*args, **kwargs)
 
-        self.fields['new_forum'].set_acl(acl)
+        self.fields['new_category'].set_acl(acl)
 
     def clean(self):
         data = super(MoveThreadsForm, self).clean()
 
-        new_forum = data.get('new_forum')
-        if new_forum:
-            if new_forum.is_category:
+        new_category = data.get('new_category')
+        if new_category:
+            if new_category.is_category:
                 message = _("You can't move threads to category.")
                 raise forms.ValidationError(message)
-            if new_forum.is_redirect:
+            if new_category.is_redirect:
                 message = _("You can't move threads to redirect.")
                 raise forms.ValidationError(message)
-            if new_forum.pk == self.forum.pk:
-                message = _("New forum is same as current one.")
+            if new_category.pk == self.category.pk:
+                message = _("New category is same as current one.")
                 raise forms.ValidationError(message)
         else:
-            raise forms.ValidationError(_("You have to select forum."))
+            raise forms.ValidationError(_("You have to select category."))
         return data
 
 
 class MoveThreadForm(MoveThreadsForm):
-    new_forum = ForumChoiceField(label=_("Move thread to forum"),
-                                 empty_label=None)
+    new_category = CategoryChoiceField(label=_("Move thread to category"),
+                                       empty_label=None)
 
 
 class MovePostsForm(forms.Form):
@@ -90,14 +91,14 @@ class MovePostsForm(forms.Form):
             if not 'thread_id' in resolution.kwargs:
                 raise Http404()
 
-            queryset = Thread.objects.select_related('forum')
+            queryset = Thread.objects.select_related('category')
             self.new_thread = queryset.get(id=resolution.kwargs['thread_id'])
 
-            add_acl(self.user, self.new_thread.forum)
+            add_acl(self.user, self.new_thread.category)
             add_acl(self.user, self.new_thread)
 
-            allow_see_forum(self.user, self.new_thread.forum)
-            allow_browse_forum(self.user, self.new_thread.forum)
+            allow_see_category(self.user, self.new_thread.category)
+            allow_browse_category(self.user, self.new_thread.category)
             allow_see_thread(self.user, self.new_thread)
 
         except (Http404, Thread.DoesNotExist):
@@ -108,7 +109,7 @@ class MovePostsForm(forms.Form):
             message = _("New thread is same as current one.")
             raise forms.ValidationError(message)
 
-        if self.new_thread.forum.special_role:
+        if self.new_thread.category.special_role:
             message = _("You can't move posts to special threads.")
             raise forms.ValidationError(message)
 
@@ -116,7 +117,7 @@ class MovePostsForm(forms.Form):
 
 
 class SplitThreadForm(forms.Form):
-    forum = ForumChoiceField(label=_("New thread forum"),
+    category = CategoryChoiceField(label=_("New thread category"),
                                  empty_label=None)
 
     thread_title = forms.CharField(label=_("New thread title"),
@@ -127,21 +128,21 @@ class SplitThreadForm(forms.Form):
 
         super(SplitThreadForm, self).__init__(*args, **kwargs)
 
-        self.fields['forum'].set_acl(acl)
+        self.fields['category'].set_acl(acl)
 
     def clean(self):
         data = super(SplitThreadForm, self).clean()
 
-        forum = data.get('forum')
-        if forum:
-            if forum.is_category:
+        category = data.get('category')
+        if category:
+            if category.is_category:
                 message = _("You can't start threads in category.")
                 raise forms.ValidationError(message)
-            if forum.is_redirect:
+            if category.is_redirect:
                 message = _("You can't start threads in redirect.")
                 raise forms.ValidationError(message)
         else:
-            raise forms.ValidationError(_("You have to select forum."))
+            raise forms.ValidationError(_("You have to select category."))
 
         thread_title = data.get('thread_title')
         if thread_title:

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

@@ -12,7 +12,7 @@ from misago.core.pgutils import CreatePartialIndex, CreatePartialCompositeIndex
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('misago_forums', '0001_initial'),
+        ('misago_categories', '0001_initial'),
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
@@ -24,7 +24,7 @@ class Migration(migrations.Migration):
                 ('name', models.CharField(max_length=255)),
                 ('slug', models.SlugField(max_length=255)),
                 ('css_class', models.CharField(max_length=255, null=True, blank=True)),
-                ('forums', models.ManyToManyField(to='misago_forums.Forum')),
+                ('categories', models.ManyToManyField(to='misago_categories.Category')),
             ],
             options={
             },
@@ -55,7 +55,7 @@ class Migration(migrations.Migration):
                 ('is_moderated', models.BooleanField(default=False, db_index=True)),
                 ('is_hidden', models.BooleanField(default=False)),
                 ('is_protected', models.BooleanField(default=False)),
-                ('forum', models.ForeignKey(to='misago_forums.Forum')),
+                ('category', models.ForeignKey(to='misago_categories.Category')),
                 ('last_editor', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True)),
                 ('mentions', models.ManyToManyField(related_name='mention_set', to=settings.AUTH_USER_MODEL)),
                 ('poster', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True)),
@@ -132,7 +132,7 @@ class Migration(migrations.Migration):
                 ('checksum', models.CharField(max_length=64, default='-')),
                 ('is_hidden', models.BooleanField(default=False)),
                 ('author', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True)),
-                ('forum', models.ForeignKey(to='misago_forums.Forum')),
+                ('category', models.ForeignKey(to='misago_categories.Category')),
                 ('thread', models.ForeignKey(to='misago_threads.Thread')),
             ],
             options={
@@ -154,7 +154,7 @@ class Migration(migrations.Migration):
                 ('closed_by_slug', models.CharField(max_length=255)),
                 ('closed_by', models.ForeignKey(related_name='closedreport_set', on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True)),
                 ('closed_on', models.DateTimeField(default=django.utils.timezone.now)),
-                ('forum', models.ForeignKey(to='misago_forums.Forum')),
+                ('category', models.ForeignKey(to='misago_categories.Category')),
                 ('post', models.ForeignKey(to='misago_threads.Post')),
                 ('reported_by', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to=settings.AUTH_USER_MODEL, null=True)),
                 ('thread', models.ForeignKey(to='misago_threads.Thread')),
@@ -192,8 +192,8 @@ class Migration(migrations.Migration):
         ),
         migrations.AddField(
             model_name='thread',
-            name='forum',
-            field=models.ForeignKey(to='misago_forums.Forum'),
+            name='category',
+            field=models.ForeignKey(to='misago_categories.Category'),
             preserve_default=True,
         ),
         migrations.AddField(
@@ -223,9 +223,9 @@ class Migration(migrations.Migration):
         migrations.AlterIndexTogether(
             name='thread',
             index_together=set([
-                ('forum', 'id'),
-                ('forum', 'last_post_on'),
-                ('forum', 'replies'),
+                ('category', 'id'),
+                ('category', 'last_post_on'),
+                ('category', 'replies'),
             ]),
         ),
         CreatePartialCompositeIndex(

+ 2 - 2
misago/threads/models/event.py

@@ -8,7 +8,7 @@ from misago.threads.checksums import is_event_valid
 
 
 class Event(models.Model):
-    forum = models.ForeignKey('misago_forums.Forum')
+    category = models.ForeignKey('misago_categories.Category')
     thread = models.ForeignKey('Thread')
     author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
                                on_delete=models.SET_NULL)
@@ -31,7 +31,7 @@ class Event(models.Model):
 
     @property
     def thread_type(self):
-        return self.forum.thread_type
+        return self.category.thread_type
 
     def get_edit_url(self):
         return self.thread_type.get_event_edit_url(self)

+ 7 - 7
misago/threads/models/label.py

@@ -8,10 +8,10 @@ CACHE_NAME = 'misago_threads_labels'
 
 
 class LabelManager(models.Manager):
-    def get_forum_labels(self, forum):
+    def get_category_labels(self, category):
         labels = []
         for label in self.get_cached_labels():
-            if forum.pk in label.forums_ids:
+            if category.pk in label.categories_ids:
                 labels.append(label)
         return labels
 
@@ -22,9 +22,9 @@ class LabelManager(models.Manager):
         labels = cache.get(CACHE_NAME, 'nada')
         if labels == 'nada':
             labels = []
-            labels_qs = self.all().prefetch_related('forums')
+            labels_qs = self.all().prefetch_related('categories')
             for label in labels_qs.order_by('name'):
-                label.forums_ids = [f.pk for f in label.forums.all()]
+                label.categories_ids = [f.pk for f in label.categories.all()]
                 labels.append(label)
             cache.set(CACHE_NAME, labels)
         return labels
@@ -34,7 +34,7 @@ class LabelManager(models.Manager):
 
 
 class Label(models.Model):
-    forums = models.ManyToManyField('misago_forums.Forum')
+    categories = models.ManyToManyField('misago_categories.Category')
     name = models.CharField(max_length=255)
     slug = models.SlugField(max_length=255)
     css_class = models.CharField(max_length=255, null=True, blank=True)
@@ -56,8 +56,8 @@ class Label(models.Model):
 
     def strip_inavailable_labels(self):
         qs = self.thread_set
-        if self.forums:
-            qs = qs.exclude(forum__in=self.forums.all())
+        if self.categories:
+            qs = qs.exclude(category__in=self.categories.all())
         qs.update(label=None)
 
     def set_name(self, name):

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

@@ -10,7 +10,7 @@ from misago.threads.checksums import update_post_checksum, is_post_valid
 
 
 class Post(models.Model):
-    forum = models.ForeignKey('misago_forums.Forum')
+    category = models.ForeignKey('misago_categories.Category')
     thread = models.ForeignKey('misago_threads.Thread')
     poster = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
                                on_delete=models.SET_NULL)
@@ -82,13 +82,13 @@ class Post(models.Model):
     def move(self, new_thread):
         from misago.threads.signals import move_post
 
-        self.forum = new_thread.forum
+        self.category = new_thread.category
         self.thread = new_thread
         move_post.send(sender=self)
 
     @property
     def thread_type(self):
-        return self.forum.thread_type
+        return self.category.thread_type
 
     def get_absolute_url(self):
         return self.thread_type.get_post_absolute_url(self)

+ 1 - 1
misago/threads/models/report.py

@@ -5,7 +5,7 @@ from misago.conf import settings
 
 
 class Report(models.Model):
-    forum = models.ForeignKey('misago_forums.Forum')
+    category = models.ForeignKey('misago_categories.Category')
     thread = models.ForeignKey('misago_threads.Thread')
     post = models.ForeignKey('misago_threads.Post')
     reported_by = models.ForeignKey(settings.AUTH_USER_MODEL,

+ 7 - 7
misago/threads/models/thread.py

@@ -5,7 +5,7 @@ from misago.core.utils import slugify
 
 
 class Thread(models.Model):
-    forum = models.ForeignKey('misago_forums.Forum')
+    category = models.ForeignKey('misago_categories.Category')
     label = models.ForeignKey('misago_threads.Label',
                               null=True, blank=True,
                               on_delete=models.SET_NULL)
@@ -49,9 +49,9 @@ class Thread(models.Model):
 
     class Meta:
         index_together = [
-            ['forum', 'id'],
-            ['forum', 'last_post_on'],
-            ['forum', 'replies'],
+            ['category', 'id'],
+            ['category', 'last_post_on'],
+            ['category', 'replies'],
         ]
 
     def __unicode__(self):
@@ -73,10 +73,10 @@ class Thread(models.Model):
         from misago.threads.signals import merge_thread
         merge_thread.send(sender=self, other_thread=other_thread)
 
-    def move(self, new_forum):
+    def move(self, new_category):
         from misago.threads.signals import move_thread
 
-        self.forum = new_forum
+        self.category = new_category
         move_thread.send(sender=self)
 
     def synchronize(self):
@@ -116,7 +116,7 @@ class Thread(models.Model):
 
     @property
     def thread_type(self):
-        return self.forum.thread_type
+        return self.category.thread_type
 
     def get_absolute_url(self):
         return self.thread_type.get_thread_absolute_url(self)

+ 6 - 6
misago/threads/moderation/threads.py

@@ -68,16 +68,16 @@ def unpin_thread(user, thread):
 
 
 @atomic
-def move_thread(user, thread, new_forum):
-    if thread.forum_id != new_forum.pk:
-        message = _("%(user)s moved thread from %(forum)s.")
+def move_thread(user, thread, new_category):
+    if thread.category_id != new_category.pk:
+        message = _("%(user)s moved thread from %(category)s.")
         record_event(user, thread, "arrow-right", message, {
             'user': user,
-            'forum': thread.forum
+            'category': thread.category
         })
 
-        thread.move(new_forum)
-        thread.save(update_fields=['has_events', 'forum'])
+        thread.move(new_category)
+        thread.save(update_fields=['has_events', 'category'])
         return True
     else:
         return False

+ 6 - 6
misago/threads/permissions/privatethreads.py

@@ -7,8 +7,8 @@ from django.utils.translation import ugettext_lazy as _
 from misago.acl import add_acl, algebra
 from misago.acl.decorators import return_boolean
 from misago.acl.models import Role
+from misago.categories.models import Category
 from misago.core import forms
-from misago.forums.models import Forum
 
 
 __all__ = [
@@ -87,12 +87,12 @@ def build_acl(acl, roles, key_name):
     if not new_acl['can_use_private_threads']:
         return new_acl
 
-    private_forum = Forum.objects.private_threads()
+    private_category = Category.objects.private_threads()
 
     if new_acl['can_moderate_private_threads']:
-        new_acl['can_review_moderated_content'].append(private_forum.pk)
+        new_acl['can_review_moderated_content'].append(private_category.pk)
 
-    forum_acl = {
+    category_acl = {
         'can_see': 1,
         'can_browse': 1,
         'can_see_all_threads': 1,
@@ -117,7 +117,7 @@ def build_acl(acl, roles, key_name):
     }
 
     if new_acl['can_moderate_private_threads']:
-        forum_acl.update({
+        category_acl.update({
             'can_edit_threads': 2,
             'can_edit_posts': 2,
             'can_hide_threads': 2,
@@ -130,7 +130,7 @@ def build_acl(acl, roles, key_name):
             'can_hide_events': 2,
         })
 
-    new_acl['forums'][private_forum.pk] = forum_acl
+    new_acl['categories'][private_category.pk] = category_acl
 
     return new_acl
 

+ 153 - 150
misago/threads/permissions/threads.py

@@ -6,9 +6,9 @@ from django.utils.translation import ungettext, ugettext_lazy as _
 
 from misago.acl import add_acl, algebra
 from misago.acl.decorators import return_boolean
+from misago.categories.models import Category, RoleCategoryACL, CategoryRole
+from misago.categories.permissions import get_categories_roles
 from misago.core import forms
-from misago.forums.models import Forum, RoleForumACL, ForumRole
-from misago.forums.permissions import get_forums_roles
 
 from misago.threads.models import Thread, Post, Event
 
@@ -148,7 +148,7 @@ class PermissionsForm(forms.Form):
 
 
 def change_permissions_form(role):
-    if isinstance(role, ForumRole):
+    if isinstance(role, CategoryRole):
         return PermissionsForm
     else:
         return None
@@ -160,22 +160,22 @@ ACL Builder
 def build_acl(acl, roles, key_name):
     acl['can_review_moderated_content'] = []
     acl['can_see_reports'] = []
-    forums_roles = get_forums_roles(roles)
-
-    for forum in Forum.objects.all_forums():
-        forum_acl = acl['forums'].get(forum.pk, {'can_browse': 0})
-        if forum_acl['can_browse']:
-            acl['forums'][forum.pk] = build_forum_acl(
-                forum_acl, forum, forums_roles, key_name)
-            if acl['forums'][forum.pk]['can_review_moderated_content']:
-                acl['can_review_moderated_content'].append(forum.pk)
-            if acl['forums'][forum.pk]['can_see_reports']:
-                acl['can_see_reports'].append(forum.pk)
+    categories_roles = get_categories_roles(roles)
+
+    for category in Category.objects.all_categories():
+        category_acl = acl['categories'].get(category.pk, {'can_browse': 0})
+        if category_acl['can_browse']:
+            acl['categories'][category.pk] = build_category_acl(
+                category_acl, category, categories_roles, key_name)
+            if acl['categories'][category.pk]['can_review_moderated_content']:
+                acl['can_review_moderated_content'].append(category.pk)
+            if acl['categories'][category.pk]['can_see_reports']:
+                acl['can_see_reports'].append(category.pk)
     return acl
 
 
-def build_forum_acl(acl, forum, forums_roles, key_name):
-    forum_roles = forums_roles.get(forum.pk, [])
+def build_category_acl(acl, category, categories_roles, key_name):
+    category_roles = categories_roles.get(category.pk, [])
 
     final_acl = {
         'can_see_all_threads': 0,
@@ -205,7 +205,7 @@ def build_forum_acl(acl, forum, forums_roles, key_name):
     }
     final_acl.update(acl)
 
-    algebra.sum_acls(final_acl, roles=forum_roles, key=key_name,
+    algebra.sum_acls(final_acl, roles=category_roles, key=key_name,
         can_see_all_threads=algebra.greater,
         can_start_threads=algebra.greater,
         can_reply_threads=algebra.greater,
@@ -238,10 +238,10 @@ def build_forum_acl(acl, forum, forums_roles, key_name):
 """
 ACL's for targets
 """
-def add_acl_to_forum(user, forum):
-    forum_acl = user.acl['forums'].get(forum.pk, {})
+def add_acl_to_category(user, category):
+    category_acl = user.acl['categories'].get(category.pk, {})
 
-    forum.acl.update({
+    category.acl.update({
         'can_see_all_threads': 0,
         'can_start_threads': 0,
         'can_reply_threads': 0,
@@ -268,11 +268,11 @@ def add_acl_to_forum(user, forum):
         'can_hide_events': 0,
     })
 
-    algebra.sum_acls(forum.acl, acls=[forum_acl],
+    algebra.sum_acls(category.acl, acls=[category_acl],
         can_see_all_threads=algebra.greater)
 
     if user.is_authenticated():
-        algebra.sum_acls(forum.acl, acls=[forum_acl],
+        algebra.sum_acls(category.acl, acls=[category_acl],
             can_start_threads=algebra.greater,
             can_reply_threads=algebra.greater,
             can_edit_threads=algebra.greater,
@@ -298,55 +298,55 @@ def add_acl_to_forum(user, forum):
             can_hide_events=algebra.greater,
         )
 
-    forum.acl['can_see_own_threads'] = not forum.acl['can_see_all_threads']
+    category.acl['can_see_own_threads'] = not category.acl['can_see_all_threads']
 
 
 def add_acl_to_thread(user, thread):
-    forum_acl = user.acl['forums'].get(thread.forum_id, {})
+    category_acl = user.acl['categories'].get(thread.category_id, {})
 
     thread.acl.update({
         'can_reply': can_reply_thread(user, thread),
         'can_edit': can_edit_thread(user, thread),
-        'can_hide': forum_acl.get('can_hide_threads'),
-        'can_change_label': forum_acl.get('can_change_threads_labels') == 2,
-        'can_pin': forum_acl.get('can_pin_threads'),
-        'can_close': forum_acl.get('can_close_threads'),
-        'can_move': forum_acl.get('can_move_threads'),
-        'can_review': forum_acl.get('can_review_moderated_content'),
-        'can_report': forum_acl.get('can_report_content'),
-        'can_see_reports': forum_acl.get('can_see_reports')
+        'can_hide': category_acl.get('can_hide_threads'),
+        'can_change_label': category_acl.get('can_change_threads_labels') == 2,
+        'can_pin': category_acl.get('can_pin_threads'),
+        'can_close': category_acl.get('can_close_threads'),
+        'can_move': category_acl.get('can_move_threads'),
+        'can_review': category_acl.get('can_review_moderated_content'),
+        'can_report': category_acl.get('can_report_content'),
+        'can_see_reports': category_acl.get('can_see_reports')
     })
 
     if can_change_owned_thread(user, thread):
-        if not forum_acl.get('can_close_threads'):
-            thread_is_protected = thread.is_closed or thread.forum.is_closed
+        if not category_acl.get('can_close_threads'):
+            thread_is_protected = thread.is_closed or thread.category.is_closed
         else:
             thread_is_protected = False
 
         if not thread_is_protected:
             if not thread.acl['can_change_label']:
-                can_change_label = forum_acl.get('can_change_threads_labels')
+                can_change_label = category_acl.get('can_change_threads_labels')
                 thread.acl['can_change_label'] = can_change_label == 1
             if not thread.acl['can_hide']:
                 if not thread.replies:
-                    can_hide_thread = forum_acl.get('can_hide_own_threads')
+                    can_hide_thread = category_acl.get('can_hide_own_threads')
                     thread.acl['can_hide'] = can_hide_thread
 
 
 def add_acl_to_post(user, post):
-    forum_acl = user.acl['forums'].get(post.forum_id, {})
+    category_acl = user.acl['categories'].get(post.category_id, {})
 
     post.acl.update({
         'can_reply': can_reply_thread(user, post.thread),
         'can_edit': can_edit_post(user, post),
-        'can_see_hidden': forum_acl.get('can_hide_posts'),
+        'can_see_hidden': category_acl.get('can_hide_posts'),
         'can_unhide': can_unhide_post(user, post),
         'can_hide': can_hide_post(user, post),
         'can_delete': can_delete_post(user, post),
-        'can_protect': forum_acl.get('can_protect_posts'),
-        'can_report': forum_acl.get('can_report_content'),
-        'can_see_reports': forum_acl.get('can_see_reports'),
-        'can_approve': forum_acl.get('can_review_moderated_content'),
+        'can_protect': category_acl.get('can_protect_posts'),
+        'can_report': category_acl.get('can_report_content'),
+        'can_see_reports': category_acl.get('can_see_reports'),
+        'can_approve': category_acl.get('can_review_moderated_content'),
     })
 
     if not post.is_moderated:
@@ -360,15 +360,15 @@ def add_acl_to_post(user, post):
 
 
 def add_acl_to_event(user, event):
-    forum_acl = user.acl['forums'].get(event.forum_id, {})
-    can_hide_events = forum_acl.get('can_hide_events', 0)
+    category_acl = user.acl['categories'].get(event.category_id, {})
+    can_hide_events = category_acl.get('can_hide_events', 0)
 
     event.acl['can_hide'] = can_hide_events > 0
     event.acl['can_delete'] = can_hide_events == 2
 
 
 def register_with(registry):
-    registry.acl_annotator(Forum, add_acl_to_forum)
+    registry.acl_annotator(Category, add_acl_to_category)
     registry.acl_annotator(Thread, add_acl_to_thread)
     registry.acl_annotator(Post, add_acl_to_post)
     registry.acl_annotator(Event, add_acl_to_event)
@@ -378,17 +378,17 @@ def register_with(registry):
 ACL tests
 """
 def allow_see_thread(user, target):
-    forum_acl = user.acl['forums'].get(target.forum_id, {})
-    if not forum_acl.get('can_browse'):
+    category_acl = user.acl['categories'].get(target.category_id, {})
+    if not category_acl.get('can_browse'):
         raise Http404()
 
     if user.is_anonymous() or user.pk != target.starter_id:
-        if not forum_acl.get('can_see_all_threads'):
+        if not category_acl.get('can_see_all_threads'):
             raise Http404()
         if target.is_moderated:
-            if not forum_acl.get('can_review_moderated_content'):
+            if not category_acl.get('can_review_moderated_content'):
                 raise Http404()
-        if target.is_hidden and not forum_acl.get('can_hide_threads'):
+        if target.is_hidden and not category_acl.get('can_hide_threads'):
             raise Http404()
 can_see_thread = return_boolean(allow_see_thread)
 
@@ -399,11 +399,11 @@ def allow_start_thread(user, target):
 
     if target.is_closed and not target.acl['can_close_threads']:
         raise PermissionDenied(
-            _("This forum is closed. You can't start new threads in it."))
+            _("This category is closed. You can't start new threads in it."))
 
-    if not user.acl['forums'].get(target.id, {'can_start_threads': False}):
+    if not user.acl['categories'].get(target.id, {'can_start_threads': False}):
         raise PermissionDenied(_("You don't have permission to start "
-                                 "new threads in this forum."))
+                                 "new threads in this category."))
 can_start_thread = return_boolean(allow_start_thread)
 
 
@@ -411,18 +411,19 @@ def allow_reply_thread(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to reply threads."))
 
-    forum_acl = target.forum.acl
+    category_acl = target.category.acl
 
-    if not forum_acl['can_close_threads']:
-        if target.forum.is_closed:
+    if not category_acl['can_close_threads']:
+        if target.category.is_closed:
             raise PermissionDenied(
-                _("This forum is closed. You can't reply to threads in it."))
+                _("This category is closed. You can't reply to threads in it."))
         if target.is_closed:
             raise PermissionDenied(
-                _("You can't reply to closed threads in this forum."))
+                _("You can't reply to closed threads in this category."))
 
-    if not forum_acl['can_reply_threads']:
-        raise PermissionDenied(_("You can't reply to threads in this forum."))
+    if not category_acl['can_reply_threads']:
+        raise PermissionDenied(
+            _("You can't reply to threads in this category."))
 can_reply_thread = return_boolean(allow_reply_thread)
 
 
@@ -430,39 +431,39 @@ def allow_edit_thread(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to edit threads."))
 
-    forum_acl = target.forum.acl
+    category_acl = target.category.acl
 
-    if not forum_acl['can_edit_threads']:
-        raise PermissionDenied(_("You can't edit threads in this forum."))
+    if not category_acl['can_edit_threads']:
+        raise PermissionDenied(_("You can't edit threads in this category."))
 
-    if forum_acl['can_edit_threads'] == 1:
+    if category_acl['can_edit_threads'] == 1:
         if target.starter_id != user.pk:
             raise PermissionDenied(
-                _("You can't edit other users threads in this forum."))
+                _("You can't edit other users threads in this category."))
 
-        if not forum_acl['can_close_threads']:
-            if target.forum.is_closed:
+        if not category_acl['can_close_threads']:
+            if target.category.is_closed:
                 raise PermissionDenied(
-                    _("This forum is closed. You can't edit threads in it."))
+                    _("This category is closed. You can't edit threads in it."))
             if target.is_closed:
                 raise PermissionDenied(
-                    _("You can't edit closed threads in this forum."))
+                    _("You can't edit closed threads in this category."))
 
         if not has_time_to_edit_thread(user, target):
             message = ungettext("You can't edit threads that are "
                                 "older than %(minutes)s minute.",
                                 "You can't edit threads that are "
                                 "older than %(minutes)s minutes.",
-                                forum_acl['thread_edit_time'])
+                                category_acl['thread_edit_time'])
             raise PermissionDenied(
-                message % {'minutes': forum_acl['thread_edit_time']})
+                message % {'minutes': category_acl['thread_edit_time']})
 can_edit_thread = return_boolean(allow_edit_thread)
 
 
 def allow_see_post(user, target):
     if target.is_moderated:
-        forum_acl = user.acl['forums'].get(target.forum_id, {})
-        if not forum_acl.get('can_review_moderated_content'):
+        category_acl = user.acl['categories'].get(target.category_id, {})
+        if not category_acl.get('can_review_moderated_content'):
             if user.is_anonymous() or user.pk != target.poster_id:
                 raise Http404()
 can_see_post = return_boolean(allow_see_post)
@@ -472,28 +473,28 @@ def allow_edit_post(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to edit posts."))
 
-    forum_acl = target.forum.acl
+    category_acl = target.category.acl
 
-    if not forum_acl['can_edit_posts']:
-        raise PermissionDenied(_("You can't edit posts in this forum."))
+    if not category_acl['can_edit_posts']:
+        raise PermissionDenied(_("You can't edit posts in this category."))
 
     if target.is_hidden and not can_unhide_post(user, target):
         raise PermissionDenied(_("This post is hidden, you can't edit it."))
 
-    if forum_acl['can_edit_posts'] == 1:
+    if category_acl['can_edit_posts'] == 1:
         if target.poster_id != user.pk:
             raise PermissionDenied(
-                _("You can't edit other users posts in this forum."))
+                _("You can't edit other users posts in this category."))
 
-        if not forum_acl['can_close_threads']:
-            if target.forum.is_closed:
+        if not category_acl['can_close_threads']:
+            if target.category.is_closed:
                 raise PermissionDenied(
-                    _("This forum is closed. You can't edit posts in it."))
+                    _("This category is closed. You can't edit posts in it."))
             if target.thread.is_closed:
                 raise PermissionDenied(
                     _("This thread is closed. You can't edit posts in it."))
 
-        if target.is_protected and not forum_acl['can_protect_posts']:
+        if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(
                 _("This post is protected. You can't edit it."))
 
@@ -502,9 +503,9 @@ def allow_edit_post(user, target):
                                 "older than %(minutes)s minute.",
                                 "You can't edit posts that are "
                                 "older than %(minutes)s minutes.",
-                                forum_acl['post_edit_time'])
+                                category_acl['post_edit_time'])
             raise PermissionDenied(
-                message % {'minutes': forum_acl['post_edit_time']})
+                message % {'minutes': category_acl['post_edit_time']})
 can_edit_post = return_boolean(allow_edit_post)
 
 
@@ -512,25 +513,26 @@ def allow_unhide_post(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to reveal posts."))
 
-    forum_acl = target.forum.acl
+    category_acl = target.category.acl
 
-    if not forum_acl['can_hide_posts']:
-        if not forum_acl['can_hide_own_posts']:
-            raise PermissionDenied(_("You can't reveal posts in this forum."))
+    if not category_acl['can_hide_posts']:
+        if not category_acl['can_hide_own_posts']:
+            raise PermissionDenied(
+                _("You can't reveal posts in this category."))
 
         if user.id != target.poster_id:
             raise PermissionDenied(
-                _("You can't reveal other users posts in this forum."))
+                _("You can't reveal other users posts in this category."))
 
-        if not forum_acl['can_close_threads']:
-            if target.forum.is_closed:
-                raise PermissionDenied(_("This forum is closed. You can't "
+        if not category_acl['can_close_threads']:
+            if target.category.is_closed:
+                raise PermissionDenied(_("This category is closed. You can't "
                                          "reveal posts in it."))
             if target.thread.is_closed:
                 raise PermissionDenied(_("This thread is closed. You can't "
                                          "reveal posts in it."))
 
-        if target.is_protected and not forum_acl['can_protect_posts']:
+        if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(
                 _("This post is protected. You can't reveal it."))
 
@@ -539,9 +541,9 @@ def allow_unhide_post(user, target):
                                 "older than %(minutes)s minute.",
                                 "You can't reveal posts that are "
                                 "older than %(minutes)s minutes.",
-                                forum_acl['post_edit_time'])
+                                category_acl['post_edit_time'])
             raise PermissionDenied(
-                message % {'minutes': forum_acl['post_edit_time']})
+                message % {'minutes': category_acl['post_edit_time']})
 
     if target.id == target.thread.first_post_id:
         raise PermissionDenied(_("You can't reveal thread's first post."))
@@ -554,25 +556,25 @@ def allow_hide_post(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to hide posts."))
 
-    forum_acl = target.forum.acl
+    category_acl = target.category.acl
 
-    if not forum_acl['can_hide_posts']:
-        if not forum_acl['can_hide_own_posts']:
-            raise PermissionDenied(_("You can't hide posts in this forum."))
+    if not category_acl['can_hide_posts']:
+        if not category_acl['can_hide_own_posts']:
+            raise PermissionDenied(_("You can't hide posts in this category."))
 
         if user.id != target.poster_id:
             raise PermissionDenied(
-                _("You can't hide other users posts in this forum."))
+                _("You can't hide other users posts in this category."))
 
-        if not forum_acl['can_close_threads']:
-            if target.forum.is_closed:
-                raise PermissionDenied(_("This forum is closed. You can't "
+        if not category_acl['can_close_threads']:
+            if target.category.is_closed:
+                raise PermissionDenied(_("This category is closed. You can't "
                                          "hide posts in it."))
             if target.thread.is_closed:
                 raise PermissionDenied(_("This thread is closed. You can't "
                                          "hide posts in it."))
 
-        if target.is_protected and not forum_acl['can_protect_posts']:
+        if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(
                 _("This post is protected. You can't hide it."))
 
@@ -581,9 +583,9 @@ def allow_hide_post(user, target):
                                 "older than %(minutes)s minute.",
                                 "You can't hide posts that are "
                                 "older than %(minutes)s minutes.",
-                                forum_acl['post_edit_time'])
+                                category_acl['post_edit_time'])
             raise PermissionDenied(
-                message % {'minutes': forum_acl['post_edit_time']})
+                message % {'minutes': category_acl['post_edit_time']})
 
     if target.id == target.thread.first_post_id:
         raise PermissionDenied(_("You can't hide thread's first post."))
@@ -596,25 +598,26 @@ def allow_delete_post(user, target):
     if user.is_anonymous():
         raise PermissionDenied(_("You have to sign in to delete posts."))
 
-    forum_acl = target.forum.acl
+    category_acl = target.category.acl
 
-    if forum_acl['can_hide_posts'] != 2:
-        if not forum_acl['can_hide_own_posts'] != 2:
-            raise PermissionDenied(_("You can't delete posts in this forum."))
+    if category_acl['can_hide_posts'] != 2:
+        if not category_acl['can_hide_own_posts'] != 2:
+            raise PermissionDenied(
+                _("You can't delete posts in this category."))
 
         if user.id != target.poster_id:
             raise PermissionDenied(
-                _("You can't delete other users posts in this forum."))
+                _("You can't delete other users posts in this category."))
 
-        if not forum_acl['can_close_threads']:
-            if target.forum.is_closed:
-                raise PermissionDenied(_("This forum is closed. You can't "
+        if not category_acl['can_close_threads']:
+            if target.category.is_closed:
+                raise PermissionDenied(_("This category is closed. You can't "
                                          "delete posts from it."))
             if target.thread.is_closed:
                 raise PermissionDenied(_("This thread is closed. You can't "
                                          "delete posts from it."))
 
-        if target.is_protected and not forum_acl['can_protect_posts']:
+        if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(
                 _("This post is protected. You can't delete it."))
 
@@ -623,9 +626,9 @@ def allow_delete_post(user, target):
                                 "older than %(minutes)s minute.",
                                 "You can't delete posts that are "
                                 "older than %(minutes)s minutes.",
-                                forum_acl['post_edit_time'])
+                                category_acl['post_edit_time'])
             raise PermissionDenied(
-                message % {'minutes': forum_acl['post_edit_time']})
+                message % {'minutes': category_acl['post_edit_time']})
 
     if target.id == target.thread.first_post_id:
         raise PermissionDenied(_("You can't delete thread's first post."))
@@ -636,12 +639,12 @@ can_delete_post = return_boolean(allow_delete_post)
 Permission check helpers
 """
 def can_change_owned_thread(user, target):
-    forum_acl = user.acl['forums'].get(target.forum_id, {})
+    category_acl = user.acl['categories'].get(target.category_id, {})
 
     if user.is_anonymous() or user.pk != target.starter_id:
         return False
 
-    if target.forum.is_closed or target.is_closed:
+    if target.category.is_closed or target.is_closed:
         return False
 
     if target.first_post.is_protected:
@@ -651,23 +654,23 @@ def can_change_owned_thread(user, target):
 
 
 def has_time_to_edit_thread(user, target):
-    forum_acl = user.acl['forums'].get(target.forum_id, {})
-    if forum_acl.get('thread_edit_time'):
+    category_acl = user.acl['categories'].get(target.category_id, {})
+    if category_acl.get('thread_edit_time'):
         diff = timezone.now() - target.started_on
         diff_minutes = int(diff.total_seconds() / 60)
 
-        return diff_minutes < forum_acl.get('thread_edit_time')
+        return diff_minutes < category_acl.get('thread_edit_time')
     else:
         return True
 
 
 def has_time_to_edit_post(user, target):
-    forum_acl = user.acl['forums'].get(target.forum_id, {})
-    if forum_acl.get('post_edit_time'):
+    category_acl = user.acl['categories'].get(target.category_id, {})
+    if category_acl.get('post_edit_time'):
         diff = timezone.now() - target.posted_on
         diff_minutes = int(diff.total_seconds() / 60)
 
-        return diff_minutes < forum_acl.get('post_edit_time')
+        return diff_minutes < category_acl.get('post_edit_time')
     else:
         return True
 
@@ -675,19 +678,19 @@ def has_time_to_edit_post(user, target):
 """
 Queryset helpers
 """
-def exclude_invisible_threads(queryset, user, forum=None):
-    if forum:
-        return exclude_invisible_forum_threads(queryset, user, forum)
+def exclude_invisible_threads(queryset, user, category=None):
+    if category:
+        return exclude_invisible_category_threads(queryset, user, category)
     else:
         return exclude_all_invisible_threads(queryset, user)
 
 
-def exclude_invisible_forum_threads(queryset, user, forum):
+def exclude_invisible_category_threads(queryset, user, category):
     if user.is_authenticated():
         condition_author = Q(starter_id=user.id)
 
-        can_mod = forum.acl['can_review_moderated_content']
-        can_hide = forum.acl['can_hide_threads']
+        can_mod = category.acl['can_review_moderated_content']
+        can_hide = category.acl['can_hide_threads']
 
         if not can_mod and not can_hide:
             condition = Q(is_moderated=False) & Q(is_hidden=False)
@@ -699,28 +702,28 @@ def exclude_invisible_forum_threads(queryset, user, forum):
             condition = Q(is_hidden=False)
             queryset = queryset.filter(condition_author | condition)
     else:
-        if not forum.acl['can_review_moderated_content']:
+        if not category.acl['can_review_moderated_content']:
             queryset = queryset.filter(is_moderated=False)
-        if not forum.acl['can_hide_threads']:
+        if not category.acl['can_hide_threads']:
             queryset = queryset.filter(is_hidden=False)
 
     return queryset
 
 
 def exclude_all_invisible_threads(queryset, user):
-    forums_in = []
+    categories_in = []
     conditions = None
 
-    for forum in Forum.objects.all_forums():
-        add_acl(user, forum)
+    for category in Category.objects.all_categories():
+        add_acl(user, category)
 
-        condition_forum = Q(forum=forum)
+        condition_category = Q(category=category)
         condition_author = Q(starter_id=user.id)
 
         # can see all threads?
-        if forum.acl['can_see_all_threads']:
-            can_mod = forum.acl['can_review_moderated_content']
-            can_hide = forum.acl['can_hide_threads']
+        if category.acl['can_see_all_threads']:
+            can_mod = category.acl['can_review_moderated_content']
+            can_hide = category.acl['can_hide_threads']
 
             if not can_mod or not can_hide:
                 if not can_mod and not can_hide:
@@ -730,32 +733,32 @@ def exclude_all_invisible_threads(queryset, user):
                 elif not can_hide:
                     condition = Q(is_hidden=False)
                 visibility_condition = condition_author | condition
-                visibility_condition = condition_forum & visibility_condition
+                visibility_condition = condition_category & visibility_condition
             else:
                 # user can see everything so don't bother with rest of routine
-                forums_in.append(forum.pk)
+                categories_in.append(category.pk)
                 continue
         else:
-            # show all threads in forum made by user
-            visibility_condition = condition_forum & condition_author
+            # show all threads in category made by user
+            visibility_condition = condition_category & condition_author
 
         if conditions:
             conditions = conditions | visibility_condition
         else:
             conditions = visibility_condition
 
-    if conditions and forums_in:
-        return queryset.filter(Q(forum_id__in=forums_in) | conditions)
+    if conditions and categories_in:
+        return queryset.filter(Q(category_id__in=categories_in) | conditions)
     elif conditions:
         return queryset.filter(conditions)
-    elif forums_in:
-        return queryset.filter(forum_id__in=forums_in)
+    elif categories_in:
+        return queryset.filter(category_id__in=categories_in)
     else:
         return Thread.objects.none()
 
 
-def exclude_invisible_posts(queryset, user, forum):
-    if not forum.acl['can_review_moderated_content']:
+def exclude_invisible_posts(queryset, user, category):
+    if not category.acl['can_review_moderated_content']:
         if user.is_authenticated():
             condition_author = Q(poster_id=user.id)
             condition = Q(is_moderated=False)

+ 1 - 1
misago/threads/posting/__init__.py

@@ -28,7 +28,7 @@ class EditorFormset(object):
         self._forms_list = []
         self._forms_dict = {}
 
-        is_private = kwargs['forum'].special_role == "private_threads"
+        is_private = kwargs['category'].special_role == 'private_threads'
         kwargs['is_private'] = is_private
 
         self.kwargs = kwargs

+ 3 - 4
misago/threads/posting/savechanges.py

@@ -1,5 +1,4 @@
 from collections import OrderedDict
-
 from misago.threads.posting import PostingMiddleware
 
 
@@ -10,18 +9,18 @@ class SaveChangesMiddleware(PostingMiddleware):
 
     def reset_state(self):
         self.user.update_all = False
-        self.forum.update_all = False
+        self.category.update_all = False
         self.thread.update_all = False
         self.post.update_all = False
 
         self.user.update_fields = []
-        self.forum.update_fields = []
+        self.category.update_fields = []
         self.thread.update_fields = []
         self.post.update_fields = []
 
     def save_models(self):
         self.save_model(self.user)
-        self.save_model(self.forum)
+        self.save_model(self.category)
         self.save_model(self.thread)
         self.save_model(self.post)
         self.reset_state()

+ 1 - 1
misago/threads/posting/threadclose.py

@@ -5,7 +5,7 @@ from misago.threads.posting import PostingMiddleware, START
 
 class ThreadCloseFormMiddleware(PostingMiddleware):
     def use_this_middleware(self):
-        if self.forum.acl['can_close_threads']:
+        if self.category.acl['can_close_threads']:
             self.is_closed = self.thread.is_closed
             return True
         else:

+ 4 - 3
misago/threads/posting/threadlabel.py

@@ -7,7 +7,8 @@ from misago.threads.posting import PostingMiddleware, START, EDIT
 
 class ThreadLabelFormMiddleware(PostingMiddleware):
     def use_this_middleware(self):
-        if self.forum.labels and self.forum.acl['can_change_threads_labels']:
+        if (self.category.labels and
+                self.category.acl['can_change_threads_labels']):
             self.label_id = self.thread.label_id
 
             if self.mode == START:
@@ -21,11 +22,11 @@ class ThreadLabelFormMiddleware(PostingMiddleware):
     def make_form(self):
         if self.request.method == 'POST':
             return ThreadLabelForm(self.request.POST, prefix=self.prefix,
-                                    labels=self.forum.labels)
+                                    labels=self.category.labels)
         else:
             initial = {'label_id': self.label_id}
             return ThreadLabelForm(prefix=self.prefix,
-                                    labels=self.forum.labels,
+                                    labels=self.category.labels,
                                     initial=initial)
 
     def pre_save(self, form):

+ 1 - 1
misago/threads/posting/threadpin.py

@@ -5,7 +5,7 @@ from misago.threads.posting import PostingMiddleware, START
 
 class ThreadPinFormMiddleware(PostingMiddleware):
     def use_this_middleware(self):
-        if self.forum.acl['can_pin_threads']:
+        if self.category.acl['can_pin_threads']:
             self.is_pinned = self.thread.is_pinned
             return True
         else:

+ 6 - 7
misago/threads/posting/updatestats.py

@@ -1,22 +1,21 @@
 from django.db.models import F
-
 from misago.threads.posting import PostingMiddleware, START, REPLY, EDIT
 
 
 class UpdateStatsMiddleware(PostingMiddleware):
     def save(self, form):
         self.update_thread()
-        self.update_forum()
+        self.update_category()
         self.update_user()
 
-    def update_forum(self):
+    def update_category(self):
         if self.mode == START:
-            self.forum.threads = F('threads') + 1
+            self.category.threads = F('threads') + 1
 
         if self.mode != EDIT:
-            self.forum.set_last_thread(self.thread)
-            self.forum.posts = F('posts') + 1
-            self.forum.update_all = True
+            self.category.set_last_thread(self.thread)
+            self.category.posts = F('posts') + 1
+            self.category.update_all = True
 
     def update_thread(self):
         if self.mode == START:

+ 1 - 1
misago/threads/reports.py

@@ -39,7 +39,7 @@ def report_post(request, post, message):
         message = message['parsed_text']
 
     report = Report.objects.create(
-        forum=post.forum,
+        category=post.category,
         thread=post.thread,
         post=post,
         reported_by=request.user,

+ 28 - 27
misago/threads/signals.py

@@ -1,8 +1,8 @@
 from django.db import transaction
 from django.dispatch import receiver, Signal
 
+from misago.categories.models import Category
 from misago.core.pgutils import batch_update, batch_delete
-from misago.forums.models import Forum
 
 from misago.threads.models import Thread, Post, Event, Label
 
@@ -22,58 +22,59 @@ Signal handlers
 @receiver(merge_thread)
 def merge_threads_posts(sender, **kwargs):
     other_thread = kwargs['other_thread']
-    other_thread.post_set.update(forum=sender.forum, thread=sender)
+    other_thread.post_set.update(category=sender.category, thread=sender)
 
 
 @receiver(move_thread)
 def move_thread_content(sender, **kwargs):
-    sender.post_set.update(forum=sender.forum)
-    sender.event_set.update(forum=sender.forum)
+    sender.post_set.update(category=sender.category)
+    sender.event_set.update(category=sender.category)
 
     # remove unavailable labels
     if sender.label_id:
-        new_forum_labels = Label.objects.get_forum_labels(sender.forum)
-        if sender.label_id not in [l.pk for l in new_forum_labels]:
+        new_category_labels = Label.objects.get_category_labels(sender.category)
+        if sender.label_id not in [l.pk for l in new_category_labels]:
             sender.label = None
 
 
-from misago.forums.signals import delete_forum_content, move_forum_content
-@receiver(delete_forum_content)
-def delete_forum_threads(sender, **kwargs):
+from misago.categories.signals import (delete_category_content,
+                                       move_category_content)
+@receiver(delete_category_content)
+def delete_category_threads(sender, **kwargs):
     sender.event_set.all().delete()
     sender.thread_set.all().delete()
     sender.post_set.all().delete()
 
 
-@receiver(move_forum_content)
-def move_forum_threads(sender, **kwargs):
-    new_forum = kwargs['new_forum']
-    Thread.objects.filter(forum=sender).update(forum=new_forum)
-    Post.objects.filter(forum=sender).update(forum=new_forum)
-    Event.objects.filter(forum=sender).update(forum=new_forum)
+@receiver(move_category_content)
+def move_category_threads(sender, **kwargs):
+    new_category = kwargs['new_category']
+    Thread.objects.filter(category=sender).update(category=new_category)
+    Post.objects.filter(category=sender).update(category=new_category)
+    Event.objects.filter(category=sender).update(category=new_category)
 
     # move labels
-    old_forum_labels = Label.objects.get_forum_labels(sender)
-    new_forum_labels = Label.objects.get_forum_labels(new_forum)
+    old_category_labels = Label.objects.get_category_labels(sender)
+    new_category_labels = Label.objects.get_category_labels(new_category)
 
-    for label in old_forum_labels:
-        if label not in new_forum_labels:
-            label.forums.add(new_forum_labels)
+    for label in old_category_labels:
+        if label not in new_category_labels:
+            label.categories.add(new_category_labels)
 
 
 from misago.users.signals import delete_user_content, username_changed
 @receiver(delete_user_content)
 def delete_user_threads(sender, **kwargs):
-    recount_forums = set()
+    recount_categories = set()
     recount_threads = set()
 
     for thread in batch_delete(sender.thread_set.all(), 50):
-        recount_forums.add(thread.forum_id)
+        recount_categories.add(thread.category_id)
         with transaction.atomic():
             thread.delete()
 
     for post in batch_delete(sender.post_set.all(), 50):
-        recount_forums.add(post.forum_id)
+        recount_categories.add(post.category_id)
         recount_threads.add(post.thread_id)
         with transaction.atomic():
             post.delete()
@@ -84,10 +85,10 @@ def delete_user_threads(sender, **kwargs):
             thread.synchronize()
             thread.save()
 
-    if recount_forums:
-        for forum in Forum.objects.filter(id__in=recount_forums):
-            forum.synchronize()
-            forum.save()
+    if recount_categories:
+        for category in Category.objects.filter(id__in=recount_categories):
+            category.synchronize()
+            category.save()
 
 
 @receiver(username_changed)

+ 59 - 58
misago/threads/tests/-test_editpost_view.py

@@ -4,7 +4,7 @@ from django.conf import settings
 from django.core.urlresolvers import reverse
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.models import Label, Thread, Post
@@ -17,10 +17,11 @@ class EditPostTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(EditPostTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.thread = post_thread(self.forum, poster=self.user)
+        self.category = Category.objects.all_categories().filter(
+            role='category')[:1][0]
+        self.thread = post_thread(self.category, poster=self.user)
         self.link = reverse('misago:edit_post', kwargs={
-            'forum_id': self.forum.id,
+            'category_id': self.category.id,
             'thread_id': self.thread.id,
             'post_id': self.thread.first_post_id,
         })
@@ -30,69 +31,69 @@ class EditPostTests(AuthenticatedUserTestCase):
     def tearDown(self):
         Label.objects.clear_cache()
 
-    def override_forum_acl(self, extra_acl=None):
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+    def override_category_acl(self, extra_acl=None):
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 1,
             'can_see_all_threads': 1,
         }
         if extra_acl:
-            forums_acl['forums'][self.forum.pk].update(extra_acl)
-        override_acl(self.user, forums_acl)
+            categories_acl['categories'][self.category.pk].update(extra_acl)
+        override_acl(self.user, categories_acl)
 
     def test_cant_see(self):
-        """has no permission to see forum"""
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].remove(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        """has no permission to see category"""
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].remove(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 0,
             'can_browse': 0,
             'can_see_all_threads': 1,
             'can_reply_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 404)
 
     def test_cant_browse(self):
-        """has no permission to browse forum"""
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        """has no permission to browse category"""
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 0,
             'can_see_all_threads': 1,
             'can_reply_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 404)
 
-    def test_cant_edit_own_post_in_locked_forum(self):
-        """can't edit own post in closed forum"""
-        self.forum.is_closed = True
-        self.forum.save()
+    def test_cant_edit_own_post_in_locked_category(self):
+        """can't edit own post in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
-        self.override_forum_acl({'can_edit_threads': 1})
+        self.override_category_acl({'can_edit_threads': 1})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
 
-    def test_cant_edit_other_user_post_in_locked_forum(self):
-        """can't edit other user post in closed forum"""
-        self.forum.is_closed = True
-        self.forum.save()
+    def test_cant_edit_other_user_post_in_locked_category(self):
+        """can't edit other user post in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
         self.thread.first_post.poster = None
         self.thread.first_post.save()
         self.thread.synchronize()
         self.thread.save()
 
-        self.override_forum_acl({'can_edit_threads': 2})
+        self.override_category_acl({'can_edit_threads': 2})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
@@ -102,14 +103,14 @@ class EditPostTests(AuthenticatedUserTestCase):
         self.thread.is_closed = True
         self.thread.save()
 
-        self.override_forum_acl({'can_edit_threads': 1})
+        self.override_category_acl({'can_edit_threads': 1})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
 
     def test_cant_edit_other_user_post_in_locked_thread(self):
         """can't edit other user post in closed thread"""
-        self.override_forum_acl({'can_edit_threads': 2})
+        self.override_category_acl({'can_edit_threads': 2})
 
         self.thread.first_post.poster = None
         self.thread.first_post.save()
@@ -122,14 +123,14 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_cant_edit_own_post(self):
         """can't edit own post"""
-        self.override_forum_acl({'can_edit_posts': 0})
+        self.override_category_acl({'can_edit_posts': 0})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
 
     def test_cant_edit_other_user_post(self):
         """can't edit other user post"""
-        self.override_forum_acl({'can_edit_posts': 1})
+        self.override_category_acl({'can_edit_posts': 1})
 
         self.thread.first_post.poster = None
         self.thread.first_post.save()
@@ -141,7 +142,7 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_cant_edit_protected_post(self):
         """can't edit post that was protected by moderator"""
-        self.override_forum_acl({'can_edit_posts': 1, 'can_protect_posts': 0})
+        self.override_category_acl({'can_edit_posts': 1, 'can_protect_posts': 0})
 
         self.thread.first_post.is_protected = True
         self.thread.first_post.save()
@@ -151,13 +152,13 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_can_edit_own_post(self):
         """can edit own post"""
-        self.override_forum_acl({'can_edit_posts': 1, 'can_edit_threads': 0})
+        self.override_category_acl({'can_edit_posts': 1, 'can_edit_threads': 0})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
         self.assertNotIn('thread-title', response.content)
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_edit_threads': 0})
+        self.override_category_acl({'can_edit_posts': 1, 'can_edit_threads': 0})
         response = self.client.post(self.link, data={
             'post': 'Edited reply!',
             'submit': True,
@@ -174,7 +175,7 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_empty_edit_form(self):
         """empty edit form has no crashes"""
-        self.override_forum_acl({'can_edit_posts': 2, 'can_edit_threads': 2})
+        self.override_category_acl({'can_edit_posts': 2, 'can_edit_threads': 2})
 
         response = self.client.post(self.link, data={
             'submit': True,
@@ -184,7 +185,7 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_can_edit_other_user_post(self):
         """can edit other user post"""
-        self.override_forum_acl({'can_edit_posts': 2, 'can_edit_threads': 0})
+        self.override_category_acl({'can_edit_posts': 2, 'can_edit_threads': 0})
 
         self.thread.first_post.poster = None
         self.thread.first_post.save()
@@ -195,7 +196,7 @@ class EditPostTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertNotIn('thread-title', response.content)
 
-        self.override_forum_acl({'can_edit_posts': 2, 'can_edit_threads': 0})
+        self.override_category_acl({'can_edit_posts': 2, 'can_edit_threads': 0})
         response = self.client.post(self.link, data={
             'post': 'Edited reply!',
             'submit': True,
@@ -212,13 +213,13 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_can_edit_own_thread(self):
         """can edit own thread"""
-        self.override_forum_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
         self.assertIn('thread-title', response.content)
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
         response = self.client.post(self.link, data={
             'title': 'Edited title!',
             'post': self.thread.first_post.original,
@@ -239,7 +240,7 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_can_edit_other_user_thread(self):
         """can edit other user thread"""
-        self.override_forum_acl({'can_edit_posts': 2, 'can_edit_threads': 2})
+        self.override_category_acl({'can_edit_posts': 2, 'can_edit_threads': 2})
 
         self.thread.first_post.poster = None
         self.thread.first_post.save()
@@ -250,7 +251,7 @@ class EditPostTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertIn('thread-title', response.content)
 
-        self.override_forum_acl({'can_edit_posts': 2, 'can_edit_threads': 2})
+        self.override_category_acl({'can_edit_posts': 2, 'can_edit_threads': 2})
         response = self.client.post(self.link, data={
             'title': 'Edited title!',
             'post': self.thread.first_post.original,
@@ -271,13 +272,13 @@ class EditPostTests(AuthenticatedUserTestCase):
 
     def test_no_change_edit(self):
         """user edited post but submited no changes"""
-        self.override_forum_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
         self.assertIn('thread-title', response.content)
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_edit_threads': 1})
         response = self.client.post(self.link, data={
             'title': self.thread.title,
             'post': self.thread.first_post.original,
@@ -297,13 +298,13 @@ class EditPostTests(AuthenticatedUserTestCase):
         """user edited post to close and open thread"""
         prefix = 'misago.threads.posting.threadclose.ThreadCloseFormMiddleware'
         field_name = '%s-is_closed' % prefix
-        self.override_forum_acl({'can_edit_posts': 1, 'can_close_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_close_threads': 1})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
         self.assertIn(field_name, response.content)
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_close_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_close_threads': 1})
         response = self.client.post(self.link, data={
             'post': self.thread.first_post.original,
             field_name: 1,
@@ -321,7 +322,7 @@ class EditPostTests(AuthenticatedUserTestCase):
         self.user.last_posted_on = None
         self.user.save()
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_close_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_close_threads': 1})
         response = self.client.post(self.link, data={
             'post': self.thread.first_post.original,
             field_name: 0,
@@ -340,13 +341,13 @@ class EditPostTests(AuthenticatedUserTestCase):
         """user edited post to pin and unpin thread"""
         prefix = 'misago.threads.posting.threadpin.ThreadPinFormMiddleware'
         field_name = '%s-is_pinned' % prefix
-        self.override_forum_acl({'can_edit_posts': 1, 'can_pin_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_pin_threads': 1})
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
         self.assertIn(field_name, response.content)
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_pin_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_pin_threads': 1})
         response = self.client.post(self.link, data={
             'post': self.thread.first_post.original,
             field_name: 1,
@@ -364,7 +365,7 @@ class EditPostTests(AuthenticatedUserTestCase):
         self.user.last_posted_on = None
         self.user.save()
 
-        self.override_forum_acl({'can_edit_posts': 1, 'can_pin_threads': 1})
+        self.override_category_acl({'can_edit_posts': 1, 'can_pin_threads': 1})
         response = self.client.post(self.link, data={
             'post': self.thread.first_post.original,
             field_name: 0,
@@ -385,19 +386,19 @@ class EditPostTests(AuthenticatedUserTestCase):
         field_name = '%s-label' % prefix
 
         label = Label.objects.create(name="Label", slug="label")
-        label.forums.add(self.forum)
+        label.categories.add(self.category)
 
         acls = {
             'can_edit_posts': 1,
             'can_edit_threads': 1,
             'can_change_threads_labels': 1
         }
-        self.override_forum_acl(acls)
+        self.override_category_acl(acls)
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
         self.assertIn(field_name, response.content)
 
-        self.override_forum_acl(acls)
+        self.override_category_acl(acls)
         response = self.client.post(self.link, data={
             field_name: label.pk,
             'title': self.thread.title,
@@ -413,7 +414,7 @@ class EditPostTests(AuthenticatedUserTestCase):
         self.user.last_posted_on = None
         self.user.save()
 
-        self.override_forum_acl(acls)
+        self.override_category_acl(acls)
         response = self.client.post(self.link, data={
             field_name: 0,
             'title': self.thread.title,
@@ -434,7 +435,7 @@ class EditPostTests(AuthenticatedUserTestCase):
             'can_change_threads_labels': 1
         }
 
-        self.override_forum_acl(acls)
+        self.override_category_acl(acls)
         response = self.client.post(self.link, data={
             'title': '',
             'post': '',
@@ -442,7 +443,7 @@ class EditPostTests(AuthenticatedUserTestCase):
         **self.ajax_header)
         self.assertEqual(response.status_code, 200)
 
-        self.override_forum_acl(acls)
+        self.override_category_acl(acls)
         response = self.client.post(self.link, data={
             'title': '',
             'post': '',

+ 156 - 155
misago/threads/tests/-test_forumthreads_view.py

@@ -4,33 +4,34 @@ from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads import testutils
 from misago.threads.models import Thread, Label
 from misago.threads.moderation import ModerationError
-from misago.threads.views.generic.forum import (ForumActions, ForumFiltering,
-                                                ForumThreads)
+from misago.threads.views.generic.category import (CategoryActions,
+                                                   CategoryFiltering,
+                                                   CategoryThreads)
 
 
-class ForumViewHelperTestCase(AuthenticatedUserTestCase):
+class CategoryViewHelperTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ForumViewHelperTestCase, self).setUp()
+        super(CategoryViewHelperTestCase, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='category')[:1][0]
+        self.category.labels = []
 
     def override_acl(self, new_acl):
         new_acl.update({'can_browse': True})
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = new_acl
-        override_acl(self.user, forums_acl)
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = new_acl
+        override_acl(self.user, categories_acl)
 
-        self.forum.acl = {}
-        add_acl(self.user, self.forum)
+        self.category.acl = {}
+        add_acl(self.user, self.category)
 
 
 class MockRequest(object):
@@ -39,10 +40,10 @@ class MockRequest(object):
         self.user = user
         self.user_ip = '127.0.0.1'
         self.session = {}
-        self.path = '/forum/fake-forum-1/'
+        self.path = '/category/fake-category-1/'
 
 
-class ActionsTests(ForumViewHelperTestCase):
+class ActionsTests(CategoryViewHelperTestCase):
     def setUp(self):
         super(ActionsTests, self).setUp()
         Label.objects.clear_cache()
@@ -52,32 +53,32 @@ class ActionsTests(ForumViewHelperTestCase):
         Label.objects.clear_cache()
 
     def test_label_actions(self):
-        """ForumActions initializes list with label actions"""
+        """CategoryActions initializes list with label actions"""
         self.override_acl({
             'can_change_threads_labels': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_change_threads_labels': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_change_threads_labels': 2,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         label = Label.objects.create(name="Mock Label", slug="mock-label")
-        self.forum.labels = [label]
+        self.category.labels = [label]
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'label:%s' % label.slug,
@@ -92,19 +93,19 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
     def test_pin_unpin_actions(self):
-        """ForumActions initializes list with pin and unpin actions"""
+        """CategoryActions initializes list with pin and unpin actions"""
         self.override_acl({
             'can_pin_threads': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_pin_threads': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'pin',
@@ -119,19 +120,19 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
     def test_approve_action(self):
-        """ForumActions initializes list with approve threads action"""
+        """CategoryActions initializes list with approve threads action"""
         self.override_acl({
             'can_review_moderated_content': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_review_moderated_content': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'approve',
@@ -141,19 +142,19 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
     def test_move_action(self):
-        """ForumActions initializes list with move threads action"""
+        """CategoryActions initializes list with move threads action"""
         self.override_acl({
             'can_move_threads': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_move_threads': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'move',
@@ -163,19 +164,19 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
     def test_merge_action(self):
-        """ForumActions initializes list with merge threads action"""
+        """CategoryActions initializes list with merge threads action"""
         self.override_acl({
             'can_merge_threads': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_merge_threads': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'merge',
@@ -185,19 +186,19 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
     def test_close_open_actions(self):
-        """ForumActions initializes list with close and open actions"""
+        """CategoryActions initializes list with close and open actions"""
         self.override_acl({
             'can_close_threads': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_close_threads': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'open',
@@ -212,19 +213,19 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
     def test_hide_delete_actions(self):
-        """ForumActions initializes list with hide/delete actions"""
+        """CategoryActions initializes list with hide/delete actions"""
         self.override_acl({
             'can_hide_threads': 0,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [])
 
         self.override_acl({
             'can_hide_threads': 1,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'unhide',
@@ -242,7 +243,7 @@ class ActionsTests(ForumViewHelperTestCase):
             'can_hide_threads': 2,
         })
 
-        actions = ForumActions(user=self.user, forum=self.forum)
+        actions = CategoryActions(user=self.user, category=self.category)
         self.assertEqual(actions.available_actions, [
             {
                 'action': 'unhide',
@@ -264,17 +265,17 @@ class ActionsTests(ForumViewHelperTestCase):
         ])
 
 
-class ForumFilteringTests(ForumViewHelperTestCase):
+class CategoryFilteringTests(CategoryViewHelperTestCase):
     def setUp(self):
-        super(ForumFilteringTests, self).setUp()
+        super(CategoryFilteringTests, self).setUp()
         Label.objects.clear_cache()
 
     def tearDown(self):
-        super(ForumFilteringTests, self).tearDown()
+        super(CategoryFilteringTests, self).tearDown()
         Label.objects.clear_cache()
 
     def test_get_available_filters(self):
-        """get_available_filters returns filters varying on forum acl"""
+        """get_available_filters returns filters varying on category acl"""
         default_acl = {
             'can_see_all_threads': False,
             'can_see_reports': False,
@@ -289,9 +290,9 @@ class ForumFilteringTests(ForumViewHelperTestCase):
 
         for permission, filter_type in cases:
             self.override_acl(default_acl)
-            filtering = ForumFiltering(self.forum, 'misago:forum', {
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+            filtering = CategoryFiltering(self.category, 'misago:category', {
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
             })
 
             available_filters = filtering.get_available_filters()
@@ -302,16 +303,16 @@ class ForumFilteringTests(ForumViewHelperTestCase):
             acl[permission] = True
             self.override_acl(acl)
 
-            filtering = ForumFiltering(self.forum, 'misago:forum', {
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+            filtering = CategoryFiltering(self.category, 'misago:category', {
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
             })
 
             available_filters = filtering.get_available_filters()
             available_filters = [f['type'] for f in available_filters]
             self.assertIn(filter_type, available_filters)
 
-        self.forum.labels = [
+        self.category.labels = [
             Label(name='Label A', slug='label-a'),
             Label(name='Label B', slug='label-b'),
             Label(name='Label C', slug='label-c'),
@@ -319,16 +320,16 @@ class ForumFilteringTests(ForumViewHelperTestCase):
         ]
 
         self.override_acl(default_acl)
-        ForumFiltering(self.forum, 'misago:forum', {
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+        CategoryFiltering(self.category, 'misago:category', {
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
             })
 
         available_filters = filtering.get_available_filters()
         available_filters = [f['type'] for f in available_filters]
 
-        self.assertEqual(len(available_filters), len(self.forum.labels))
-        for label in self.forum.labels:
+        self.assertEqual(len(available_filters), len(self.category.labels))
+        for label in self.category.labels:
             self.assertIn(label.slug, available_filters)
 
     def test_clean_kwargs(self):
@@ -339,9 +340,9 @@ class ForumFilteringTests(ForumViewHelperTestCase):
             'can_review_moderated_content': True,
         })
 
-        filtering = ForumFiltering(self.forum, 'misago:forum', {
-            'forum_id': self.forum.id,
-            'forum_slug': self.forum.slug,
+        filtering = CategoryFiltering(self.category, 'misago:category', {
+            'category_id': self.category.id,
+            'category_slug': self.category.slug,
         })
 
         available_filters = filtering.get_available_filters()
@@ -387,9 +388,9 @@ class ForumFilteringTests(ForumViewHelperTestCase):
         )
 
         for filter_type, name in test_cases:
-            filtering = ForumFiltering(self.forum, 'misago:forum', {
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+            filtering = CategoryFiltering(self.category, 'misago:category', {
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
             })
             filtering.clean_kwargs({'show': filter_type})
             self.assertEqual(filtering.current['name'], name)
@@ -410,9 +411,9 @@ class ForumFilteringTests(ForumViewHelperTestCase):
         )
 
         for filter_type in test_cases:
-            filtering = ForumFiltering(self.forum, 'misago:forum', {
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+            filtering = CategoryFiltering(self.category, 'misago:category', {
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
             })
             filtering.clean_kwargs({'show': filter_type})
 
@@ -420,7 +421,7 @@ class ForumFilteringTests(ForumViewHelperTestCase):
             self.assertNotIn(filter_type, choices)
 
 
-class ForumThreadsTests(ForumViewHelperTestCase):
+class CategoryThreadsTests(CategoryViewHelperTestCase):
     def test_empty_list(self):
         """list returns empty list of items"""
         self.override_acl({
@@ -428,7 +429,7 @@ class ForumThreadsTests(ForumViewHelperTestCase):
             'can_review_moderated_content': False
         })
 
-        threads = ForumThreads(self.user, self.forum)
+        threads = CategoryThreads(self.user, self.category)
         threads_list = threads.list()
 
         self.assertEqual(threads_list, [])
@@ -443,7 +444,7 @@ class ForumThreadsTests(ForumViewHelperTestCase):
             'can_review_moderated_content': False
         })
 
-        threads = ForumThreads(self.user, self.forum)
+        threads = CategoryThreads(self.user, self.category)
 
         with self.assertRaises(AttributeError):
             threads.page
@@ -455,22 +456,22 @@ class ForumThreadsTests(ForumViewHelperTestCase):
         """list returns list of visible threads"""
         test_threads = [
             testutils.post_thread(
-                forum=self.forum,
+                category=self.category,
                 title="Hello, I am thread",
                 is_moderated=False,
                 poster=self.user),
             testutils.post_thread(
-                forum=self.forum,
+                category=self.category,
                 title="Hello, I am moderated thread",
                 is_moderated=True,
                 poster=self.user),
             testutils.post_thread(
-                forum=self.forum,
+                category=self.category,
                 title="Hello, I am other user thread",
                 is_moderated=False,
                 poster="Bob"),
             testutils.post_thread(
-                forum=self.forum,
+                category=self.category,
                 title="Hello, I am other user moderated thread",
                 is_moderated=True,
                 poster="Bob"),
@@ -481,7 +482,7 @@ class ForumThreadsTests(ForumViewHelperTestCase):
             'can_review_moderated_content': False
         })
 
-        threads = ForumThreads(self.user, self.forum)
+        threads = CategoryThreads(self.user, self.category)
         self.assertEqual(threads.list(), [test_threads[1], test_threads[0]])
 
         self.override_acl({
@@ -489,7 +490,7 @@ class ForumThreadsTests(ForumViewHelperTestCase):
             'can_review_moderated_content': False
         })
 
-        threads = ForumThreads(self.user, self.forum)
+        threads = CategoryThreads(self.user, self.category)
         self.assertEqual(threads.list(),
                          [test_threads[2], test_threads[1], test_threads[0]])
 
@@ -498,7 +499,7 @@ class ForumThreadsTests(ForumViewHelperTestCase):
             'can_review_moderated_content': True
         })
 
-        threads = ForumThreads(self.user, self.forum)
+        threads = CategoryThreads(self.user, self.category)
         test_threads.reverse()
         self.assertEqual(threads.list(), test_threads)
 
@@ -506,33 +507,33 @@ class ForumThreadsTests(ForumViewHelperTestCase):
         self.assertTrue(threads.paginator)
 
 
-class ForumThreadsViewTests(AuthenticatedUserTestCase):
+class CategoryThreadsViewTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ForumThreadsViewTests, self).setUp()
+        super(CategoryThreadsViewTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.link = self.forum.get_absolute_url()
-        self.forum.delete_content()
+        self.category = Category.objects.all_categories().filter(role='category')[:1][0]
+        self.link = self.category.get_absolute_url()
+        self.category.delete_content()
 
         Label.objects.clear_cache()
 
     def tearDown(self):
-        super(ForumThreadsViewTests, self).tearDown()
+        super(CategoryThreadsViewTests, self).tearDown()
         Label.objects.clear_cache()
 
-    def override_acl(self, new_acl, forum=None):
-        forum = forum or self.forum
+    def override_acl(self, new_acl, category=None):
+        category = category or self.category
 
-        forums_acl = self.user.acl
+        categories_acl = self.user.acl
         if new_acl['can_see']:
-            forums_acl['visible_forums'].append(forum.pk)
+            categories_acl['visible_categories'].append(category.pk)
         else:
-            forums_acl['visible_forums'].remove(forum.pk)
-        forums_acl['forums'][forum.pk] = new_acl
-        override_acl(self.user, forums_acl)
+            categories_acl['visible_categories'].remove(category.pk)
+        categories_acl['categories'][category.pk] = new_acl
+        override_acl(self.user, categories_acl)
 
     def test_cant_see(self):
-        """has no permission to see forum"""
+        """has no permission to see category"""
         self.override_acl({
             'can_see': 0,
             'can_browse': 0,
@@ -542,7 +543,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 404)
 
     def test_cant_browse(self):
-        """has no permission to browse forum"""
+        """has no permission to browse category"""
         self.override_acl({
             'can_see': 1,
             'can_browse': 0,
@@ -552,7 +553,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 403)
 
     def test_can_browse_empty(self):
-        """has permission to browse forum, sees empty list"""
+        """has permission to browse category, sees empty list"""
         self.override_acl({
             'can_see': 1,
             'can_browse': 1,
@@ -575,20 +576,20 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
         other_moderated_title = "Test other user moderated thread"
         testutils.post_thread(
-            forum=self.forum, title=other_moderated_title, is_moderated=True)
+            category=self.category, title=other_moderated_title, is_moderated=True)
 
         other_title = "Test other user thread"
-        testutils.post_thread(forum=self.forum, title=other_title)
+        testutils.post_thread(category=self.category, title=other_title)
 
         owned_title = "Test authenticated user thread"
         testutils.post_thread(
-            forum=self.forum,
+            category=self.category,
             title=owned_title,
             poster=self.user)
 
         owned_moderated_title = "Test authenticated user moderated thread"
         testutils.post_thread(
-            forum=self.forum,
+            category=self.category,
             title=owned_moderated_title,
             poster=self.user,
             is_moderated=True)
@@ -613,7 +614,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
         test_title = "Test moderated thread"
         thread = testutils.post_thread(
-            forum=self.forum, title=test_title, is_moderated=True)
+            category=self.category, title=test_title, is_moderated=True)
 
         self.override_acl(test_acl)
         response = self.client.get(self.link)
@@ -622,7 +623,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
         test_title = "Test owned moderated thread"
         thread = testutils.post_thread(
-            forum=self.forum,
+            category=self.category,
             title=test_title,
             is_moderated=True,
             poster=self.user)
@@ -643,20 +644,20 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
         other_moderated_title = "Test other user moderated thread"
         testutils.post_thread(
-            forum=self.forum, title=other_moderated_title, is_moderated=True)
+            category=self.category, title=other_moderated_title, is_moderated=True)
 
         other_title = "Test other user thread"
-        testutils.post_thread(forum=self.forum, title=other_title)
+        testutils.post_thread(category=self.category, title=other_title)
 
         owned_title = "Test authenticated user thread"
         testutils.post_thread(
-            forum=self.forum,
+            category=self.category,
             title=owned_title,
             poster=self.user)
 
         owned_moderated_title = "Test authenticated user moderated thread"
         testutils.post_thread(
-            forum=self.forum,
+            category=self.category,
             title=owned_moderated_title,
             poster=self.user,
             is_moderated=True)
@@ -671,9 +672,9 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertIn('show-my-threads', response.content)
 
         self.override_acl(test_acl)
-        response = self.client.get(reverse('misago:forum', kwargs={
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+        response = self.client.get(reverse('misago:category', kwargs={
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
                 'show': 'my-threads',
             }))
 
@@ -693,15 +694,15 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         }
 
         not_moderated_title = "Not moderated thread"
-        testutils.post_thread(forum=self.forum, title=not_moderated_title)
+        testutils.post_thread(category=self.category, title=not_moderated_title)
 
         hidden_title = "Test moderated thread"
         testutils.post_thread(
-            forum=self.forum, title=hidden_title, is_moderated=True)
+            category=self.category, title=hidden_title, is_moderated=True)
 
         visible_title = "Test owned moderated thread"
         testutils.post_thread(
-            forum=self.forum,
+            category=self.category,
             title=visible_title,
             is_moderated=True,
             poster=self.user)
@@ -716,17 +717,17 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertNotIn('show-moderated-posts', response.content)
 
         self.override_acl(test_acl)
-        response = self.client.get(reverse('misago:forum', kwargs={
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+        response = self.client.get(reverse('misago:category', kwargs={
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
                 'show': 'moderated-threads',
             }))
         self.assertEqual(response.status_code, 302)
 
         self.override_acl(test_acl)
-        response = self.client.get(reverse('misago:forum', kwargs={
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+        response = self.client.get(reverse('misago:category', kwargs={
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
                 'show': 'moderated-posts',
             }))
         self.assertEqual(response.status_code, 302)
@@ -748,9 +749,9 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertIn('show-moderated-posts', response.content)
 
         self.override_acl(test_acl)
-        response = self.client.get(reverse('misago:forum', kwargs={
-                'forum_id': self.forum.id,
-                'forum_slug': self.forum.slug,
+        response = self.client.get(reverse('misago:category', kwargs={
+                'category_id': self.category.id,
+                'category_slug': self.category.slug,
                 'show': 'moderated-threads',
             }))
 
@@ -764,7 +765,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
     def test_anonymous_request(self):
         """view renders to anonymous users"""
         anon_title = "Hello Anon!"
-        testutils.post_thread(forum=self.forum, title=anon_title)
+        testutils.post_thread(category=self.category, title=anon_title)
 
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
@@ -772,7 +773,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
     def test_change_threads_labels(self):
         """moderation allows for changing threads labels"""
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
 
         test_acl = {
             'can_see': 1,
@@ -787,7 +788,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         ]
         for label in labels:
             label.save()
-            label.forums.add(self.forum)
+            label.categories.add(self.category)
 
         self.override_acl(test_acl)
         response = self.client.get(self.link)
@@ -879,8 +880,8 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertIn("Pin threads", response.content)
 
-        pinned = testutils.post_thread(self.forum, is_pinned=True)
-        thread = testutils.post_thread(self.forum, is_pinned=False)
+        pinned = testutils.post_thread(self.category, is_pinned=True)
+        thread = testutils.post_thread(self.category, is_pinned=False)
 
         # pin nothing
         self.override_acl(test_acl)
@@ -953,8 +954,8 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertIn("Approve threads", response.content)
 
-        thread = testutils.post_thread(self.forum, is_moderated=False)
-        moderated_thread = testutils.post_thread(self.forum, is_moderated=True)
+        thread = testutils.post_thread(self.category, is_moderated=False)
+        moderated_thread = testutils.post_thread(self.category, is_moderated=True)
 
         # approve approved thread
         self.override_acl(test_acl)
@@ -982,10 +983,10 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
     def test_move_threads(self):
         """moderation allows for moving threads"""
-        new_forum = Forum(name="New Forum",
-                          slug="new-forum",
-                          role="forum")
-        new_forum.insert_at(self.forum, save=True)
+        new_category = Category(name="New Category",
+                          slug="new-category",
+                          role='category')
+        new_category.insert_at(self.category, save=True)
 
         test_acl = {
             'can_see': 1,
@@ -999,7 +1000,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertIn("Move threads", response.content)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
 
         # see move threads form
         self.override_acl(test_acl)
@@ -1011,26 +1012,26 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         for thread in threads[:5]:
             self.assertIn(thread.title, response.content)
 
-        # submit form with non-existing forum
+        # submit form with non-existing category
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'move',
             'item': [t.pk for t in threads[:5]],
             'submit': '',
-            'new_forum': new_forum.pk + 1234
+            'new_category': new_category.pk + 1234
         })
         self.assertEqual(response.status_code, 200)
-        self.assertIn("Select valid forum.", response.content)
+        self.assertIn("Select valid category.", response.content)
 
         # attempt move to category
         self.override_acl(test_acl)
 
-        category = Forum.objects.all_forums().filter(role="category")[:1][0]
+        category = Category.objects.all_categories().filter(role='category')[:1][0]
         response = self.client.post(self.link, data={
             'action': 'move',
             'item': [t.pk for t in threads[:5]],
             'submit': '',
-            'new_forum': category.pk
+            'new_category': category.pk
         })
         self.assertEqual(response.status_code, 200)
         self.assertIn("You can&#39;t move threads to category.",
@@ -1039,37 +1040,37 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # attempt move to redirect
         self.override_acl(test_acl)
 
-        redirect = Forum.objects.all_forums().filter(role="redirect")[:1][0]
+        redirect = Category.objects.all_categories().filter(role="redirect")[:1][0]
         response = self.client.post(self.link, data={
             'action': 'move',
             'item': [t.pk for t in threads[:5]],
             'submit': '',
-            'new_forum': redirect.pk
+            'new_category': redirect.pk
         })
         self.assertEqual(response.status_code, 200)
         self.assertIn("You can&#39;t move threads to redirect.",
                       response.content)
 
-        # move to new_forum
+        # move to new_category
         self.override_acl(test_acl)
-        self.override_acl(test_acl, new_forum)
+        self.override_acl(test_acl, new_category)
         response = self.client.post(self.link, data={
             'action': 'move',
             'item': [t.pk for t in threads[:5]],
             'submit': '',
-            'new_forum': new_forum.pk
+            'new_category': new_category.pk
         })
         self.assertEqual(response.status_code, 302)
 
         self.override_acl(test_acl)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
-        self.assertIn("5 threads were moved to &quot;New Forum&quot;.",
+        self.assertIn("5 threads were moved to &quot;New Category&quot;.",
                       response.content)
 
-        for thread in new_forum.thread_set.all():
+        for thread in new_category.thread_set.all():
             self.assertIn(thread, threads[:5])
-        for thread in self.forum.thread_set.all():
+        for thread in self.category.thread_set.all():
             self.assertIn(thread, threads[5:])
 
     def test_merge_threads(self):
@@ -1086,7 +1087,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertIn("Merge threads", response.content)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
 
         # see merge threads form
         self.override_acl(test_acl)
@@ -1156,7 +1157,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertIn("Close threads", response.content)
         self.assertIn("Open threads", response.content)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
 
         # close threads
         self.override_acl(test_acl)
@@ -1221,7 +1222,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertIn("Reveal threads", response.content)
         self.assertIn("Hide threads", response.content)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
 
         # hide threads
         self.override_acl(test_acl)
@@ -1273,10 +1274,10 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
     def test_delete_threads(self):
         """moderation allows for deleting threads"""
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
 
-        self.forum.synchronize()
-        self.assertEqual(self.forum.threads, 10)
+        self.category.synchronize()
+        self.assertEqual(self.category.threads, 10)
 
         test_acl = {
             'can_see': 1,
@@ -1297,14 +1298,14 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 302)
         self.assertTrue(response['location'].endswith(self.link))
 
-        forum = Forum.objects.get(pk=self.forum.pk)
-        self.assertEqual(forum.threads, 0)
+        category = Category.objects.get(pk=self.category.pk)
+        self.assertEqual(category.threads, 0)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(60)]
+        threads = [testutils.post_thread(self.category) for t in xrange(60)]
 
-        second_page_link = reverse('misago:forum', kwargs={
-            'forum_id': self.forum.id,
-            'forum_slug': self.forum.slug,
+        second_page_link = reverse('misago:category', kwargs={
+            'category_id': self.category.id,
+            'category_slug': self.category.slug,
             'page': 2
         })
 
@@ -1315,8 +1316,8 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 302)
         self.assertTrue(response['location'].endswith(second_page_link))
 
-        forum = Forum.objects.get(pk=self.forum.pk)
-        self.assertEqual(forum.threads, 40)
+        category = Category.objects.get(pk=self.category.pk)
+        self.assertEqual(category.threads, 40)
 
         self.override_acl(test_acl)
         response = self.client.post(second_page_link, data={

+ 5 - 5
misago/threads/tests/-test_goto_views.py

@@ -1,5 +1,5 @@
 from misago.acl import add_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads import goto
@@ -10,11 +10,11 @@ class GotoViewsTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(GotoViewsTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.category.labels = []
 
-        self.thread = post_thread(self.forum)
-        add_acl(self.user, self.forum)
+        self.thread = post_thread(self.category)
+        add_acl(self.user, self.category)
         add_acl(self.user, self.thread)
 
     def test_goto_last(self):

+ 10 - 10
misago/threads/tests/-test_gotolists_views.py

@@ -1,6 +1,6 @@
 from misago.acl import add_acl
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.testutils import post_thread, reply_thread
@@ -12,10 +12,10 @@ class GotoListsViewsTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(GotoListsViewsTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='category')[:1][0]
+        self.category.labels = []
 
-        self.thread = post_thread(self.forum)
+        self.thread = post_thread(self.category)
 
     def override_acl(self, new_acl):
         new_acl.update({
@@ -24,13 +24,13 @@ class GotoListsViewsTests(AuthenticatedUserTestCase):
             'can_see_all_threads': True,
         })
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = new_acl
-        override_acl(self.user, forums_acl)
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = new_acl
+        override_acl(self.user, categories_acl)
 
-        self.forum.acl = {}
-        add_acl(self.user, self.forum)
+        self.category.acl = {}
+        add_acl(self.user, self.category)
 
     def test_moderated_list(self):
         """moderated posts list works"""

+ 12 - 12
misago/threads/tests/-test_moderatedcontent_view.py

@@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext as _
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import UserTestCase, AuthenticatedUserTestCase
 
 from misago.threads import testutils
@@ -17,14 +17,14 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
             'can_review_moderated_content': True,
         }
 
-        forums_acl = self.user.acl
+        categories_acl = self.user.acl
 
-        for forum in Forum.objects.all():
-            forums_acl['visible_forums'].append(forum.pk)
-            forums_acl['can_review_moderated_content'].append(forum.pk)
-            forums_acl['forums'][forum.pk] = new_acl
+        for category in Category.objects.all():
+            categories_acl['visible_categories'].append(category.pk)
+            categories_acl['can_review_moderated_content'].append(category.pk)
+            categories_acl['categories'][category.pk] = new_acl
 
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
     def test_cant_see_threads_list(self):
         """user has no permission to see moderated list"""
@@ -34,8 +34,8 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
 
     def test_empty_threads_list(self):
         """empty threads list is rendered"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        [testutils.post_thread(forum) for t in xrange(10)]
+        category = Category.objects.all_categories().filter(role="category")[:1][0]
+        [testutils.post_thread(category) for t in xrange(10)]
 
         self.override_acl();
         response = self.client.get(reverse('misago:moderated_content'))
@@ -44,14 +44,14 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
 
     def test_filled_threads_list(self):
         """filled threads list is rendered"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        category = Category.objects.all_categories().filter(role="category")[:1][0]
 
         threads = []
         for t in xrange(10):
-            threads.append(testutils.post_thread(forum, is_moderated=True))
+            threads.append(testutils.post_thread(category, is_moderated=True))
 
         for t in xrange(10):
-            threads.append(testutils.post_thread(forum))
+            threads.append(testutils.post_thread(category))
             testutils.reply_thread(threads[-1], is_moderated=True)
 
         self.override_acl();

+ 5 - 5
misago/threads/tests/-test_newthreads_view.py

@@ -1,7 +1,7 @@
 from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext as _
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import UserTestCase, AuthenticatedUserTestCase
 
 from misago.threads import testutils
@@ -16,8 +16,8 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
 
     def test_single_page_threads_list(self):
         """filled threads list is rendered"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        threads = [testutils.post_thread(forum) for t in xrange(10)]
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        threads = [testutils.post_thread(category) for t in xrange(10)]
 
         response = self.client.get(reverse('misago:new_threads'))
         self.assertEqual(response.status_code, 200)
@@ -45,8 +45,8 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
 
     def test_multipage_threads_list(self):
         """multipage threads list is rendered"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        threads = [testutils.post_thread(forum) for t in xrange(80)]
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        threads = [testutils.post_thread(category) for t in xrange(80)]
 
         response = self.client.get(reverse('misago:new_threads'))
         self.assertEqual(response.status_code, 200)

+ 10 - 10
misago/threads/tests/-test_post_views.py

@@ -1,7 +1,7 @@
 from django.core.urlresolvers import reverse
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.models import Thread, Post
@@ -12,12 +12,12 @@ class PostViewTestCase(AuthenticatedUserTestCase):
     def setUp(self):
         super(PostViewTestCase, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.category.labels = []
 
-        self.thread = post_thread(self.forum)
+        self.thread = post_thread(self.category)
 
-    def override_acl(self, new_acl, forum=None):
+    def override_acl(self, new_acl, category=None):
         new_acl.update({
             'can_see': True,
             'can_browse': True,
@@ -25,12 +25,12 @@ class PostViewTestCase(AuthenticatedUserTestCase):
             'can_see_own_threads': False
         })
 
-        forum = forum or self.forum
+        category = category or self.category
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(forum.pk)
-        forums_acl['forums'][forum.pk] = new_acl
-        override_acl(self.user, forums_acl)
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(category.pk)
+        categories_acl['categories'][category.pk] = new_acl
+        override_acl(self.user, categories_acl)
 
 
 class ApprovePostViewTests(PostViewTestCase):

+ 3 - 3
misago/threads/tests/-test_privatethread_view.py

@@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
 from django.utils import timezone
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads import testutils
@@ -13,8 +13,8 @@ class PrivateThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(PrivateThreadTests, self).setUp()
 
-        self.forum = Forum.objects.private_threads()
-        self.thread = testutils.post_thread(self.forum)
+        self.category = Category.objects.private_threads()
+        self.thread = testutils.post_thread(self.category)
 
     def test_anon_access_to_view(self):
         """anonymous user has no access to private thread"""

+ 7 - 7
misago/threads/tests/-test_privatethreads_view.py

@@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse
 from django.utils import timezone
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import UserTestCase, AuthenticatedUserTestCase
 
 from misago.threads import testutils
@@ -30,9 +30,9 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
         """private threads list displays threads user participates in"""
         override_acl(self.user, {'can_moderate_private_threads': False})
 
-        forum = Forum.objects.private_threads()
-        invisible_threads = [testutils.post_thread(forum) for t in xrange(10)]
-        visible_threads = [testutils.post_thread(forum) for t in xrange(10)]
+        category = Category.objects.private_threads()
+        invisible_threads = [testutils.post_thread(category) for t in xrange(10)]
+        visible_threads = [testutils.post_thread(category) for t in xrange(10)]
 
         for thread in visible_threads:
             ThreadParticipant.objects.set_owner(thread, self.user)
@@ -50,9 +50,9 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
         """private threads list displays threads with reports"""
         override_acl(self.user, {'can_moderate_private_threads': True})
 
-        forum = Forum.objects.private_threads()
-        invisible_threads = [testutils.post_thread(forum) for t in xrange(10)]
-        visible_threads = [testutils.post_thread(forum) for t in xrange(10)]
+        category = Category.objects.private_threads()
+        invisible_threads = [testutils.post_thread(category) for t in xrange(10)]
+        visible_threads = [testutils.post_thread(category) for t in xrange(10)]
 
         for thread in visible_threads:
             thread.has_reported_posts = True

+ 38 - 38
misago/threads/tests/-test_replythread_view.py

@@ -4,7 +4,7 @@ from django.conf import settings
 from django.core.urlresolvers import reverse
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.models import Thread
@@ -17,70 +17,70 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(ReplyThreadTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.thread = post_thread(self.forum)
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.thread = post_thread(self.category)
         self.link = reverse('misago:reply_thread', kwargs={
-            'forum_id': self.forum.id,
+            'category_id': self.category.id,
             'thread_id': self.thread.id,
         })
 
     def allow_reply_thread(self, extra_acl=None):
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 1,
             'can_see_all_threads': 1,
             'can_reply_threads': 2,
         }
         if extra_acl:
-            forums_acl['forums'][self.forum.pk].update(extra_acl)
-        override_acl(self.user, forums_acl)
+            categories_acl['categories'][self.category.pk].update(extra_acl)
+        override_acl(self.user, categories_acl)
 
     def test_cant_see(self):
-        """has no permission to see forum"""
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].remove(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        """has no permission to see category"""
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].remove(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 0,
             'can_browse': 0,
             'can_see_all_threads': 1,
             'can_reply_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 404)
 
     def test_cant_browse(self):
-        """has no permission to browse forum"""
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        """has no permission to browse category"""
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 0,
             'can_see_all_threads': 1,
             'can_reply_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 404)
 
-    def test_cant_reply_thread_in_locked_forum(self):
-        """can't post in closed forum"""
-        self.forum.is_closed = True
-        self.forum.save()
+    def test_cant_reply_thread_in_locked_category(self):
+        """can't post in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 1,
             'can_see_all_threads': 1,
             'can_reply_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
@@ -148,28 +148,28 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.thread = Thread.objects.get(id=self.thread.id)
 
         self.assertEqual(self.thread.replies, 1)
-        self.assertEqual(self.thread.forum_id, self.forum.pk)
+        self.assertEqual(self.thread.category_id, self.category.pk)
         self.assertEqual(self.thread.last_poster_id, updated_user.id)
         self.assertEqual(self.thread.last_poster_name, updated_user.username)
         self.assertEqual(self.thread.last_poster_slug, updated_user.slug)
 
         last_post = self.user.post_set.all()[:1][0]
-        self.assertEqual(last_post.forum_id, self.forum.pk)
+        self.assertEqual(last_post.category_id, self.category.pk)
         self.assertEqual(last_post.original, 'Hello, I am test reply!')
         self.assertEqual(last_post.poster_id, updated_user.id)
         self.assertEqual(last_post.poster_name, updated_user.username)
 
-        updated_forum = Forum.objects.get(id=self.forum.id)
-        self.assertEqual(updated_forum.threads, 1)
-        self.assertEqual(updated_forum.posts, 2)
-        self.assertEqual(updated_forum.last_thread_id, self.thread.id)
-        self.assertEqual(updated_forum.last_thread_title, self.thread.title)
-        self.assertEqual(updated_forum.last_thread_slug, self.thread.slug)
+        updated_category = Category.objects.get(id=self.category.id)
+        self.assertEqual(updated_category.threads, 1)
+        self.assertEqual(updated_category.posts, 2)
+        self.assertEqual(updated_category.last_thread_id, self.thread.id)
+        self.assertEqual(updated_category.last_thread_title, self.thread.title)
+        self.assertEqual(updated_category.last_thread_slug, self.thread.slug)
 
-        self.assertEqual(updated_forum.last_poster_id, updated_user.id)
-        self.assertEqual(updated_forum.last_poster_name,
+        self.assertEqual(updated_category.last_poster_id, updated_user.id)
+        self.assertEqual(updated_category.last_poster_name,
                          updated_user.username)
-        self.assertEqual(updated_forum.last_poster_slug, updated_user.slug)
+        self.assertEqual(updated_category.last_poster_slug, updated_user.slug)
 
     def test_can_close_replied_thread(self):
         """can close/open thread while replying to it"""

+ 38 - 38
misago/threads/tests/-test_startthread_view.py

@@ -7,7 +7,7 @@ from django.conf import settings
 from django.core.urlresolvers import reverse
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.models import Label, Thread
@@ -19,9 +19,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(StartThreadTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
         self.link = reverse('misago:start_thread', kwargs={
-            'forum_id': self.forum.id
+            'category_id': self.category.id
         })
 
         Label.objects.clear_cache()
@@ -30,59 +30,59 @@ class StartThreadTests(AuthenticatedUserTestCase):
         Label.objects.clear_cache()
 
     def allow_start_thread(self, extra_acl=None):
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 1,
             'can_start_threads': 1,
         }
 
         if extra_acl:
-            forums_acl['forums'][self.forum.pk].update(extra_acl)
-        override_acl(self.user, forums_acl)
+            categories_acl['categories'][self.category.pk].update(extra_acl)
+        override_acl(self.user, categories_acl)
 
     def test_cant_see(self):
-        """has no permission to see forum"""
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].remove(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        """has no permission to see category"""
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].remove(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 0,
             'can_browse': 0,
             'can_start_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 404)
 
     def test_cant_browse(self):
-        """has no permission to browse forum"""
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        """has no permission to browse category"""
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 0,
             'can_start_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
 
-    def test_cant_start_thread_in_locked_forum(self):
-        """can't post in closed forum"""
-        self.forum.is_closed = True
-        self.forum.save()
+    def test_cant_start_thread_in_locked_category(self):
+        """can't post in closed category"""
+        self.category.is_closed = True
+        self.category.save()
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = {
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = {
             'can_see': 1,
             'can_browse': 1,
             'can_start_threads': 1,
         }
-        override_acl(self.user, forums_acl)
+        override_acl(self.user, categories_acl)
 
         response = self.client.get(self.link, **self.ajax_header)
         self.assertEqual(response.status_code, 403)
@@ -136,7 +136,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
         self.assertEqual(updated_user.threads, 1)
         self.assertEqual(updated_user.posts, 1)
 
-        self.assertEqual(last_thread.forum_id, self.forum.pk)
+        self.assertEqual(last_thread.category_id, self.category.pk)
         self.assertEqual(last_thread.title, "Hello, I am test thread!")
         self.assertEqual(last_thread.starter_id, updated_user.id)
         self.assertEqual(last_thread.starter_name, updated_user.username)
@@ -146,22 +146,22 @@ class StartThreadTests(AuthenticatedUserTestCase):
         self.assertEqual(last_thread.last_poster_slug, updated_user.slug)
 
         last_post = self.user.post_set.all()[:1][0]
-        self.assertEqual(last_post.forum_id, self.forum.pk)
+        self.assertEqual(last_post.category_id, self.category.pk)
         self.assertEqual(last_post.original, 'Lorem ipsum dolor met!')
         self.assertEqual(last_post.poster_id, updated_user.id)
         self.assertEqual(last_post.poster_name, updated_user.username)
 
-        updated_forum = Forum.objects.get(id=self.forum.id)
-        self.assertEqual(updated_forum.threads, 1)
-        self.assertEqual(updated_forum.posts, 1)
-        self.assertEqual(updated_forum.last_thread_id, last_thread.id)
-        self.assertEqual(updated_forum.last_thread_title, last_thread.title)
-        self.assertEqual(updated_forum.last_thread_slug, last_thread.slug)
+        updated_category = Category.objects.get(id=self.category.id)
+        self.assertEqual(updated_category.threads, 1)
+        self.assertEqual(updated_category.posts, 1)
+        self.assertEqual(updated_category.last_thread_id, last_thread.id)
+        self.assertEqual(updated_category.last_thread_title, last_thread.title)
+        self.assertEqual(updated_category.last_thread_slug, last_thread.slug)
 
-        self.assertEqual(updated_forum.last_poster_id, updated_user.id)
-        self.assertEqual(updated_forum.last_poster_name,
+        self.assertEqual(updated_category.last_poster_id, updated_user.id)
+        self.assertEqual(updated_category.last_poster_name,
                          updated_user.username)
-        self.assertEqual(updated_forum.last_poster_slug, updated_user.slug)
+        self.assertEqual(updated_category.last_poster_slug, updated_user.slug)
 
     def test_start_closed_thread(self):
         """can post closed thread"""
@@ -215,7 +215,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
         field_name = '%s-label' % prefix
 
         label = Label.objects.create(name="Label", slug="label")
-        label.forums.add(self.forum)
+        label.categories.add(self.category)
 
         self.allow_start_thread({'can_change_threads_labels': 1})
         response = self.client.get(self.link, **self.ajax_header)

+ 34 - 32
misago/threads/tests/-test_thread_view.py

@@ -1,7 +1,7 @@
 from django.core.urlresolvers import reverse
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.models import Thread, Label
@@ -12,20 +12,20 @@ class ThreadViewTestCase(AuthenticatedUserTestCase):
     def setUp(self):
         super(ThreadViewTestCase, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.category.labels = []
 
-        self.thread = post_thread(self.forum)
+        self.thread = post_thread(self.category)
 
-    def override_acl(self, new_acl, forum=None):
-        forum = forum or self.forum
+    def override_acl(self, new_acl, category=None):
+        category = category or self.category
 
         new_acl.update({'can_see': True, 'can_browse': True})
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(forum.pk)
-        forums_acl['forums'][forum.pk] = new_acl
-        override_acl(self.user, forums_acl)
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(category.pk)
+        categories_acl['categories'][category.pk] = new_acl
+        override_acl(self.user, categories_acl)
 
     def reload_thread(self):
         self.thread = Thread.objects.get(id=self.thread.id)
@@ -44,7 +44,7 @@ class ThreadViewTests(ThreadViewTestCase):
         self.assertEqual(response.status_code, 404)
 
     def test_can_see_all_threads_false_owned_thread(self):
-        """user can see thread he started in private forum"""
+        """user can see thread he started in private category"""
         self.override_acl({
             'can_see_all_threads': False,
             'can_see_own_threads': True
@@ -78,12 +78,12 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         super(ThreadViewModerationTests, self).tearDown()
         Label.objects.clear_cache()
 
-    def override_acl(self, new_acl, forum=None):
+    def override_acl(self, new_acl, category=None):
         new_acl.update({
             'can_see_all_threads': True,
             'can_see_own_threads': False
         })
-        super(ThreadViewModerationTests, self).override_acl(new_acl, forum)
+        super(ThreadViewModerationTests, self).override_acl(new_acl, category)
 
     def test_label_thread(self):
         """its possible to set thread label"""
@@ -98,7 +98,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         self.assertNotIn("Thread actions", response.content)
 
         test_label = Label.objects.create(name="Foxtrot", slug="foxtrot")
-        test_label.forums.add(self.forum)
+        test_label.categories.add(self.category)
         Label.objects.clear_cache()
 
         self.override_acl({'can_change_threads_labels': 0})
@@ -122,9 +122,9 @@ class ThreadViewModerationTests(ThreadViewTestCase):
     def test_change_thread_label(self):
         """its possible to change thread label"""
         test_label = Label.objects.create(name="Foxtrot", slug="foxtrot")
-        test_label.forums.add(self.forum)
+        test_label.categories.add(self.category)
         other_label = Label.objects.create(name="Uniform", slug="uniform")
-        other_label.forums.add(self.forum)
+        other_label.categories.add(self.category)
 
         Label.objects.clear_cache()
 
@@ -155,7 +155,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
     def test_unlabel_thread(self):
         """its possible to unset thread label"""
         test_label = Label.objects.create(name="Foxtrot", slug="foxtrot")
-        test_label.forums.add(self.forum)
+        test_label.categories.add(self.category)
         Label.objects.clear_cache()
 
         self.thread.label = test_label
@@ -267,24 +267,26 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         response = self.client.post(self.thread.get_absolute_url(),
                                     data={'thread_action': 'move'})
         self.assertEqual(response.status_code, 200)
-        self.assertIn("Move thread to forum:", response.content)
+        self.assertIn("Move thread to category:", response.content)
 
-        new_forum = Forum(name="New Forum",
-                          slug="new-forum",
-                          role="forum")
-        new_forum.insert_at(self.forum.parent, save=True)
+        new_category = Category(
+            name="New Category",
+            slug="new-category",
+            role='forum'
+        )
+        new_category.insert_at(self.category.parent, save=True)
 
         self.override_acl({'can_move_threads': 1})
-        self.override_acl({'can_move_threads': 1}, new_forum)
+        self.override_acl({'can_move_threads': 1}, new_category)
         response = self.client.post(self.thread.get_absolute_url(), data={
             'thread_action': 'move',
-            'new_forum': unicode(new_forum.id),
+            'new_category': unicode(new_category.id),
             'submit': '1'
         })
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(self.reload_thread().forum, new_forum)
+        self.assertEqual(self.reload_thread().category, new_category)
 
-        # we made forum "empty", assert that board index renders
+        # we made category "empty", assert that board index renders
         response = self.client.get(reverse('misago:index'))
         self.assertEqual(response.status_code, 200)
 
@@ -301,7 +303,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         self.assertEqual(response.status_code, 302)
         self.assertTrue(self.reload_thread().is_hidden)
 
-        # we made forum "empty", assert that board index renders
+        # we made category "empty", assert that board index renders
         response = self.client.get(reverse('misago:index'))
         self.assertEqual(response.status_code, 200)
 
@@ -330,7 +332,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
                                     data={'thread_action': 'delete'})
         self.assertEqual(response.status_code, 302)
 
-        # we made forum empty, assert that board index renders
+        # we made category empty, assert that board index renders
         response = self.client.get(reverse('misago:index'))
         self.assertEqual(response.status_code, 200)
 
@@ -461,7 +463,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         response = self.client.post(self.thread.get_absolute_url(), data={
             'action': 'move',
             'item': [p.pk for p in posts],
-            'new_thread_url': self.forum.get_absolute_url(),
+            'new_thread_url': self.category.get_absolute_url(),
             'submit': ''
         })
         self.assertEqual(response.status_code, 200)
@@ -485,7 +487,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertIn('Move posts', response.content)
 
-        other_thread = post_thread(self.forum)
+        other_thread = post_thread(self.category)
 
         self.override_acl(test_acl)
         response = self.client.post(self.thread.get_absolute_url(), data={
@@ -543,7 +545,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         response = self.client.post(self.thread.get_absolute_url(), data={
             'action': 'split',
             'item': [p.pk for p in posts[:3]],
-            'forum': self.forum.id,
+            'category': self.category.id,
             'thread_title': 'Split thread',
             'submit': ''
         })
@@ -556,7 +558,7 @@ class ThreadViewModerationTests(ThreadViewTestCase):
         response = self.client.post(self.thread.get_absolute_url(), data={
             'action': 'split',
             'item': [posts[-1].pk],
-            'forum': self.forum.id,
+            'category': self.category.id,
             'thread_title': 'Split thread',
             'follow': ''
         })

+ 3 - 3
misago/threads/tests/-test_threadparticipants_views.py

@@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse
 from django.utils import timezone
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads import testutils
@@ -16,8 +16,8 @@ class ThreadParticipantsTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(ThreadParticipantsTests, self).setUp()
 
-        self.forum = Forum.objects.private_threads()
-        self.thread = testutils.post_thread(self.forum)
+        self.category = Category.objects.private_threads()
+        self.thread = testutils.post_thread(self.category)
 
     def test_participants_list(self):
         """participants list displays thread participants"""

+ 3 - 3
misago/threads/tests/-test_unreadthreads_view.py

@@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import UserTestCase, AuthenticatedUserTestCase
 
 from misago.threads import testutils
@@ -17,8 +17,8 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
 
     def test_filled_threads_list(self):
         """filled threads list is rendered"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        threads = [testutils.post_thread(forum) for t in xrange(10)]
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        threads = [testutils.post_thread(category) for t in xrange(10)]
 
         # only unread tracker threads are shown on unread list
         response = self.client.get(reverse('misago:unread_threads'))

+ 12 - 12
misago/threads/tests/test_counters.py

@@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.readtracker.models import ThreadRead
 from misago.users.testutils import AuthenticatedUserTestCase
 
@@ -18,14 +18,14 @@ class TestNewThreadsCount(AuthenticatedUserTestCase):
     def setUp(self):
         super(TestNewThreadsCount, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        self.category = Category.objects.all_categories().filter(role='category')[:1][0]
 
     def test_cast_to_int(self):
         """counter is castable to int"""
         counter = NewThreadsCount(self.user, {})
         self.assertEqual(int(counter), 0)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(42)]
+        threads = [testutils.post_thread(self.category) for t in xrange(42)]
         counter = NewThreadsCount(self.user, {})
         self.assertEqual(int(counter), 42)
 
@@ -34,7 +34,7 @@ class TestNewThreadsCount(AuthenticatedUserTestCase):
         counter = NewThreadsCount(self.user, {})
         self.assertFalse(counter)
 
-        threads = [testutils.post_thread(self.forum) for t in xrange(42)]
+        threads = [testutils.post_thread(self.category) for t in xrange(42)]
         counter = NewThreadsCount(self.user, {})
         self.assertTrue(counter)
 
@@ -69,7 +69,7 @@ class TestNewThreadsCount(AuthenticatedUserTestCase):
         self.assertEqual(counter.get_current_count_dict()['threads'], 0)
 
         # create 10 new threads
-        threads = [testutils.post_thread(self.forum) for t in xrange(10)]
+        threads = [testutils.post_thread(self.category) for t in xrange(10)]
         self.assertEqual(counter.get_current_count_dict()['threads'], 10)
 
         # create new counter
@@ -110,7 +110,7 @@ class TestSyncUnreadPrivateThreadsCount(AuthenticatedUserTestCase):
     def setUp(self):
         super(TestSyncUnreadPrivateThreadsCount, self).setUp()
 
-        self.forum = Forum.objects.private_threads()
+        self.category = Category.objects.private_threads()
         self.user.sync_unread_private_threads = True
 
     def test_user_with_no_threads(self):
@@ -118,7 +118,7 @@ class TestSyncUnreadPrivateThreadsCount(AuthenticatedUserTestCase):
         for i in range(5):
             # post 5 invisible threads
             testutils.post_thread(
-                self.forum, started_on=timezone.now() - timedelta(days=2))
+                self.category, started_on=timezone.now() - timedelta(days=2))
 
         sync_user_unread_private_threads_count(self.user)
         self.assertEqual(self.user.unread_private_threads, 0)
@@ -128,10 +128,10 @@ class TestSyncUnreadPrivateThreadsCount(AuthenticatedUserTestCase):
         for i in range(5):
             # post 5 invisible threads
             testutils.post_thread(
-                self.forum, started_on=timezone.now() - timedelta(days=2))
+                self.category, started_on=timezone.now() - timedelta(days=2))
 
         thread = testutils.post_thread(
-            self.forum, started_on=timezone.now() - timedelta(days=2))
+            self.category, started_on=timezone.now() - timedelta(days=2))
         thread.threadparticipant_set.create(user=self.user)
 
         sync_user_unread_private_threads_count(self.user)
@@ -142,15 +142,15 @@ class TestSyncUnreadPrivateThreadsCount(AuthenticatedUserTestCase):
         for i in range(5):
             # post 5 invisible threads
             testutils.post_thread(
-                self.forum, started_on=timezone.now() - timedelta(days=2))
+                self.category, started_on=timezone.now() - timedelta(days=2))
 
         thread = testutils.post_thread(
-            self.forum, started_on=timezone.now() - timedelta(days=2))
+            self.category, started_on=timezone.now() - timedelta(days=2))
         thread.threadparticipant_set.create(user=self.user)
 
         ThreadRead.objects.create(
             user=self.user,
-            forum=self.forum,
+            category=self.category,
             thread=thread,
             last_read_on=timezone.now() - timedelta(days=3))
 

+ 4 - 4
misago/threads/tests/test_event_model.py

@@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.utils import timezone
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.checksums import is_event_valid, update_event_checksum
 from misago.threads.models import Thread, Event
@@ -15,9 +15,9 @@ class EventModelTests(TestCase):
 
         datetime = timezone.now()
 
-        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.category = Category.objects.filter(role='forum')[:1][0]
         self.thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -31,7 +31,7 @@ class EventModelTests(TestCase):
     def test_is_event_valid(self):
         """event is_valid flag returns valid value"""
         event = Event.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             author=self.user,
             message="Lorem ipsum",

+ 4 - 4
misago/threads/tests/test_events.py

@@ -4,7 +4,7 @@ from django.test import TestCase
 from django.utils import timezone
 
 from misago.acl import add_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.events import record_event, add_events_to_posts
 from misago.threads.models import Thread, Event
@@ -18,9 +18,9 @@ class EventsAPITests(TestCase):
 
         datetime = timezone.now()
 
-        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.category = Category.objects.filter(role='forum')[:1][0]
         self.thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -31,7 +31,7 @@ class EventsAPITests(TestCase):
         self.thread.set_title("Test thread")
         self.thread.save()
 
-        add_acl(self.user, self.forum)
+        add_acl(self.user, self.category)
         add_acl(self.user, self.thread)
 
     def test_record_event(self):

+ 8 - 8
misago/threads/tests/test_events_view.py

@@ -1,7 +1,7 @@
 from django.core.urlresolvers import reverse
 
 from misago.acl.testutils import override_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads.models import Thread, Event
@@ -14,10 +14,10 @@ class EventsViewTestCase(AuthenticatedUserTestCase):
     def setUp(self):
         super(EventsViewTestCase, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.category.labels = []
 
-        self.thread = post_thread(self.forum)
+        self.thread = post_thread(self.category)
 
     def override_acl(self, new_acl):
         new_acl.update({
@@ -28,10 +28,10 @@ class EventsViewTestCase(AuthenticatedUserTestCase):
             'can_pin_threads': True
         })
 
-        forums_acl = self.user.acl
-        forums_acl['visible_forums'].append(self.forum.pk)
-        forums_acl['forums'][self.forum.pk] = new_acl
-        override_acl(self.user, forums_acl)
+        categories_acl = self.user.acl
+        categories_acl['visible_categories'].append(self.category.pk)
+        categories_acl['categories'][self.category.pk] = new_acl
+        override_acl(self.user, categories_acl)
 
     def test_hide_event(self):
         """its possible to hide event"""

+ 5 - 5
misago/threads/tests/test_goto.py

@@ -1,7 +1,7 @@
 from django.conf import settings
 
 from misago.acl import add_acl
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.readtracker import threadstracker
 from misago.users.testutils import AuthenticatedUserTestCase
 
@@ -27,11 +27,11 @@ class GotoTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(GotoTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.category.labels = []
 
-        self.thread = post_thread(self.forum)
-        add_acl(self.user, self.forum)
+        self.thread = post_thread(self.category)
+        add_acl(self.user, self.category)
         add_acl(self.user, self.thread)
 
     def test_get_thread_pages(self):

+ 12 - 14
misago/threads/tests/test_label_model.py

@@ -1,7 +1,5 @@
 from django.test import TestCase
-
-from misago.forums.models import Forum
-
+from misago.categories.models import Category
 from misago.threads.models import Label
 
 
@@ -31,9 +29,9 @@ class LabelsManagerTests(TestCase):
         for label in test_labels:
             self.assertEqual(db_labels[label.pk], label)
 
-    def test_get_forum_labels(self):
-        """get_forum_labels returns labels for forum"""
-        forum = Forum.objects.all_forums().filter(role='forum')[:1][0]
+    def test_get_category_labels(self):
+        """get_category_labels returns labels for category"""
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
 
         test_labels = (
             Label.objects.create(name="Label 1", slug="label-1"),
@@ -42,12 +40,12 @@ class LabelsManagerTests(TestCase):
             Label.objects.create(name="Label 4", slug="label-4"),
         )
 
-        test_labels[0].forums.add(forum)
-        test_labels[2].forums.add(forum)
+        test_labels[0].categories.add(category)
+        test_labels[2].categories.add(category)
 
-        forum_labels = Label.objects.get_forum_labels(forum)
-        self.assertEqual(len(forum_labels), 2)
-        self.assertIn(test_labels[0], forum_labels)
-        self.assertIn(test_labels[2], forum_labels)
-        self.assertNotIn(test_labels[1], forum_labels)
-        self.assertNotIn(test_labels[3], forum_labels)
+        category_labels = Label.objects.get_category_labels(category)
+        self.assertEqual(len(category_labels), 2)
+        self.assertIn(test_labels[0], category_labels)
+        self.assertIn(test_labels[2], category_labels)
+        self.assertNotIn(test_labels[1], category_labels)
+        self.assertNotIn(test_labels[3], category_labels)

+ 23 - 23
misago/threads/tests/test_labelsadmin_views.py

@@ -1,7 +1,7 @@
 from django.core.urlresolvers import reverse
 
 from misago.admin.testutils import AdminTestCase
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.models import Label
 
@@ -10,14 +10,14 @@ class LabelAdminViewsTests(AdminTestCase):
     def test_link_registered(self):
         """admin nav contains labels link"""
         response = self.client.get(
-            reverse('misago:admin:forums:nodes:index'))
-        self.assertIn(reverse('misago:admin:forums:labels:index'),
+            reverse('misago:admin:categories:nodes:index'))
+        self.assertIn(reverse('misago:admin:categories:labels:index'),
                       response.content)
 
     def test_list_view(self):
         """labels list view returns 200"""
         response = self.client.get(
-            reverse('misago:admin:forums:labels:index'))
+            reverse('misago:admin:categories:labels:index'))
 
         self.assertEqual(response.status_code, 200)
         self.assertIn('No thread labels', response.content)
@@ -25,61 +25,61 @@ class LabelAdminViewsTests(AdminTestCase):
     def test_new_view(self):
         """new label view has no showstoppers"""
         response = self.client.get(
-            reverse('misago:admin:forums:labels:new'))
+            reverse('misago:admin:categories:labels:new'))
         self.assertEqual(response.status_code, 200)
 
         response = self.client.post(
-            reverse('misago:admin:forums:labels:new'),
+            reverse('misago:admin:categories:labels:new'),
             data={
                 'name': 'Test Label',
                 'css_class': 'test_label',
-                'forums': [f.pk for f in Forum.objects.all_forums()],
+                'categories': [f.pk for f in Category.objects.all_categories()],
             })
         self.assertEqual(response.status_code, 302)
 
         response = self.client.get(
-            reverse('misago:admin:forums:labels:index'))
+            reverse('misago:admin:categories:labels:index'))
         self.assertEqual(response.status_code, 200)
         self.assertIn('Test Label', response.content)
         self.assertIn('test_label', response.content)
 
         test_label = Label.objects.get(slug='test-label')
-        self.assertEqual(len(test_label.forums.all()),
-                         len(Forum.objects.all_forums()))
-        for forum in Forum.objects.all_forums():
-            self.assertIn(forum, test_label.forums.all())
+        self.assertEqual(len(test_label.categories.all()),
+                         len(Category.objects.all_categories()))
+        for category in Category.objects.all_categories():
+            self.assertIn(category, test_label.categories.all())
 
     def test_edit_view(self):
         """edit label view has no showstoppers"""
         self.client.post(
-            reverse('misago:admin:forums:labels:new'),
+            reverse('misago:admin:categories:labels:new'),
             data={
                 'name': 'Test Label',
                 'css_class': 'test_label',
-                'forums': [f.pk for f in Forum.objects.all_forums()],
+                'categories': [f.pk for f in Category.objects.all_categories()],
             })
         test_label = Label.objects.get(slug='test-label')
 
         response = self.client.get(
-            reverse('misago:admin:forums:labels:edit',
+            reverse('misago:admin:categories:labels:edit',
                     kwargs={'label_id': test_label.pk}))
         self.assertEqual(response.status_code, 200)
         self.assertIn(test_label.name, response.content)
         self.assertIn(test_label.css_class, response.content)
 
         response = self.client.post(
-            reverse('misago:admin:forums:labels:edit',
+            reverse('misago:admin:categories:labels:edit',
                     kwargs={'label_id': test_label.pk}),
             data={
                 'name': 'Top Lel',
                 'css_class': 'test_lel',
-                'forums': [f.pk for f in Forum.objects.all_forums()],
+                'categories': [f.pk for f in Category.objects.all_categories()],
             })
         self.assertEqual(response.status_code, 302)
 
         test_label = Label.objects.get(slug='top-lel')
         response = self.client.get(
-            reverse('misago:admin:forums:labels:index'))
+            reverse('misago:admin:categories:labels:index'))
         self.assertEqual(response.status_code, 200)
         self.assertIn(test_label.name, response.content)
         self.assertIn(test_label.css_class, response.content)
@@ -87,22 +87,22 @@ class LabelAdminViewsTests(AdminTestCase):
     def test_delete_view(self):
         """delete label view has no showstoppers"""
         self.client.post(
-            reverse('misago:admin:forums:labels:new'),
+            reverse('misago:admin:categories:labels:new'),
             data={
                 'name': 'Test Label',
                 'css_class': 'test_label',
-                'forums': [f.pk for f in Forum.objects.all_forums()],
+                'categories': [f.pk for f in Category.objects.all_categories()],
             })
         test_label = Label.objects.get(slug='test-label')
 
         response = self.client.post(
-            reverse('misago:admin:forums:labels:delete',
+            reverse('misago:admin:categories:labels:delete',
                     kwargs={'label_id': test_label.pk}))
         self.assertEqual(response.status_code, 302)
 
-        self.client.get(reverse('misago:admin:forums:labels:index'))
+        self.client.get(reverse('misago:admin:categories:labels:index'))
         response = self.client.get(
-            reverse('misago:admin:forums:labels:index'))
+            reverse('misago:admin:categories:labels:index'))
         self.assertEqual(response.status_code, 200)
 
         self.assertNotIn(test_label.name, response.content)

+ 5 - 6
misago/threads/tests/test_participants.py

@@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.utils import timezone
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.models import Thread, ThreadParticipant, Post
 from misago.threads.participants import (thread_has_participants,
@@ -10,17 +10,16 @@ from misago.threads.participants import (thread_has_participants,
                                          set_thread_owner,
                                          set_user_unread_private_threads_sync,
                                          add_owner,
-                                         remove_participant
-                                         )
+                                         remove_participant)
 
 
 class ParticipantsTests(TestCase):
     def setUp(self):
         datetime = timezone.now()
 
-        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.category = Category.objects.filter(role='forum')[:1][0]
         self.thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -32,7 +31,7 @@ class ParticipantsTests(TestCase):
         self.thread.save()
 
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster_name='Tester',
             poster_ip='127.0.0.1',

+ 10 - 10
misago/threads/tests/test_post_model.py

@@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.utils import timezone
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.checksums import update_post_checksum
 from misago.threads.models import Thread, Post
@@ -17,9 +17,9 @@ class PostModelTests(TestCase):
 
         datetime = timezone.now()
 
-        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.category = Category.objects.filter(role='forum')[:1][0]
         self.thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -31,7 +31,7 @@ class PostModelTests(TestCase):
         self.thread.save()
 
         self.post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=self.user,
             poster_name=self.user.username,
@@ -58,7 +58,7 @@ class PostModelTests(TestCase):
         other_user = User.objects.create_user("Jeff", "Je@ff.com", "Pass.123")
 
         other_post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=other_user,
             poster_name=other_user.username,
@@ -73,7 +73,7 @@ class PostModelTests(TestCase):
             self.post.merge(other_post)
 
         other_thread = Thread.objects.create(
-            forum=self.forum,
+            category=self.category,
             started_on=timezone.now(),
             starter_name='Tester',
             starter_slug='tester',
@@ -82,7 +82,7 @@ class PostModelTests(TestCase):
             last_poster_slug='tester')
 
         other_post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=other_thread,
             poster=self.user,
             poster_name=self.user.username,
@@ -97,7 +97,7 @@ class PostModelTests(TestCase):
             self.post.merge(other_post)
 
         other_post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster_name=other_user.username,
             poster_ip='127.0.0.1',
@@ -115,7 +115,7 @@ class PostModelTests(TestCase):
     def test_merge(self):
         """merge method merges two posts into one"""
         other_post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=self.user,
             poster_name=self.user.username,
@@ -135,7 +135,7 @@ class PostModelTests(TestCase):
     def test_move(self):
         """move method moves post to other thread"""
         new_thread = Thread.objects.create(
-            forum=self.forum,
+            category=self.category,
             started_on=timezone.now(),
             starter_name='Tester',
             starter_slug='tester',

+ 3 - 3
misago/threads/tests/test_posts_moderation.py

@@ -1,4 +1,4 @@
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads import moderation, testutils
@@ -9,8 +9,8 @@ class PostsModerationTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(PostsModerationTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.thread = testutils.post_thread(self.forum)
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.thread = testutils.post_thread(self.category)
         self.post = testutils.reply_thread(self.thread)
 
     def reload_thread(self):

+ 4 - 4
misago/threads/tests/test_synchronizethreads.py

@@ -1,7 +1,7 @@
 from django.test import TestCase
 from django.utils.six import StringIO
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads import testutils
 from misago.threads.management.commands import synchronizethreads
@@ -20,9 +20,9 @@ class SynchronizeThreadsTests(TestCase):
 
     def test_threads_sync(self):
         """command synchronizes threads"""
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
 
-        threads = [testutils.post_thread(forum) for t in xrange(10)]
+        threads = [testutils.post_thread(category) for t in xrange(10)]
         for i, thread in enumerate(threads):
             [testutils.reply_thread(thread) for r in xrange(i)]
             thread.replies = 0
@@ -34,7 +34,7 @@ class SynchronizeThreadsTests(TestCase):
         command.execute(stdout=out)
 
         for i, thread in enumerate(threads):
-            db_thread = forum.thread_set.get(id=thread.id)
+            db_thread = category.thread_set.get(id=thread.id)
             self.assertEqual(db_thread.replies, i)
 
         command_output = out.getvalue().splitlines()[-1].strip()

+ 18 - 18
misago/threads/tests/test_thread_model.py

@@ -4,7 +4,7 @@ from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.utils import timezone
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.models import Thread, ThreadParticipant, Post, Event
 
@@ -13,9 +13,9 @@ class ThreadModelTests(TestCase):
     def setUp(self):
         datetime = timezone.now()
 
-        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.category = Category.objects.filter(role='forum')[:1][0]
         self.thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -27,7 +27,7 @@ class ThreadModelTests(TestCase):
         self.thread.save()
 
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster_name='Tester',
             poster_ip='127.0.0.1',
@@ -50,7 +50,7 @@ class ThreadModelTests(TestCase):
 
         datetime = timezone.now() + timedelta(5)
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=user,
             poster_name=user.username,
@@ -77,7 +77,7 @@ class ThreadModelTests(TestCase):
 
         # add moderated post
         moderated_post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=user,
             poster_name=user.username,
@@ -103,7 +103,7 @@ class ThreadModelTests(TestCase):
 
         # add hidden post
         hidden_post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=user,
             poster_name=user.username,
@@ -163,7 +163,7 @@ class ThreadModelTests(TestCase):
 
         # add event
         event = Event.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             author_name=user.username,
             author_slug=user.slug,
@@ -186,7 +186,7 @@ class ThreadModelTests(TestCase):
         datetime = timezone.now() + timedelta(5)
 
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=user,
             poster_name=user.username,
@@ -212,7 +212,7 @@ class ThreadModelTests(TestCase):
         datetime = timezone.now() + timedelta(5)
 
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster=user,
             poster_name=user.username,
@@ -231,15 +231,15 @@ class ThreadModelTests(TestCase):
         self.assertEqual(self.thread.last_poster_slug, user.slug)
 
     def test_move(self):
-        """move(new_forum) moves thread to other forum"""
-        # pick category instead of forum (so we don't have to create one)
-        new_forum = Forum.objects.filter(role="category")[:1][0]
+        """move(new_category) moves thread to other category"""
+        # pick category instead of category (so we don't have to create one)
+        new_category = Category.objects.filter(role='forum')[:1][0]
 
-        self.thread.move(new_forum)
-        self.assertEqual(self.thread.forum, new_forum)
+        self.thread.move(new_category)
+        self.assertEqual(self.thread.category, new_category)
 
         for post in self.thread.post_set.all():
-            self.assertEqual(post.forum_id, new_forum.id)
+            self.assertEqual(post.category_id, new_category.id)
 
     def test_merge(self):
         """merge(other_thread) moves other thread content to this thread"""
@@ -249,7 +249,7 @@ class ThreadModelTests(TestCase):
         datetime = timezone.now() + timedelta(5)
 
         other_thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -261,7 +261,7 @@ class ThreadModelTests(TestCase):
         other_thread.save()
 
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=other_thread,
             poster_name='Admin',
             poster_ip='127.0.0.1',

+ 4 - 4
misago/threads/tests/test_threadparticipant_model.py

@@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.utils import timezone
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 
 from misago.threads.models import Thread, ThreadParticipant, Post
 
@@ -11,9 +11,9 @@ class ThreadParticipantTests(TestCase):
     def setUp(self):
         datetime = timezone.now()
 
-        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.category = Category.objects.filter(role='forum')[:1][0]
         self.thread = Thread(
-            forum=self.forum,
+            category=self.category,
             started_on=datetime,
             starter_name='Tester',
             starter_slug='tester',
@@ -25,7 +25,7 @@ class ThreadParticipantTests(TestCase):
         self.thread.save()
 
         post = Post.objects.create(
-            forum=self.forum,
+            category=self.category,
             thread=self.thread,
             poster_name='Tester',
             poster_ip='127.0.0.1',

+ 13 - 13
misago/threads/tests/test_threads_moderation.py

@@ -1,4 +1,4 @@
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.users.testutils import AuthenticatedUserTestCase
 
 from misago.threads import moderation, testutils
@@ -9,8 +9,8 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
     def setUp(self):
         super(ThreadsModerationTests, self).setUp()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.thread = testutils.post_thread(self.forum)
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.thread = testutils.post_thread(self.category)
         Label.objects.clear_cache()
 
     def tearDown(self):
@@ -97,7 +97,7 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
 
     def test_approve_thread(self):
         """approve_thread approves moderated thread"""
-        thread = testutils.post_thread(self.forum, is_moderated=True)
+        thread = testutils.post_thread(self.category, is_moderated=True)
 
         self.assertTrue(thread.is_moderated)
         self.assertTrue(thread.first_post.is_moderated)
@@ -114,28 +114,28 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
 
     def test_move_thread(self):
         """moves_thread moves moderated thread to other froum"""
-        new_forum = Forum.objects.all_forums().filter(role="category")[:1][0]
+        new_category = Category.objects.all_categories().filter(role='forum')[:1][0]
 
-        self.assertEqual(self.thread.forum, self.forum)
+        self.assertEqual(self.thread.category, self.category)
         self.assertTrue(
-            moderation.move_thread(self.user, self.thread, new_forum))
+            moderation.move_thread(self.user, self.thread, new_category))
 
         self.reload_thread()
-        self.assertEqual(self.thread.forum, new_forum)
+        self.assertEqual(self.thread.category, new_category)
         self.assertTrue(self.thread.has_events)
         event = self.thread.event_set.last()
 
         self.assertIn("moved thread", event.message)
         self.assertEqual(event.icon, "arrow-right")
 
-    def test_move_thread_to_same_forum(self):
-        """moves_thread does not move thread to same forum it is in"""
-        self.assertEqual(self.thread.forum, self.forum)
+    def test_move_thread_to_same_category(self):
+        """moves_thread does not move thread to same category it is in"""
+        self.assertEqual(self.thread.category, self.category)
         self.assertFalse(
-            moderation.move_thread(self.user, self.thread, self.forum))
+            moderation.move_thread(self.user, self.thread, self.category))
 
         self.reload_thread()
-        self.assertEqual(self.thread.forum, self.forum)
+        self.assertEqual(self.thread.category, self.category)
         self.assertFalse(self.thread.has_events)
 
     def test_close_thread(self):

+ 3 - 3
misago/threads/tests/test_threadslist_view.py

@@ -133,9 +133,9 @@ class ActionsTests(AuthenticatedUserTestCase):
 
 class SortingTests(TestCase):
     def setUp(self):
-        self.sorting = Sorting('misago:forum', {
-            'forum_slug': "test-forum",
-            'forum_id': 42,
+        self.sorting = Sorting('misago:category', {
+            'category_slug': "test-category",
+            'category_id': 42,
         })
 
     def test_clean_kwargs_removes_default_sorting(self):

+ 7 - 7
misago/threads/testutils.py

@@ -7,13 +7,13 @@ from misago.core.utils import slugify
 from misago.threads.models import Thread, Post
 
 
-def post_thread(forum, title='Test thread', poster='Tester', is_pinned=False,
-                is_moderated=False, is_hidden=False, is_closed=False,
-                started_on=None):
+def post_thread(category, title='Test thread', poster='Tester',
+                is_pinned=False, is_moderated=False, is_hidden=False,
+                is_closed=False, started_on=None):
     started_on = started_on or timezone.now()
 
     kwargs = {
-        'forum': forum,
+        'category': category,
         'title': title,
         'slug': slugify(title),
         'started_on': started_on,
@@ -56,7 +56,7 @@ def reply_thread(thread, poster="Tester", message='I am test message',
     posted_on = posted_on or thread.last_post_on + timedelta(minutes=5)
 
     kwargs = {
-        'forum': thread.forum,
+        'category': thread.category,
         'thread': thread,
         'original': message,
         'parsed': message,
@@ -78,7 +78,7 @@ def reply_thread(thread, poster="Tester", message='I am test message',
     post = Post.objects.create(**kwargs)
     thread.synchronize()
     thread.save()
-    thread.forum.synchronize()
-    thread.forum.save()
+    thread.category.synchronize()
+    thread.category.save()
 
     return post

+ 2 - 2
misago/threads/threadtypes/__init__.py

@@ -7,8 +7,8 @@ from django.utils.translation import ugettext_lazy as _
 class ThreadTypeBase(object):
     type_name = 'undefined'
 
-    def get_forum_name(self, forum):
-        return forum.name
+    def get_category_name(self, category):
+        return category.name
 
 
 def load_types(types_list):

+ 9 - 9
misago/threads/threadtypes/forumthread.py

@@ -3,28 +3,28 @@ from django.core.urlresolvers import reverse
 from misago.threads.threadtypes import ThreadTypeBase
 
 
-class ForumThread(ThreadTypeBase):
-    type_name = 'forum'
+class CategoryThread(ThreadTypeBase):
+    type_name = 'category'
 
-    def get_forum_absolute_url(self, forum):
-            return reverse('misago:%s' % forum.role, kwargs={
-                'forum_id': forum.id, 'forum_slug': forum.slug
+    def get_category_absolute_url(self, category):
+            return reverse('misago:%s' % category.role, kwargs={
+                'category_id': category.id, 'category_slug': category.slug
             })
 
-    def get_new_thread_url(self, forum):
+    def get_new_thread_url(self, category):
         return reverse('misago:thread_new', kwargs={
-            'forum_id': forum.id, 'forum_slug': forum.slug
+            'category_id': category.id, 'category_slug': category.slug
         })
 
     def get_reply_url(self, thread):
         return reverse('misago:reply_thread', kwargs={
-            'forum_id': thread.forum.id,
+            'category_id': thread.category.id,
             'thread_id': thread.id,
         })
 
     def get_edit_post_url(self, post):
         return reverse('misago:edit_post', kwargs={
-            'forum_id': post.forum_id,
+            'category_id': post.category_id,
             'thread_id': post.thread_id,
             'post_id': post.id
         })

+ 4 - 4
misago/threads/threadtypes/privatethread.py

@@ -7,13 +7,13 @@ from misago.threads.threadtypes import ThreadTypeBase
 class PrivateThread(ThreadTypeBase):
     type_name = 'private_threads'
 
-    def get_forum_name(self, forum):
+    def get_category_name(self, category):
         return _('Private Threads')
 
-    def get_forum_absolute_url(self, forum):
+    def get_category_absolute_url(self, category):
         return reverse('misago:private_threads')
 
-    def get_new_thread_url(self, forum):
+    def get_new_thread_url(self, category):
         return reverse('misago:private_thread_new')
 
     def get_reply_url(self, thread):
@@ -23,7 +23,7 @@ class PrivateThread(ThreadTypeBase):
 
     def get_edit_post_url(self, post):
         return reverse('misago:edit_private_post', kwargs={
-            'forum_id': post.forum_id,
+            'category_id': post.category_id,
             'thread_id': post.thread_id,
             'post_id': post.id
         })

+ 1 - 1
misago/threads/threadtypes/report.py

@@ -7,5 +7,5 @@ from misago.threads.threadtypes import ThreadTypeBase
 class Report(ThreadTypeBase):
     type_name = 'reports'
 
-    def get_forum_name(self, forum):
+    def get_category_name(self, category):
         return _('Reports')

+ 13 - 13
misago/threads/urls/threads.py

@@ -1,17 +1,17 @@
 from django.conf.urls import patterns, include, url
 
 
-# forum view
-from misago.threads.views.threads import ForumView
+# category view
+from misago.threads.views.threads import CategoryView
 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+)/(?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'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/(?P<page>\d+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/sort-(?P<sort>[\w-]+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/sort-(?P<sort>[\w-]+)/(?P<page>\d+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/show-(?P<show>[\w-]+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/show-(?P<show>[\w-]+)/(?P<page>\d+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/sort-(?P<sort>[\w-]+)/show-(?P<show>[\w-]+)/$', CategoryView.as_view(), name='category'),
+    url(r'^category/(?P<category_slug>[\w\d-]+)-(?P<category_id>\d+)/sort-(?P<sort>[\w-]+)/show-(?P<show>[\w-]+)/(?P<page>\d+)/$', CategoryView.as_view(), name='category'),
 )
 
 
@@ -66,9 +66,9 @@ urlpatterns += patterns('',
 # posting views
 from misago.threads.views.threads import PostingView
 urlpatterns += patterns('',
-    url(r'^start-thread/(?P<forum_id>\d+)/$', PostingView.as_view(), name='start_thread'),
-    url(r'^reply-thread/(?P<forum_id>\d+)/(?P<thread_id>\d+)/$', PostingView.as_view(), name='reply_thread'),
-    url(r'^edit-post/(?P<forum_id>\d+)/(?P<thread_id>\d+)/(?P<post_id>\d+)/edit/$', PostingView.as_view(), name='edit_post'),
+    url(r'^start-thread/(?P<category_id>\d+)/$', PostingView.as_view(), name='start_thread'),
+    url(r'^reply-thread/(?P<category_id>\d+)/(?P<thread_id>\d+)/$', PostingView.as_view(), name='reply_thread'),
+    url(r'^edit-post/(?P<category_id>\d+)/(?P<thread_id>\d+)/(?P<post_id>\d+)/edit/$', PostingView.as_view(), name='edit_post'),
 )
 
 

+ 1 - 1
misago/threads/views/generic/__init__.py

@@ -7,4 +7,4 @@ from misago.threads.views.generic.posting import *
 from misago.threads.views.generic.post import *
 from misago.threads.views.generic.thread import *
 from misago.threads.views.generic.threads import *
-from misago.threads.views.generic.forum import *
+from misago.threads.views.generic.category import *

+ 38 - 37
misago/threads/views/generic/base.py

@@ -3,46 +3,47 @@ from django.shortcuts import render
 from django.views.generic import View
 
 from misago.acl import add_acl
+from misago.categories.models import Category
+from misago.categories.permissions import (allow_see_category,
+                                           allow_browse_category)
 from misago.core.shortcuts import get_object_or_404, validate_slug
-from misago.forums.models import Forum
-from misago.forums.permissions import allow_see_forum, allow_browse_forum
 
 from misago.threads.models import Thread, Post
 from misago.threads.permissions import (allow_see_thread, allow_see_post,
                                         exclude_invisible_posts)
 
 
-__all__ = ['ForumMixin', 'ThreadMixin', 'PostMixin', 'ViewBase']
+__all__ = ['CategoryMixin', 'ThreadMixin', 'PostMixin', 'ViewBase']
 
 
-class ForumMixin(object):
+class CategoryMixin(object):
     """
-    Mixin for getting forums
+    Mixin for getting categories
     """
-    def get_forum(self, request, lock=False, **kwargs):
-        forum = self.fetch_forum(request, lock, **kwargs)
-        self.check_forum_permissions(request, forum)
+    def get_category(self, request, lock=False, **kwargs):
+        category = self.fetch_category(request, lock, **kwargs)
+        self.check_category_permissions(request, category)
 
-        if kwargs.get('forum_slug'):
-            validate_slug(forum, kwargs.get('forum_slug'))
+        if kwargs.get('category_slug'):
+            validate_slug(category, kwargs.get('category_slug'))
 
-        return forum
+        return category
 
-    def fetch_forum(self, request, lock=False, **kwargs):
-        queryset = Forum.objects
+    def fetch_category(self, request, lock=False, **kwargs):
+        queryset = Category.objects
         if lock:
             queryset = queryset.select_for_update()
 
         return get_object_or_404(
-            queryset, id=kwargs.get('forum_id'), role='forum')
+            queryset, id=kwargs.get('category_id'), role='forum')
 
-    def check_forum_permissions(self, request, forum):
-        if forum.special_role:
+    def check_category_permissions(self, request, category):
+        if category.special_role:
             raise Http404()
 
-        add_acl(request.user, forum)
-        allow_see_forum(request.user, forum)
-        allow_browse_forum(request.user, forum)
+        add_acl(request.user, category)
+        allow_see_category(request.user, category)
+        allow_browse_category(request.user, category)
 
 
 class ThreadMixin(object):
@@ -65,31 +66,31 @@ class ThreadMixin(object):
             queryset = queryset.select_for_update()
 
         select_related = select_related or []
-        if not 'forum' in select_related:
-            select_related.append('forum')
+        if not 'category' in select_related:
+            select_related.append('category')
         queryset = queryset.select_related(*select_related)
 
         where = {'id': kwargs.get('thread_id')}
-        if 'forum_id' in kwargs:
-            where['forum_id'] = kwargs.get('forum_id')
+        if 'category_id' in kwargs:
+            where['category_id'] = kwargs.get('category_id')
         return get_object_or_404(queryset, **where)
 
     def check_thread_permissions(self, request, thread):
-        if thread.forum.special_role:
+        if thread.category.special_role:
             raise Http404()
 
-        add_acl(request.user, thread.forum)
+        add_acl(request.user, thread.category)
         add_acl(request.user, thread)
 
         allow_see_thread(request.user, thread)
-        allow_see_forum(request.user, thread.forum)
+        allow_see_category(request.user, thread.category)
 
 
 class PostMixin(object):
     def get_post(self, request, lock=False, **kwargs):
         post = self.fetch_post(request, lock, **kwargs)
 
-        post.thread.forum = post.forum
+        post.thread.category = post.category
 
         self.check_post_permissions(request, post)
         return post
@@ -101,8 +102,8 @@ class PostMixin(object):
             queryset = queryset.select_for_update()
 
         select_related = select_related or []
-        if not 'forum' in select_related:
-            select_related.append('forum')
+        if not 'category' in select_related:
+            select_related.append('category')
         if not 'thread' in select_related:
             select_related.append('thread')
         queryset = queryset.select_related(*select_related)
@@ -110,28 +111,28 @@ class PostMixin(object):
         where = {'id': kwargs.get('post_id')}
         if 'thread_id' in kwargs:
             where['thread_id'] = kwargs.get('thread_id')
-        if 'forum_id' in kwargs:
-            where['forum_id'] = kwargs.get('forum_id')
+        if 'category_id' in kwargs:
+            where['category_id'] = kwargs.get('category_id')
 
         return get_object_or_404(queryset, **where)
 
     def check_post_permissions(self, request, post):
-        if post.forum.special_role:
+        if post.category.special_role:
             raise Http404()
 
-        add_acl(request.user, post.forum)
+        add_acl(request.user, post.category)
         add_acl(request.user, post.thread)
         add_acl(request.user, post)
 
         allow_see_post(request.user, post)
         allow_see_thread(request.user, post.thread)
-        allow_see_forum(request.user, post.forum)
+        allow_see_category(request.user, post.category)
 
-    def exclude_invisible_posts(self, queryset, user, forum, thread):
-        return exclude_invisible_posts(queryset, user, forum)
+    def exclude_invisible_posts(self, queryset, user, category, thread):
+        return exclude_invisible_posts(queryset, user, category)
 
 
-class ViewBase(ForumMixin, ThreadMixin, PostMixin, View):
+class ViewBase(CategoryMixin, ThreadMixin, PostMixin, View):
     def process_context(self, request, context):
         """
         Simple hook for extending and manipulating template context.

+ 6 - 6
misago/threads/views/generic/events.py

@@ -35,22 +35,22 @@ class EventsView(ViewBase):
         @atomic
         def real_view(request, event_id):
             queryset = Event.objects.select_for_update()
-            queryset = queryset.select_related('forum', 'thread')
+            queryset = queryset.select_related('category', 'thread')
             event = get_object_or_404(queryset, id=event_id)
 
-            forum = event.forum
+            category = event.category
             thread = event.thread
-            thread.forum = forum
+            thread.category = category
 
-            self.check_forum_permissions(request, forum)
+            self.check_category_permissions(request, category)
             self.check_thread_permissions(request, thread)
 
             if request.POST.get('action') == 'toggle':
-                if not forum.acl.get('can_hide_events'):
+                if not category.acl.get('can_hide_events'):
                     raise PermissionDenied(_("You can't hide events."))
                 return toggle_event(request, event)
             elif request.POST.get('action') == 'delete':
-                if forum.acl.get('can_hide_events') != 2:
+                if category.acl.get('can_hide_events') != 2:
                     raise PermissionDenied(_("You can't delete events."))
                 return delete_event(request, event)
             else:

+ 4 - 4
misago/threads/views/generic/forum/__init__.py

@@ -1,5 +1,5 @@
 # flake8: noqa
-from misago.threads.views.generic.forum.actions import ForumActions
-from misago.threads.views.generic.forum.filtering import ForumFiltering
-from misago.threads.views.generic.forum.threads import ForumThreads
-from misago.threads.views.generic.forum.view import ForumView
+from misago.threads.views.generic.category.actions import CategoryActions
+from misago.threads.views.generic.category.filtering import CategoryFiltering
+from misago.threads.views.generic.category.threads import CategoryThreads
+from misago.threads.views.generic.category.view import CategoryView

+ 43 - 43
misago/threads/views/generic/forum/actions.py

@@ -4,7 +4,7 @@ from django.shortcuts import render
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
 
-from misago.forums.lists import get_forum_path
+from misago.categories.lists import get_category_path
 
 from misago.threads import moderation
 from misago.threads.forms.moderation import MergeThreadsForm, MoveThreadsForm
@@ -17,26 +17,26 @@ __all__ = ['ForumActions', 'ReloadAfterDelete']
 
 class ForumActions(Actions):
     def get_available_actions(self, kwargs):
-        self.forum = kwargs['forum']
+        self.category = kwargs['category']
 
         actions = []
 
-        if self.forum.acl['can_change_threads_labels'] == 2:
-            for label in self.forum.labels:
+        if self.category.acl['can_change_threads_labels'] == 2:
+            for label in self.category.labels:
                 actions.append({
                     'action': 'label:%s' % label.slug,
                     'icon': 'tag',
                     'name': _('Label as "%(label)s"') % {'label': label.name}
                 })
 
-            if self.forum.labels:
+            if self.category.labels:
                 actions.append({
                     'action': 'unlabel',
                     'icon': 'times-circle',
                     'name': _("Remove labels")
                 })
 
-        if self.forum.acl['can_pin_threads']:
+        if self.category.acl['can_pin_threads']:
             actions.append({
                 'action': 'pin',
                 'icon': 'star',
@@ -48,28 +48,28 @@ class ForumActions(Actions):
                 'name': _("Unpin threads")
             })
 
-        if self.forum.acl['can_review_moderated_content']:
+        if self.category.acl['can_review_moderated_content']:
             actions.append({
                 'action': 'approve',
                 'icon': 'check',
                 'name': _("Approve threads")
             })
 
-        if self.forum.acl['can_move_threads']:
+        if self.category.acl['can_move_threads']:
             actions.append({
                 'action': 'move',
                 'icon': 'arrow-right',
                 'name': _("Move threads")
             })
 
-        if self.forum.acl['can_merge_threads']:
+        if self.category.acl['can_merge_threads']:
             actions.append({
                 'action': 'merge',
                 'icon': 'reply-all',
                 'name': _("Merge threads")
             })
 
-        if self.forum.acl['can_close_threads']:
+        if self.category.acl['can_close_threads']:
             actions.append({
                 'action': 'open',
                 'icon': 'unlock-alt',
@@ -81,7 +81,7 @@ class ForumActions(Actions):
                 'name': _("Close threads")
             })
 
-        if self.forum.acl['can_hide_threads']:
+        if self.category.acl['can_hide_threads']:
             actions.append({
                 'action': 'unhide',
                 'icon': 'eye',
@@ -92,7 +92,7 @@ class ForumActions(Actions):
                 'icon': 'eye-slash',
                 'name': _("Hide threads")
             })
-        if self.forum.acl['can_hide_threads'] == 2:
+        if self.category.acl['can_hide_threads'] == 2:
             actions.append({
                 'action': 'delete',
                 'icon': 'times',
@@ -104,7 +104,7 @@ class ForumActions(Actions):
         return actions
 
     def action_label(self, request, threads, label_slug):
-        for label in self.forum.labels:
+        for label in self.category.labels:
             if label.slug == label_slug:
                 break
         else:
@@ -196,34 +196,34 @@ class ForumActions(Actions):
     move_threads_modal_template = 'misago/threads/move/modal.html'
 
     def action_move(self, request, threads):
-        form = MoveThreadsForm(acl=request.user.acl, forum=self.forum)
+        form = MoveThreadsForm(acl=request.user.acl, category=self.category)
 
         if 'submit' in request.POST:
             form = MoveThreadsForm(
-                request.POST, acl=request.user.acl, forum=self.forum)
+                request.POST, acl=request.user.acl, category=self.category)
             if form.is_valid():
-                new_forum = form.cleaned_data['new_forum']
+                new_category = form.cleaned_data['new_category']
                 with atomic():
 
                     for thread in threads:
-                        moderation.move_thread(request.user, thread, new_forum)
+                        moderation.move_thread(request.user, thread, new_category)
 
-                    self.forum.lock()
-                    new_forum.lock()
+                    self.category.lock()
+                    new_category.lock()
 
-                    self.forum.synchronize()
-                    self.forum.save()
-                    new_forum.synchronize()
-                    new_forum.save()
+                    self.category.synchronize()
+                    self.category.save()
+                    new_category.synchronize()
+                    new_category.save()
 
                 changed_threads = len(threads)
                 message = ungettext(
-                    '%(changed)d thread was moved to "%(forum)s".',
-                    '%(changed)d threads were moved to "%(forum)s".',
+                    '%(changed)d thread was moved to "%(category)s".',
+                    '%(changed)d threads were moved to "%(category)s".',
                 changed_threads)
                 messages.success(request, message % {
                     'changed': changed_threads,
-                    'forum': new_forum.name
+                    'category': new_category.name
                 })
 
                 return None # trigger threads list refresh
@@ -235,8 +235,8 @@ class ForumActions(Actions):
 
         return render(request, template, {
             'form': form,
-            'forum': self.forum,
-            'path': get_forum_path(self.forum),
+            'category': self.category,
+            'path': get_category_path(self.category),
             'threads': threads
         })
 
@@ -255,7 +255,7 @@ class ForumActions(Actions):
             if form.is_valid():
                 with atomic():
                     merged_thread = Thread()
-                    merged_thread.forum = self.forum
+                    merged_thread.category = self.category
                     merged_thread.set_title(
                         form.cleaned_data['merged_thread_title'])
                     merged_thread.starter_name = "-"
@@ -275,9 +275,9 @@ class ForumActions(Actions):
                     merged_thread.synchronize()
                     merged_thread.save()
 
-                    self.forum.lock()
-                    self.forum.synchronize()
-                    self.forum.save()
+                    self.category.lock()
+                    self.category.synchronize()
+                    self.category.save()
 
                 changed_threads = len(threads)
                 message = ungettext(
@@ -298,8 +298,8 @@ class ForumActions(Actions):
 
         return render(request, template, {
             'form': form,
-            'forum': self.forum,
-            'path': get_forum_path(self.forum),
+            'category': self.category,
+            'path': get_category_path(self.category),
             'threads': threads
         })
 
@@ -343,9 +343,9 @@ class ForumActions(Actions):
 
         if changed_threads:
             with atomic():
-                self.forum.lock()
-                self.forum.synchronize()
-                self.forum.save()
+                self.category.lock()
+                self.category.synchronize()
+                self.category.save()
 
             message = ungettext(
                 '%(changed)d thread was made visible.',
@@ -364,9 +364,9 @@ class ForumActions(Actions):
 
         if changed_threads:
             with atomic():
-                self.forum.lock()
-                self.forum.synchronize()
-                self.forum.save()
+                self.category.lock()
+                self.category.synchronize()
+                self.category.save()
 
         if changed_threads:
             message = ungettext(
@@ -386,9 +386,9 @@ class ForumActions(Actions):
 
         if changed_threads:
             with atomic():
-                self.forum.lock()
-                self.forum.synchronize()
-                self.forum.save()
+                self.category.lock()
+                self.category.synchronize()
+                self.category.save()
 
         if changed_threads:
             message = ungettext(

+ 9 - 9
misago/threads/views/generic/forum/filtering.py

@@ -4,12 +4,12 @@ from django.utils.translation import ugettext as _
 from misago.threads.views.generic.threads import ThreadsFiltering
 
 
-__all__ = ['ForumFiltering']
+__all__ = ['CategoryFiltering']
 
 
-class ForumFiltering(ThreadsFiltering):
-    def __init__(self, forum, link_name, link_params):
-        self.forum = forum
+class CategoryFiltering(ThreadsFiltering):
+    def __init__(self, category, link_name, link_params):
+        self.category = category
         self.link_name = link_name
         self.link_params = link_params.copy()
 
@@ -18,21 +18,21 @@ class ForumFiltering(ThreadsFiltering):
     def get_available_filters(self):
         filters = []
 
-        if self.forum.acl['can_see_all_threads']:
+        if self.category.acl['can_see_all_threads']:
             filters.append({
                 'type': 'my-threads',
                 'name': _("My threads"),
                 'is_label': False,
             })
 
-        if self.forum.acl['can_see_reports']:
+        if self.category.acl['can_see_reports']:
             filters.append({
                 'type': 'reported',
                 'name': _("With reported posts"),
                 'is_label': False,
             })
 
-        if self.forum.acl['can_review_moderated_content']:
+        if self.category.acl['can_review_moderated_content']:
             filters.extend(({
                 'type': 'moderated-threads',
                 'name': _("Moderated threads"),
@@ -44,7 +44,7 @@ class ForumFiltering(ThreadsFiltering):
                 'is_label': False,
             }))
 
-        for label in self.forum.labels:
+        for label in self.category.labels:
             filters.append({
                 'type': label.slug,
                 'name': label.name,
@@ -57,7 +57,7 @@ class ForumFiltering(ThreadsFiltering):
     def create_dicts(self):
         dicts = []
 
-        if self.forum.acl['can_see_all_threads']:
+        if self.category.acl['can_see_all_threads']:
             default_name = _("All threads")
         else:
             default_name = _("Your threads")

+ 10 - 10
misago/threads/views/generic/forum/threads.py

@@ -1,5 +1,5 @@
 from misago.core.shortcuts import paginate
-from misago.readtracker import forumstracker, threadstracker
+from misago.readtracker import categoriestracker, threadstracker
 
 from misago.threads.permissions import exclude_invisible_threads
 from misago.threads.views.generic.threads import Threads
@@ -9,11 +9,11 @@ __all__ = ['ForumThreads']
 
 
 class ForumThreads(Threads):
-    def __init__(self, user, forum):
+    def __init__(self, user, category):
         self.user = user
-        self.forum = forum
+        self.category = category
 
-        forumstracker.make_read_aware(user, forum)
+        categoriestracker.make_read_aware(user, category)
 
         self.pinned_count = 0
         self.filter_by = None
@@ -38,23 +38,23 @@ class ForumThreads(Threads):
             threads.append(thread)
 
         for thread in threads:
-            thread.forum = self.forum
+            thread.category = self.category
 
-        self.label_threads(threads, self.forum.labels)
+        self.label_threads(threads, self.category.labels)
         self.make_threads_read_aware(threads)
 
         return threads
 
     def get_queryset(self):
         queryset = exclude_invisible_threads(
-            self.forum.thread_set, self.user, self.forum)
+            self.category.thread_set, self.user, self.category)
         return self.filter_threads(queryset)
 
     def filter_threads(self, queryset):
         if self.filter_by == 'my-threads':
             return queryset.filter(starter_id=self.user.id)
         else:
-            if self.forum.acl['can_see_own_threads']:
+            if self.category.acl['can_see_own_threads']:
                 if self.user.is_authenticated():
                     queryset = queryset.filter(starter_id=self.user.id)
                 else:
@@ -66,11 +66,11 @@ class ForumThreads(Threads):
             elif self.filter_by == 'moderated-posts':
                 return queryset.filter(has_moderated_posts=True)
             else:
-                for label in self.forum.labels:
+                for label in self.category.labels:
                     if label.slug == self.filter_by:
                         return queryset.filter(label_id=label.pk)
                 else:
                     return queryset
 
     def make_threads_read_aware(self, threads):
-        threadstracker.make_threads_read_aware(self.user, threads, self.forum)
+        threadstracker.make_threads_read_aware(self.user, threads, self.category)

+ 18 - 18
misago/threads/views/generic/forum/view.py

@@ -1,13 +1,13 @@
 from django.shortcuts import redirect
 
+from misago.categories.lists import get_categories_list, get_category_path
 from misago.core.shortcuts import validate_slug
-from misago.forums.lists import get_forums_list, get_forum_path
-from misago.readtracker import forumstracker
+from misago.readtracker import categoriestracker
 
 from misago.threads.models import Label
-from misago.threads.views.generic.forum.actions import ForumActions
-from misago.threads.views.generic.forum.filtering import ForumFiltering
-from misago.threads.views.generic.forum.threads import ForumThreads
+from misago.threads.views.generic.category.actions import ForumActions
+from misago.threads.views.generic.category.filtering import ForumFiltering
+from misago.threads.views.generic.category.threads import ForumThreads
 from misago.threads.views.generic.threads import Sorting, ThreadsView
 
 
@@ -16,9 +16,9 @@ __all__ = ['ForumView']
 
 class ForumView(ThreadsView):
     """
-    Basic view for forum threads lists
+    Basic view for category threads lists
     """
-    template = 'misago/threads/forum.html'
+    template = 'misago/threads/category.html'
 
     Threads = ForumThreads
     Sorting = Sorting
@@ -26,15 +26,15 @@ class ForumView(ThreadsView):
     Actions = ForumActions
 
     def dispatch(self, request, *args, **kwargs):
-        forum = self.get_forum(request, **kwargs)
-        validate_slug(forum, kwargs['forum_slug'])
+        category = self.get_category(request, **kwargs)
+        validate_slug(category, kwargs['category_slug'])
 
-        forum.labels = Label.objects.get_forum_labels(forum)
+        category.labels = Label.objects.get_category_labels(category)
 
-        if forum.lft + 1 < forum.rght:
-            forum.subforums = get_forums_list(request.user, forum)
+        if category.lft + 1 < category.rght:
+            category.subcategories = get_categories_list(request.user, category)
         else:
-            forum.subforums = []
+            category.subcategories = []
 
         page_number = kwargs.pop('page', None)
         cleaned_kwargs = self.clean_kwargs(request, kwargs)
@@ -44,17 +44,17 @@ class ForumView(ThreadsView):
         sorting = self.Sorting(link_name, cleaned_kwargs)
         cleaned_kwargs = sorting.clean_kwargs(cleaned_kwargs)
 
-        filtering = self.Filtering(forum, link_name, cleaned_kwargs)
+        filtering = self.Filtering(category, link_name, cleaned_kwargs)
         cleaned_kwargs = filtering.clean_kwargs(cleaned_kwargs)
 
         if cleaned_kwargs != kwargs:
             return redirect(link_name, **cleaned_kwargs)
 
-        threads = self.Threads(request.user, forum)
+        threads = self.Threads(request.user, category)
         sorting.sort(threads)
         filtering.filter(threads)
 
-        actions = self.Actions(user=request.user, forum=forum)
+        actions = self.Actions(user=request.user, category=category)
         if request.method == 'POST':
             response = actions.handle_post(request, threads.get_queryset())
             if response:
@@ -64,8 +64,8 @@ class ForumView(ThreadsView):
             'link_name': link_name,
             'links_params': cleaned_kwargs,
 
-            'forum': forum,
-            'path': get_forum_path(forum),
+            'category': category,
+            'path': get_category_path(category),
 
             'threads': threads.list(page_number),
             'threads_count': threads.count(),

+ 10 - 9
misago/threads/views/generic/goto.py

@@ -19,14 +19,15 @@ class BaseGotoView(ViewBase):
                                   "should define get_redirect method")
 
     def dispatch(self, request, *args, **kwargs):
-        thread = self.fetch_thread(request, select_related=['forum'], **kwargs)
-        forum = thread.forum
+        relations = ['categorys']
+        thread = self.fetch_thread(request, select_related=relations, **kwargs)
+        categorys = thread.categorys
 
-        self.check_forum_permissions(request, forum)
+        self.check_categorys_permissions(request, categorys)
         self.check_thread_permissions(request, thread)
 
         posts_qs = self.exclude_invisible_posts(
-            thread.post_set, request.user, forum, thread)
+            thread.post_set, request.user, categorys, thread)
 
         return redirect(self.get_redirect(request.user, thread, posts_qs))
 
@@ -47,16 +48,16 @@ class GotoPostView(BaseGotoView):
 
     def dispatch(self, request, *args, **kwargs):
         post = self.fetch_post(
-            request, select_related=['thread', 'forum'], **kwargs)
-        forum = post.forum
+            request, select_related=['thread', 'categorys'], **kwargs)
+        categorys = post.categorys
         thread = post.thread
 
-        self.check_forum_permissions(request, forum)
-        thread.forum = forum
+        self.check_categorys_permissions(request, categorys)
+        thread.categorys = categorys
         self.check_thread_permissions(request, thread)
         self.check_post_permissions(request, post)
 
         posts_qs = self.exclude_invisible_posts(
-            thread.post_set, request.user, thread.forum, thread)
+            thread.post_set, request.user, thread.categorys, thread)
 
         return redirect(self.get_redirect(thread, posts_qs, post))

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

@@ -24,22 +24,22 @@ class ModeratedPostsListView(ViewBase):
         if not request.is_ajax():
             return not_allowed(request)
 
-        relations = ['forum']
+        relations = ['category']
         thread = self.fetch_thread(request, select_related=relations, **kwargs)
-        forum = thread.forum
+        category = thread.category
 
-        self.check_forum_permissions(request, forum)
+        self.check_category_permissions(request, category)
         self.check_thread_permissions(request, thread)
 
         self.allow_action(thread)
 
         posts_qs = self.exclude_invisible_posts(
-            thread.post_set, request.user, forum, thread)
+            thread.post_set, request.user, category, thread)
         posts_qs = self.filter_posts_queryset(posts_qs)
         final_posts_qs = posts_qs.select_related('poster').order_by('-id')[:15]
 
         return self.render(request, {
-            'forum': forum,
+            'category': category,
             'thread': thread,
 
             'posts_count': posts_qs.count(),

+ 10 - 10
misago/threads/views/generic/post.py

@@ -55,7 +55,7 @@ class PostView(ViewBase):
     def redirect_to_post(self, user, post):
         posts_qs = self.exclude_invisible_posts(post.thread.post_set,
                                                 user,
-                                                post.forum,
+                                                post.category,
                                                 post.thread)
         return redirect(goto.post(post.thread, posts_qs, post))
 
@@ -86,8 +86,8 @@ class ApprovePostView(PostView):
 
         post.thread.synchronize()
         post.thread.save()
-        post.forum.synchronize()
-        post.forum.save()
+        post.category.synchronize()
+        post.category.save()
 
 
 class UnhidePostView(PostView):
@@ -117,14 +117,14 @@ class DeletePostView(PostView):
 
         post.thread.synchronize()
         post.thread.save()
-        post.forum.synchronize()
-        post.forum.save()
+        post.category.synchronize()
+        post.category.save()
 
         posts_qs = self.exclude_invisible_posts(post.thread.post_set,
                                                 request.user,
-                                                post.forum,
+                                                post.category,
                                                 post.thread)
-        posts_qs = posts_qs.select_related('thread', 'forum')
+        posts_qs = posts_qs.select_related('thread', 'category')
 
         if post_id < post.thread.last_post_id:
             target_post = posts_qs.order_by('id').filter(id__gt=post_id)
@@ -132,9 +132,9 @@ class DeletePostView(PostView):
             target_post = posts_qs.order_by('-id').filter(id__lt=post_id)
 
         target_post = target_post[:1][0]
-        target_post.thread.forum = target_post.forum
+        target_post.thread.category = target_post.category
 
-        add_acl(request.user, target_post.forum)
+        add_acl(request.user, target_post.category)
         add_acl(request.user, target_post.thread)
         add_acl(request.user, target_post)
 
@@ -192,7 +192,7 @@ class ReportPostView(PostView):
 
     def render_alerts(self, request, post):
         return render(request, self.alerts_template, {
-            'forum': post.forum,
+            'category': post.category,
             'thread': post.thread,
             'post': post
         }).content

+ 19 - 19
misago/threads/views/generic/posting.py

@@ -6,9 +6,9 @@ from django.utils import html
 from django.utils.translation import ugettext as _
 from django.views.generic import View
 
+from misago.categories.lists import get_category_path
 from misago.core.errorpages import not_allowed
 from misago.core.exceptions import AjaxError
-from misago.forums.lists import get_forum_path
 
 from misago.threads import goto
 from misago.threads.posting import (PostingInterrupt, EditorFormset,
@@ -36,19 +36,19 @@ class PostingView(ViewBase):
         if is_submit:
             request.user.lock()
 
-        forum = None
+        category = None
         thread = None
         post = None
 
         if 'post_id' in kwargs:
             post = self.get_post(request, lock=is_submit, **kwargs)
-            forum = post.forum
+            category = post.category
             thread = post.thread
         elif 'thread_id' in kwargs:
             thread = self.get_thread(request, lock=is_submit, **kwargs)
-            forum = thread.forum
+            category = thread.category
         else:
-            forum = self.get_forum(request, lock=is_submit, **kwargs)
+            category = self.get_category(request, lock=is_submit, **kwargs)
 
         if thread:
             if post:
@@ -57,26 +57,26 @@ class PostingView(ViewBase):
                 mode = REPLY
         else:
             mode = START
-            thread = Thread(forum=forum)
+            thread = Thread(category=category)
 
         if not post:
-            post = Post(forum=forum, thread=thread)
+            post = Post(category=category, thread=thread)
 
-        return mode, forum, thread, post
+        return mode, category, thread, post
 
-    def allow_mode(self, user, mode, forum, thread, post):
+    def allow_mode(self, user, mode, category, thread, post):
         """
         Second step: check start/reply/edit permissions
         """
         if mode == START:
-            self.allow_start(user, forum)
+            self.allow_start(user, category)
         if mode == REPLY:
             self.allow_reply(user, thread)
         if mode == EDIT:
             self.allow_edit(user, post)
 
-    def allow_start(self, user, forum):
-        allow_start_thread(user, forum)
+    def allow_start(self, user, category):
+        allow_start_thread(user, category)
 
     def allow_reply(self, user, thread):
         allow_reply_thread(user, thread)
@@ -97,13 +97,13 @@ class PostingView(ViewBase):
     def real_dispatch(self, request, *args, **kwargs):
         mode_context = self.find_mode(request, *args, **kwargs)
         self.allow_mode(request.user, *mode_context)
-        mode, forum, thread, post = mode_context
+        mode, category, thread, post = mode_context
 
-        forum.labels = Label.objects.get_forum_labels(forum)
+        category.labels = Label.objects.get_category_labels(category)
         formset = EditorFormset(request=request,
                                 mode=mode,
                                 user=request.user,
-                                forum=forum,
+                                category=category,
                                 thread=thread,
                                 post=post)
 
@@ -128,15 +128,15 @@ class PostingView(ViewBase):
             'forms': formset.get_forms_list(),
             'main_forms': formset.get_main_forms(),
             'supporting_forms': formset.get_supporting_forms(),
-            'forum': forum,
-            'path': get_forum_path(forum),
+            'category': category,
+            'path': get_category_path(category),
             'thread': thread,
             'post': post,
             'api_url': request.path
         })
 
     def handle_submit(self, request, formset):
-        mode, forum, thread, post = (formset.mode, formset.forum,
+        mode, category, thread, post = (formset.mode, formset.category,
                                      formset.thread, formset.post)
         if mode == EDIT:
             message = _("Changes saved.")
@@ -149,7 +149,7 @@ class PostingView(ViewBase):
 
         posts_qs = self.exclude_invisible_posts(thread.post_set,
                                                 request.user,
-                                                forum,
+                                                category,
                                                 thread)
         post_url = goto.post(thread, posts_qs, post)
 

+ 24 - 24
misago/threads/views/generic/thread/postsactions.py

@@ -5,7 +5,7 @@ from django.shortcuts import redirect, render
 from django.utils import timezone
 from django.utils.translation import ungettext, ugettext_lazy, ugettext as _
 
-from misago.forums.lists import get_forum_path
+from misago.categories.lists import get_category_path
 
 from misago.threads import moderation
 from misago.threads.forms.moderation import MovePostsForm, SplitThreadForm
@@ -37,9 +37,9 @@ def changes_thread_state(f):
             self.thread.synchronize()
             self.thread.save()
 
-            self.forum.lock()
-            self.forum.synchronize()
-            self.forum.save()
+            self.category.lock()
+            self.category.synchronize()
+            self.category.save()
 
             return response
     return decorator
@@ -67,7 +67,7 @@ class PostsActions(ActionsBase):
 
     def get_available_actions(self, kwargs):
         self.thread = kwargs['thread']
-        self.forum = self.thread.forum
+        self.category = self.thread.category
 
         actions = []
 
@@ -79,28 +79,28 @@ class PostsActions(ActionsBase):
                     'name': _("Approve posts")
                 })
 
-        if self.forum.acl['can_merge_posts']:
+        if self.category.acl['can_merge_posts']:
             actions.append({
                 'action': 'merge',
                 'icon': 'compress',
                 'name': _("Merge posts into one")
             })
 
-        if self.forum.acl['can_move_posts']:
+        if self.category.acl['can_move_posts']:
             actions.append({
                 'action': 'move',
                 'icon': 'arrow-right',
                 'name': _("Move posts to other thread")
             })
 
-        if self.forum.acl['can_split_threads']:
+        if self.category.acl['can_split_threads']:
             actions.append({
                 'action': 'split',
                 'icon': 'code-fork',
                 'name': _("Split posts to new thread")
             })
 
-        if self.forum.acl['can_protect_posts']:
+        if self.category.acl['can_protect_posts']:
             actions.append({
                 'action': 'unprotect',
                 'icon': 'unlock-alt',
@@ -112,7 +112,7 @@ class PostsActions(ActionsBase):
                 'name': _("Protect posts")
             })
 
-        if self.forum.acl['can_hide_posts']:
+        if self.category.acl['can_hide_posts']:
             actions.append({
                 'action': 'unhide',
                 'icon': 'eye',
@@ -123,7 +123,7 @@ class PostsActions(ActionsBase):
                 'icon': 'eye-slash',
                 'name': _("Hide posts")
             })
-        if self.forum.acl['can_hide_posts'] == 2:
+        if self.category.acl['can_hide_posts'] == 2:
             actions.append({
                 'action': 'delete',
                 'icon': 'times',
@@ -201,10 +201,10 @@ class PostsActions(ActionsBase):
                 form.new_thread.synchronize()
                 form.new_thread.save()
 
-                if form.new_thread.forum != self.forum:
-                    form.new_thread.forum.lock()
-                    form.new_thread.forum.synchronize()
-                    form.new_thread.forum.save()
+                if form.new_thread.category != self.category:
+                    form.new_thread.category.lock()
+                    form.new_thread.category.synchronize()
+                    form.new_thread.category.save()
 
                 changed_posts = len(posts)
                 message = ungettext(
@@ -228,9 +228,9 @@ class PostsActions(ActionsBase):
 
         return render(request, template, {
             'form': form,
-            'forum': self.forum,
+            'category': self.category,
             'thread': self.thread,
-            'path': get_forum_path(self.forum),
+            'path': get_category_path(self.category),
 
             'posts': posts
         })
@@ -250,7 +250,7 @@ class PostsActions(ActionsBase):
             form = SplitThreadForm(request.POST, acl=request.user.acl)
             if form.is_valid():
                 split_thread = Thread()
-                split_thread.forum = form.cleaned_data['forum']
+                split_thread.category = form.cleaned_data['category']
                 split_thread.set_title(
                     form.cleaned_data['thread_title'])
                 split_thread.starter_name = "-"
@@ -268,10 +268,10 @@ class PostsActions(ActionsBase):
                 split_thread.synchronize()
                 split_thread.save()
 
-                if split_thread.forum != self.forum:
-                    split_thread.forum.lock()
-                    split_thread.forum.synchronize()
-                    split_thread.forum.save()
+                if split_thread.category != self.category:
+                    split_thread.category.lock()
+                    split_thread.category.synchronize()
+                    split_thread.category.save()
 
                 changed_posts = len(posts)
                 message = ungettext(
@@ -295,9 +295,9 @@ class PostsActions(ActionsBase):
 
         return render(request, template, {
             'form': form,
-            'forum': self.forum,
+            'category': self.category,
             'thread': self.thread,
-            'path': get_forum_path(self.forum),
+            'path': get_category_path(self.category),
 
             'posts': posts
         })

+ 29 - 29
misago/threads/views/generic/thread/threadactions.py

@@ -3,7 +3,7 @@ from django.db.transaction import atomic
 from django.shortcuts import redirect, render
 from django.utils.translation import ugettext as _
 
-from misago.forums.lists import get_forum_path
+from misago.categories.lists import get_category_path
 
 from misago.threads import moderation
 from misago.threads.forms.moderation import MoveThreadForm
@@ -20,13 +20,13 @@ class ThreadActions(ActionsBase):
 
     def get_available_actions(self, kwargs):
         self.thread = kwargs['thread']
-        self.forum = self.thread.forum
+        self.category = self.thread.category
 
         actions = []
 
         if self.thread.acl['can_change_label']:
-            self.forum.labels = Label.objects.get_forum_labels(self.forum)
-            for label in self.forum.labels:
+            self.category.labels = Label.objects.get_category_labels(self.category)
+            for label in self.category.labels:
                 if label.pk != self.thread.label_id:
                     name = _('Label as "%(label)s"') % {'label': label.name}
                     actions.append({
@@ -35,7 +35,7 @@ class ThreadActions(ActionsBase):
                         'name': name
                     })
 
-            if self.forum.labels and self.thread.label_id:
+            if self.category.labels and self.thread.label_id:
                 actions.append({
                     'action': 'unlabel',
                     'icon': 'times-circle',
@@ -111,7 +111,7 @@ class ThreadActions(ActionsBase):
         return actions
 
     def action_label(self, request, thread, label_slug):
-        for label in self.forum.labels:
+        for label in self.category.labels:
             if label.slug == label_slug:
                 break
         else:
@@ -141,28 +141,28 @@ class ThreadActions(ActionsBase):
     move_thread_modal_template = 'misago/thread/move/modal.html'
 
     def action_move(self, request, thread):
-        form = MoveThreadForm(acl=request.user.acl, forum=self.forum)
+        form = MoveThreadForm(acl=request.user.acl, category=self.category)
 
         if 'submit' in request.POST:
             form = MoveThreadForm(
-                request.POST, acl=request.user.acl, forum=self.forum)
+                request.POST, acl=request.user.acl, category=self.category)
             if form.is_valid():
-                new_forum = form.cleaned_data['new_forum']
+                new_category = form.cleaned_data['new_category']
 
                 with atomic():
-                    moderation.move_thread(request.user, thread, new_forum)
+                    moderation.move_thread(request.user, thread, new_category)
 
-                    self.forum.lock()
-                    new_forum.lock()
+                    self.category.lock()
+                    new_category.lock()
 
-                    self.forum.synchronize()
-                    self.forum.save()
-                    new_forum.synchronize()
-                    new_forum.save()
+                    self.category.synchronize()
+                    self.category.save()
+                    new_category.synchronize()
+                    new_category.save()
 
-                message = _('Thread was moved to "%(forum)s".')
+                message = _('Thread was moved to "%(category)s".')
                 messages.success(request, message % {
-                    'forum': new_forum.name
+                    'category': new_category.name
                 })
 
                 return None # trigger thread refresh
@@ -174,8 +174,8 @@ class ThreadActions(ActionsBase):
 
         return render(request, template, {
             'form': form,
-            'forum': self.forum,
-            'path': get_forum_path(self.forum),
+            'category': self.category,
+            'path': get_category_path(self.category),
             'thread': thread
         })
 
@@ -189,27 +189,27 @@ class ThreadActions(ActionsBase):
 
     def action_unhide(self, request, thread):
         moderation.unhide_thread(request.user, thread)
-        self.forum.synchronize()
-        self.forum.save()
+        self.category.synchronize()
+        self.category.save()
         messages.success(request, _("Thread was made visible."))
 
     def action_hide(self, request, thread):
         with atomic():
-            self.forum.lock()
+            self.category.lock()
             moderation.hide_thread(request.user, thread)
-            self.forum.synchronize()
-            self.forum.save()
+            self.category.synchronize()
+            self.category.save()
 
         messages.success(request, _("Thread was hid."))
 
     def action_delete(self, request, thread):
         with atomic():
-            self.forum.lock()
+            self.category.lock()
             moderation.delete_thread(request.user, thread)
-            self.forum.synchronize()
-            self.forum.save()
+            self.category.synchronize()
+            self.category.save()
 
         message = _('Thread "%(thread)s" was deleted.')
         messages.success(request, message % {'thread': thread.title})
 
-        return redirect(self.forum.get_absolute_url())
+        return redirect(self.category.get_absolute_url())

+ 13 - 13
misago/threads/views/generic/thread/view.py

@@ -3,8 +3,8 @@ from django.core.exceptions import PermissionDenied
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
+from misago.categories.lists import get_category_path
 from misago.core.shortcuts import validate_slug
-from misago.forums.lists import get_forum_path
 from misago.readtracker import threadstracker
 
 from misago.threads.events import add_events_to_posts
@@ -27,16 +27,16 @@ class ThreadView(ViewBase):
     PostsActions = PostsActions
     template = 'misago/thread/replies.html'
 
-    def get_posts(self, user, forum, thread, kwargs):
-        queryset = self.get_posts_queryset(user, forum, thread)
-        queryset = self.exclude_invisible_posts(queryset, user, forum, thread)
+    def get_posts(self, user, category, thread, kwargs):
+        queryset = self.get_posts_queryset(user, category, thread)
+        queryset = self.exclude_invisible_posts(queryset, user, category, thread)
         page = paginate(queryset, kwargs.get('page', 0),
                         settings.MISAGO_POSTS_PER_PAGE,
                         settings.MISAGO_THREAD_TAIL)
 
         posts = []
         for post in page.object_list:
-            post.forum = forum
+            post.category = category
             post.thread = thread
 
             add_acl(user, post)
@@ -50,7 +50,7 @@ class ThreadView(ViewBase):
 
         return page, posts
 
-    def get_posts_queryset(self, user, forum, thread):
+    def get_posts_queryset(self, user, category, thread):
         return thread.post_set.select_related(
             'poster',
             'poster__rank',
@@ -62,11 +62,11 @@ class ThreadView(ViewBase):
         allow_reply_thread(user, thread)
 
     def dispatch(self, request, *args, **kwargs):
-        relations = ['forum', 'starter', 'last_poster', 'first_post']
+        relations = ['category', 'starter', 'last_poster', 'first_post']
         thread = self.fetch_thread(request, select_related=relations, **kwargs)
-        forum = thread.forum
+        category = thread.category
 
-        self.check_forum_permissions(request, forum)
+        self.check_category_permissions(request, category)
         self.check_thread_permissions(request, thread)
 
         validate_slug(thread, kwargs['thread_slug'])
@@ -82,12 +82,12 @@ class ThreadView(ViewBase):
                 if response:
                     return response
             if posts_actions.query_key in request.POST:
-                queryset = self.get_posts_queryset(request.user, forum, thread)
+                queryset = self.get_posts_queryset(request.user, category, thread)
                 response = posts_actions.handle_post(request, queryset)
                 if response:
                     return response
 
-        page, posts = self.get_posts(request.user, forum, thread, kwargs)
+        page, posts = self.get_posts(request.user, category, thread, kwargs)
         make_posts_reports_aware(request.user, thread, posts)
 
         threadstracker.make_posts_read_aware(request.user, thread, posts)
@@ -105,8 +105,8 @@ class ThreadView(ViewBase):
                 'thread_id': thread.id, 'thread_slug': thread.slug
             },
 
-            'forum': forum,
-            'path': get_forum_path(forum),
+            'category': category,
+            'path': get_category_path(category),
 
             'thread': thread,
             'thread_actions': thread_actions,

+ 4 - 4
misago/threads/views/labelsadmin.py

@@ -9,7 +9,7 @@ from misago.threads.forms.admin import LabelForm
 
 
 class LabelsAdmin(generic.AdminBaseMixin):
-    root_link = 'misago:admin:forums:labels:index'
+    root_link = 'misago:admin:categories:labels:index'
     Model = Label
     Form = LabelForm
     templates_dir = 'misago/admin/labels'
@@ -17,9 +17,9 @@ class LabelsAdmin(generic.AdminBaseMixin):
 
     def handle_form(self, form, request, target):
         target.save()
-        target.forums.clear()
-        if form.cleaned_data.get('forums'):
-            target.forums.add(*[f for f in form.cleaned_data.get('forums')])
+        target.categories.clear()
+        if form.cleaned_data.get('categories'):
+            target.categories.add(*[f for f in form.cleaned_data.get('categories')])
         Label.objects.clear_cache()
 
         if self.message_submit:

+ 1 - 1
misago/threads/views/moderatedcontent.py

@@ -12,7 +12,7 @@ from misago.threads.permissions import exclude_invisible_threads
 class ModeratedContent(Threads):
     def get_queryset(self):
         queryset = Thread.objects.filter(has_moderated_posts=True)
-        queryset = queryset.select_related('forum')
+        queryset = queryset.select_related('category')
         queryset = exclude_invisible_threads(queryset, self.user)
         return queryset
 

+ 1 - 1
misago/threads/views/newthreads.py

@@ -29,7 +29,7 @@ class NewThreads(Threads):
             cutoff_date = self.user.new_threads_cutoff
 
         queryset = Thread.objects.filter(started_on__gte=cutoff_date)
-        queryset = queryset.select_related('forum')
+        queryset = queryset.select_related('category')
 
         tracked_threads = self.user.threadread_set.all()
         queryset = queryset.exclude(id__in=tracked_threads.values('thread_id'))

+ 16 - 16
misago/threads/views/privatethreads.py

@@ -7,10 +7,10 @@ from django.shortcuts import get_object_or_404, redirect
 from django.utils.translation import ugettext as _, ungettext
 
 from misago.acl import add_acl
+from misago.categories.models import Category
 from misago.core.errorpages import not_allowed
 from misago.core.exceptions import AjaxError
 from misago.core.uiviews import uiview
-from misago.forums.models import Forum
 from misago.users.decorators import deny_guests
 
 from misago.threads import participants
@@ -41,13 +41,13 @@ class PrivateThreadsMixin(object):
     """
     Mixin is used to make views use different permission tests
     """
-    def get_forum(self, request, lock=False, **kwargs):
-        forum = Forum.objects.private_threads()
-        add_acl(request.user, forum)
-        return forum
+    def get_category(self, request, lock=False, **kwargs):
+        category = Category.objects.private_threads()
+        add_acl(request.user, category)
+        return category
 
-    def check_forum_permissions(self, request, forum):
-        add_acl(request.user, forum)
+    def check_category_permissions(self, request, category):
+        add_acl(request.user, category)
         allow_use_private_threads(request.user)
 
     def fetch_thread(self, request, lock=False, select_related=None,
@@ -57,18 +57,18 @@ class PrivateThreadsMixin(object):
             queryset = queryset.select_for_update()
 
         select_related = select_related or []
-        if not 'forum' in select_related:
-            select_related.append('forum')
+        if not 'category' in select_related:
+            select_related.append('category')
         queryset = queryset.select_related(*select_related)
 
         where = {'id': kwargs.get('thread_id')}
         thread = get_object_or_404(queryset, **where)
-        if thread.forum.special_role != 'private_threads':
+        if thread.category.special_role != 'private_threads':
             raise Http404()
         return thread
 
     def check_thread_permissions(self, request, thread):
-        add_acl(request.user, thread.forum)
+        add_acl(request.user, thread.category)
         add_acl(request.user, thread)
 
         participants.make_thread_participants_aware(request.user, thread)
@@ -77,7 +77,7 @@ class PrivateThreadsMixin(object):
         allow_use_private_threads(request.user)
 
     def check_post_permissions(self, request, post):
-        add_acl(request.user, post.forum)
+        add_acl(request.user, post.category)
         add_acl(request.user, post.thread)
         add_acl(request.user, post)
 
@@ -87,7 +87,7 @@ class PrivateThreadsMixin(object):
         allow_see_private_thread(request.user, post.thread)
         allow_use_private_threads(request.user)
 
-    def exclude_invisible_posts(self, queryset, user, forum, thread):
+    def exclude_invisible_posts(self, queryset, user, category, thread):
         return queryset
 
 
@@ -95,7 +95,7 @@ class PrivateThreads(generic.Threads):
     fetch_pinned_threads = False
 
     def get_queryset(self):
-        threads_qs = Forum.objects.private_threads().thread_set
+        threads_qs = Category.objects.private_threads().thread_set
         return exclude_invisible_private_threads(threads_qs, self.user)
 
 
@@ -254,7 +254,7 @@ class ThreadParticipantsView(PrivateThreadsMixin, generic.ViewBase):
         participants_qs = participants_qs.select_related('user', 'user__rank')
 
         return self.render(request, {
-            'forum': thread.forum,
+            'category': thread.category,
             'thread': thread,
             'participants': participants_qs.order_by('-is_owner', 'user__slug')
         })
@@ -318,7 +318,7 @@ class AddThreadParticipantsView(BaseEditThreadParticipantView):
         participants_list = [p for p in participants_qs]
 
         participants_list_html = self.render(request, {
-            'forum': thread.forum,
+            'category': thread.category,
             'thread': thread,
             'participants': participants_list,
         }).content

+ 1 - 1
misago/threads/views/threads.py

@@ -1,7 +1,7 @@
 from misago.threads.views import generic
 
 
-class ForumView(generic.ForumView):
+class CategoryView(generic.CategoryView):
     pass
 
 

+ 1 - 1
misago/threads/views/unreadthreads.py

@@ -30,7 +30,7 @@ class UnreadThreads(Threads):
             cutoff_date = self.user.unread_threads_cutoff
 
         queryset = Thread.objects.filter(last_post_on__gte=cutoff_date)
-        queryset = queryset.select_related('forum')
+        queryset = queryset.select_related('category')
         queryset = queryset.filter(threadread__user=self.user)
         queryset = queryset.filter(
             threadread__last_read_on__lt=F('last_post_on'))

+ 1 - 3
misago/urls.py

@@ -14,10 +14,8 @@ urlpatterns = patterns('misago.core.views',
 urlpatterns += patterns('',
     url(r'^', include('misago.legal.urls')),
     url(r'^', include('misago.users.urls')),
-    url(r'^', include('misago.notifications.urls')),
-    url(r'^', include('misago.forums.urls')),
+    url(r'^', include('misago.categories.urls')),
     url(r'^', include('misago.threads.urls')),
-    url(r'^', include('misago.readtracker.urls')),
     # UI Server view that handles realtime updates of Misago UI
     url(r'^ui-server/$', 'misago.core.uiviews.uiserver', name="ui_server"),
 )

+ 5 - 3
misago/users/activepostersranking.py

@@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
 from django.db.models import Count
 from django.utils import timezone
 
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.core.cache import cache
 
 
@@ -24,12 +24,14 @@ def get_real_active_posts_ranking():
     tracked_period = settings.MISAGO_RANKING_LENGTH
     tracked_since = timezone.now() - timedelta(days=tracked_period)
 
-    ranked_forums = [forum.pk for forum in Forum.objects.all_forums()]
+    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__forum__in=ranked_forums)
+                               post__category__in=ranked_categories)
     queryset = queryset.annotate(score=Count('post'))
     queryset = queryset.select_related('user__rank')
     queryset = queryset.order_by('-score')

+ 4 - 4
misago/users/api/auth.py

@@ -71,10 +71,10 @@ def send_activation(request):
     if form.is_valid():
         requesting_user = form.user_cache
 
-        mail_subject = _("Activate %(user)s account on %(forum_title)s forums")
+        mail_subject = _("Activate %(user)s account on %(forum_name)s forums")
         subject_formats = {
             'user': requesting_user.username,
-            'forum_title': settings.forum_name,
+            'forum_name': settings.forum_name,
         }
         mail_subject = mail_subject % subject_formats
 
@@ -103,10 +103,10 @@ def send_password_form(request):
     if form.is_valid():
         requesting_user = form.user_cache
 
-        mail_subject = _("Change %(user)s password on %(forum_title)s forums")
+        mail_subject = _("Change %(user)s password on %(forum_name)s forums")
         subject_formats = {
             'user': requesting_user.username,
-            'forum_title': settings.forum_name,
+            'forum_name': settings.forum_name,
         }
         mail_subject = mail_subject % subject_formats
 

+ 2 - 2
misago/users/api/userendpoints/changeemail.py

@@ -16,8 +16,8 @@ def change_email_endpoint(request, pk=None):
         token = store_new_credential(
             request, 'email', form.cleaned_data['new_email'])
 
-        mail_subject = _("Confirm e-mail change on %(forum_title)s forums")
-        mail_subject = mail_subject % {'forum_title': settings.forum_name}
+        mail_subject = _("Confirm e-mail change on %(forum_name)s forums")
+        mail_subject = mail_subject % {'forum_name': settings.forum_name}
 
         # swap address with new one so email is sent to new address
         request.user.email = form.cleaned_data['new_email']

+ 2 - 2
misago/users/api/userendpoints/changepassword.py

@@ -16,8 +16,8 @@ def change_password_endpoint(request, pk=None):
         token = store_new_credential(
             request, 'password', form.cleaned_data['new_password'])
 
-        mail_subject = _("Confirm password change on %(forum_title)s forums")
-        mail_subject = mail_subject % {'forum_title': settings.forum_name}
+        mail_subject = _("Confirm password change on %(forum_name)s forums")
+        mail_subject = mail_subject % {'forum_name': settings.forum_name}
 
         mail_user(request, request.user, mail_subject,
                   'misago/emails/change_password',

+ 2 - 2
misago/users/api/userendpoints/create.py

@@ -74,8 +74,8 @@ def create_endpoint(request):
                                         set_default_avatar=True,
                                         **activation_kwargs)
 
-    mail_subject = _("Welcome on %(forum_title)s forums!")
-    mail_subject = mail_subject % {'forum_title': settings.forum_name}
+    mail_subject = _("Welcome on %(forum_name)s forums!")
+    mail_subject = mail_subject % {'forum_name': settings.forum_name}
 
     if settings.account_activation == 'none':
         authenticated_user = authenticate(

+ 0 - 1
misago/users/api/userendpoints/list.py

@@ -12,7 +12,6 @@ from misago.conf import settings
 from misago.core.apipaginator import ApiPaginator
 from misago.core.cache import cache
 from misago.core.shortcuts import get_int_or_404, get_object_or_404
-from misago.forums.models import Forum
 
 from misago.users.activepostersranking import get_active_posters_ranking
 from misago.users.models import Rank

+ 10 - 8
misago/users/api/users.py

@@ -12,8 +12,8 @@ from rest_framework.parsers import JSONParser, MultiPartParser
 from rest_framework.response import Response
 
 from misago.acl import add_acl
+from misago.categories.models import Category
 from misago.core.cache import cache
-from misago.forums.models import Forum
 from misago.threads.moderation.posts import hide_post
 from misago.threads.moderation.threads import hide_thread
 
@@ -204,24 +204,26 @@ class UserViewSet(viewsets.GenericViewSet):
                 if request.data.get('with_content'):
                     profile.delete_content()
                 else:
-                    forums_to_sync = set()
+                    categories_to_sync = set()
 
                     threads = profile.thread_set.select_related('first_post')
                     for thread in threads.filter(is_hidden=False).iterator():
-                        forums_to_sync.add(thread.forum_id)
+                        categories_to_sync.add(thread.category_id)
                         hide_thread(request.user, thread)
 
                     posts = profile.post_set.select_related('thread')
                     for post in posts.filter(is_hidden=False).iterator():
-                        forums_to_sync.add(post.forum_id)
+                        categories_to_sync.add(post.category_id)
                         hide_post(request.user, post)
                         post.thread.synchronize()
                         post.thread.save()
 
-                    forums = Forum.objects.filter(id__in=forums_to_sync)
-                    for forum in forums.iterator():
-                        forum.synchronize()
-                        forum.save()
+                    categories = Category.objects.filter(
+                        id__in=categories_to_sync).iterator()
+
+                    for category in categories:
+                        category.synchronize()
+                        category.save()
 
                 profile.delete()
 

+ 7 - 7
misago/users/tests/test_activepostersranking.py

@@ -1,8 +1,8 @@
 from django.contrib.auth import get_user_model
 
+from misago.categories.models import Category
 from misago.core import threadstore
 from misago.core.cache import cache
-from misago.forums.models import Forum
 from misago.threads.testutils import post_thread
 
 from misago.users.testutils import AuthenticatedUserTestCase
@@ -18,7 +18,7 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         cache.clear()
         threadstore.clear()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        self.category = Category.objects.all_categories(role='forum').filter()[:1][0]
 
     def tearDown(self):
         super(TestActivePostersRanking, self).tearDown()
@@ -42,7 +42,7 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         other_user.posts = 1
         other_user.save()
 
-        post_thread(self.forum, poster=other_user)
+        post_thread(self.category, poster=other_user)
 
         ranking = get_real_active_posts_ranking()
 
@@ -50,8 +50,8 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         self.assertEqual(ranking['users_count'], 1)
 
         # two users in ranking
-        post_thread(self.forum, poster=self.user)
-        post_thread(self.forum, poster=self.user)
+        post_thread(self.category, poster=self.user)
+        post_thread(self.category, poster=self.user)
 
         self.user.posts = 2
         self.user.save()
@@ -72,8 +72,8 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         self.assertEqual(ranking['users_count'], 0)
 
         # post something
-        post_thread(self.forum, poster=self.user)
-        post_thread(self.forum, poster=self.user)
+        post_thread(self.category, poster=self.user)
+        post_thread(self.category, poster=self.user)
 
         self.user.posts = 2
         self.user.save()

+ 5 - 5
misago/users/tests/test_useradmin_views.py

@@ -6,7 +6,7 @@ from django.core.urlresolvers import reverse
 
 from misago.acl.models import Role
 from misago.admin.testutils import AdminTestCase
-from misago.forums.models import Forum
+from misago.categories.models import Category
 from misago.threads.testutils import post_thread, reply_thread
 
 from misago.users.models import Ban, Rank
@@ -215,8 +215,8 @@ class UserAdminViewsTests(AdminTestCase):
         test_link = reverse('misago:admin:users:accounts:delete_threads',
                             kwargs={'user_id': test_user.pk})
 
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        [post_thread(forum, poster=test_user) for i in xrange(10)]
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        [post_thread(category, poster=test_user) for i in xrange(10)]
 
         response = self.client.post(test_link, **self.ajax_header)
         self.assertEqual(response.status_code, 200)
@@ -239,8 +239,8 @@ class UserAdminViewsTests(AdminTestCase):
         test_link = reverse('misago:admin:users:accounts:delete_posts',
                             kwargs={'user_id': test_user.pk})
 
-        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        thread = post_thread(forum)
+        category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        thread = post_thread(category)
         [reply_thread(thread, poster=test_user) for i in xrange(10)]
 
         response = self.client.post(test_link, **self.ajax_header)

+ 8 - 8
misago/users/tests/test_users_api.py

@@ -4,10 +4,10 @@ import json
 from django.contrib.auth import get_user_model
 
 from misago.acl.testutils import override_acl
+from misago.categories.models import Category
 from misago.conf import settings
 from misago.core import threadstore
 from misago.core.cache import cache
-from misago.forums.models import Forum
 from misago.threads.models import Thread, Post
 from misago.threads.testutils import post_thread
 
@@ -26,8 +26,8 @@ class ActivePostersListTests(AuthenticatedUserTestCase):
         cache.clear()
         threadstore.clear()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
-        self.forum.labels = []
+        self.category = Category.objects.all_categories().filter(role='forum')[:1][0]
+        self.category.labels = []
 
     def test_empty_list(self):
         """empty list is served"""
@@ -41,7 +41,7 @@ class ActivePostersListTests(AuthenticatedUserTestCase):
 
     def test_filled_list(self):
         """filled list is served"""
-        post_thread(self.forum, poster=self.user)
+        post_thread(self.category, poster=self.user)
         self.user.posts = 1
         self.user.save()
 
@@ -186,12 +186,12 @@ class SearchNamesListTests(AuthenticatedUserTestCase):
         self.assertIn(self.user.username, response.content)
 
 
-class UserForumOptionsTests(AuthenticatedUserTestCase):
+class UserCategoriesOptionsTests(AuthenticatedUserTestCase):
     """
     tests for user forum options RPC (POST to /api/users/1/forum-options/)
     """
     def setUp(self):
-        super(UserForumOptionsTests, self).setUp()
+        super(UserCategoriesOptionsTests, self).setUp()
         self.link = '/api/users/%s/forum-options/' % self.user.pk
 
     def test_empty_request(self):
@@ -364,9 +364,9 @@ class UserDeleteTests(AuthenticatedUserTestCase):
         self.threads = Thread.objects.count()
         self.posts = Post.objects.count()
 
-        self.forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        self.category = Categories.objects.all_categories().filter(role='forum')[:1][0]
 
-        post_thread(self.forum, poster=self.other_user)
+        post_thread(self.category, poster=self.other_user)
         self.other_user.posts = 1
         self.other_user.threads = 1
         self.other_user.save()

+ 15 - 15
misago/users/views/admin/users.py

@@ -7,10 +7,10 @@ from django.utils.translation import ugettext_lazy as _
 
 from misago.admin.auth import start_admin_session
 from misago.admin.views import generic
+from misago.categories.models import Category
 from misago.conf import settings
 from misago.core.mail import mail_users
 from misago.core.pgutils import batch_update
-from misago.forums.models import Forum
 from misago.threads.models import Thread
 
 from misago.users.avatars.dynamic import set_avatar as set_dynamic_avatar
@@ -101,9 +101,9 @@ class UsersList(UserAdmin, generic.ListView):
             queryset = User.objects.filter(pk__in=activated_users_pks)
             queryset.update(requires_activation=ACTIVATION_REQUIRED_NONE)
 
-            mail_subject = _("Your account on %(forum_title)s "
+            mail_subject = _("Your account on %(forum_name)s "
                              "forums has been activated")
-            subject_formats = {'forum_title': settings.forum_name}
+            subject_formats = {'forum_name': settings.forum_name}
             mail_subject = mail_subject % subject_formats
 
             mail_subject = mail_subject
@@ -326,21 +326,21 @@ class DeletionStep(UserAdmin, generic.ButtonView):
 
 class DeleteThreadsStep(DeletionStep):
     def execute_step(self, user):
-        recount_forums = set()
+        recount_categories = set()
 
         deleted_threads = 0
         is_completed = False
 
         for thread in user.thread_set.order_by('-id')[:50]:
-            recount_forums.add(thread.forum_id)
+            recount_categories.add(thread.category_id)
             with transaction.atomic():
                 thread.delete()
                 deleted_threads += 1
 
-        if recount_forums:
-            for forum in Forum.objects.filter(id__in=recount_forums):
-                forum.synchronize()
-                forum.save()
+        if recount_categories:
+            for category in Category.objects.filter(id__in=recount_categories):
+                category.synchronize()
+                category.save()
         else:
             is_completed = True
 
@@ -352,28 +352,28 @@ class DeleteThreadsStep(DeletionStep):
 
 class DeletePostsStep(DeletionStep):
     def execute_step(self, user):
-        recount_forums = set()
+        recount_categories = set()
         recount_threads = set()
 
         deleted_posts = 0
         is_completed = False
 
         for post in user.post_set.order_by('-id')[:50]:
-            recount_forums.add(post.forum_id)
+            recount_categories.add(post.category_id)
             recount_threads.add(post.thread_id)
             with transaction.atomic():
                 post.delete()
                 deleted_posts += 1
 
-        if recount_forums:
+        if recount_categories:
             changed_threads_qs = Thread.objects.filter(id__in=recount_threads)
             for thread in batch_update(changed_threads_qs, 50):
                 thread.synchronize()
                 thread.save()
 
-            for forum in Forum.objects.filter(id__in=recount_forums):
-                forum.synchronize()
-                forum.save()
+            for category in Category.objects.filter(id__in=recount_categories):
+                category.synchronize()
+                category.save()
         else:
             is_completed = True