Browse Source

Fixing detached head

Rafał Pitoń 11 years ago
parent
commit
b0c0454cf9
98 changed files with 2674 additions and 2674 deletions
  1. 1 1
      misago/__init__.py
  2. 8 8
      misago/acl/exceptions.py
  3. 30 30
      misago/acl/panels.py
  4. 6 6
      misago/apps/activation/urls.py
  5. 43 43
      misago/apps/admin/online/views.py
  6. 30 30
      misago/apps/admin/sections/__init__.py
  7. 16 16
      misago/apps/admin/team.py
  8. 14 14
      misago/apps/privatethreads/changelog.py
  9. 36 36
      misago/apps/privatethreads/delete.py
  10. 8 8
      misago/apps/privatethreads/details.py
  11. 40 40
      misago/apps/privatethreads/urls.py
  12. 40 40
      misago/apps/profiles/decorators.py
  13. 4 4
      misago/apps/profiles/details/profile.py
  14. 13 13
      misago/apps/profiles/details/urls.py
  15. 4 4
      misago/apps/profiles/followers/profile.py
  16. 15 15
      misago/apps/profiles/followers/urls.py
  17. 4 4
      misago/apps/profiles/follows/profile.py
  18. 15 15
      misago/apps/profiles/follows/urls.py
  19. 4 4
      misago/apps/profiles/posts/profile.py
  20. 15 15
      misago/apps/profiles/posts/urls.py
  21. 59 59
      misago/apps/profiles/template.py
  22. 4 4
      misago/apps/profiles/threads/profile.py
  23. 15 15
      misago/apps/profiles/threads/urls.py
  24. 24 24
      misago/apps/profiles/urls.py
  25. 20 20
      misago/apps/redirect.py
  26. 14 14
      misago/apps/reports/changelog.py
  27. 36 36
      misago/apps/reports/delete.py
  28. 4 4
      misago/apps/reports/details.py
  29. 29 29
      misago/apps/reports/jumps.py
  30. 8 8
      misago/apps/reports/mixins.py
  31. 32 32
      misago/apps/reports/urls.py
  32. 5 5
      misago/apps/resetpswd/urls.py
  33. 7 7
      misago/apps/search/urls.py
  34. 13 13
      misago/apps/signin/urls.py
  35. 14 14
      misago/apps/threads/changelog.py
  36. 36 36
      misago/apps/threads/delete.py
  37. 8 8
      misago/apps/threads/details.py
  38. 56 56
      misago/apps/threads/jumps.py
  39. 7 7
      misago/apps/threads/mixins.py
  40. 60 60
      misago/apps/threads/thread.py
  41. 41 41
      misago/apps/threads/urls.py
  42. 1 1
      misago/apps/threadtype/list/__init__.py
  43. 3 3
      misago/apps/threadtype/posting/__init__.py
  44. 51 51
      misago/apps/threadtype/posting/editreply.py
  45. 66 66
      misago/apps/threadtype/posting/editthread.py
  46. 2 2
      misago/apps/threadtype/thread/__init__.py
  47. 21 21
      misago/apps/usercp/avatar/urls.py
  48. 4 4
      misago/apps/usercp/avatar/usercp.py
  49. 4 4
      misago/apps/usercp/credentials/usercp.py
  50. 11 11
      misago/apps/usercp/options/urls.py
  51. 4 4
      misago/apps/usercp/options/usercp.py
  52. 12 12
      misago/apps/usercp/signature/urls.py
  53. 5 5
      misago/apps/usercp/signature/usercp.py
  54. 25 25
      misago/apps/usercp/template.py
  55. 21 21
      misago/apps/usercp/urls.py
  56. 11 11
      misago/apps/usercp/username/urls.py
  57. 5 5
      misago/apps/usercp/username/usercp.py
  58. 8 8
      misago/apps/watchedthreads/urls.py
  59. 73 73
      misago/decorators.py
  60. 109 109
      misago/fixtures/accountssetings.py
  61. 12 12
      misago/fixtures/aclmonitor.py
  62. 13 13
      misago/fixtures/bansmonitor.py
  63. 72 72
      misago/fixtures/basicsettings.py
  64. 45 45
      misago/fixtures/bruteforcesettings.py
  65. 68 68
      misago/fixtures/captchasettings.py
  66. 61 61
      misago/fixtures/forums.py
  67. 116 116
      misago/fixtures/forumsroles.py
  68. 13 13
      misago/fixtures/onlinemonitor.py
  69. 27 27
      misago/fixtures/privatethreadssettings.py
  70. 83 83
      misago/fixtures/rankingsettings.py
  71. 47 47
      misago/fixtures/ranks.py
  72. 12 12
      misago/fixtures/reportsmonitor.py
  73. 66 66
      misago/fixtures/signingsettings.py
  74. 42 42
      misago/fixtures/tossettings.py
  75. 88 88
      misago/fixtures/userroles.py
  76. 16 16
      misago/fixtures/usersmonitor.py
  77. 37 37
      misago/management/commands/about.py
  78. 38 38
      misago/management/commands/adduser.py
  79. 13 13
      misago/management/commands/clearalerts.py
  80. 13 13
      misago/management/commands/clearattempts.py
  81. 9 9
      misago/management/commands/clearmonitor.py
  82. 14 14
      misago/management/commands/clearsessions.py
  83. 13 13
      misago/management/commands/cleartokens.py
  84. 14 14
      misago/management/commands/cleartracker.py
  85. 9 9
      misago/management/commands/forcepdssync.py
  86. 67 67
      misago/management/commands/genavatars.py
  87. 22 22
      misago/management/commands/reparseposts.py
  88. 31 31
      misago/management/commands/startmisago.py
  89. 12 12
      misago/management/commands/syncdeltas.py
  90. 52 52
      misago/management/commands/syncfixtures.py
  91. 13 13
      misago/management/commands/updatemisago.py
  92. 59 59
      misago/markdown/__init__.py
  93. 20 20
      misago/markdown/extensions/bbcodes.py
  94. 37 37
      misago/markdown/extensions/cleanlinks.py
  95. 43 43
      misago/markdown/extensions/magiclinks.py
  96. 59 59
      misago/markdown/extensions/mentions.py
  97. 31 31
      misago/markdown/extensions/shorthandimgs.py
  98. 13 13
      misago/markdown/extensions/strikethrough.py

+ 1 - 1
misago/__init__.py

@@ -1 +1 @@
-__version__ = "0.3.0 DEV"
+__version__ = "0.3.0 DEV"

+ 8 - 8
misago/acl/exceptions.py

@@ -1,9 +1,9 @@
-"""
-ACL Exceptions thrown by Misago actions
-"""
-
-class ACLError403(Exception):
-    pass
-
-class ACLError404(Exception):
+"""
+ACL Exceptions thrown by Misago actions
+"""
+
+class ACLError403(Exception):
+    pass
+
+class ACLError404(Exception):
     pass

+ 30 - 30
misago/acl/panels.py

@@ -1,30 +1,30 @@
-from debug_toolbar.panels import DebugPanel
-from django.template.loader import render_to_string
-from django.utils.translation import ugettext_lazy as _
-
-class MisagoACLDebugPanel(DebugPanel):
-    name = 'MisagoACL'
-    has_content = True
-
-    def nav_title(self):
-        return _('Misago ACL')
-
-    def title(self):
-        return _('Misago User ACL')
-
-    def url(self):
-        return ''
-
-    def process_request(self, request):
-        self.request = request
-
-    def content(self):
-        if self.request.heartbeat:
-            self.has_content = False
-        else:
-            context = self.context.copy()
-            try:
-                context['acl'] = self.request.acl
-            except AttributeError:
-                context['acl'] = {}
-            return render_to_string('debug_toolbar/panels/acl.html', context)
+from debug_toolbar.panels import DebugPanel
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
+
+class MisagoACLDebugPanel(DebugPanel):
+    name = 'MisagoACL'
+    has_content = True
+
+    def nav_title(self):
+        return _('Misago ACL')
+
+    def title(self):
+        return _('Misago User ACL')
+
+    def url(self):
+        return ''
+
+    def process_request(self, request):
+        self.request = request
+
+    def content(self):
+        if self.request.heartbeat:
+            self.has_content = False
+        else:
+            context = self.context.copy()
+            try:
+                context['acl'] = self.request.acl
+            except AttributeError:
+                context['acl'] = {}
+            return render_to_string('debug_toolbar/panels/acl.html', context)

+ 6 - 6
misago/apps/activation/urls.py

@@ -1,6 +1,6 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.activation.views',
-    url(r'^request/$', 'form', name="send_activation"),
-    url(r'^(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'activate', name="activate"),
-)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.activation.views',
+    url(r'^request/$', 'form', name="send_activation"),
+    url(r'^(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'activate', name="activate"),
+)

+ 43 - 43
misago/apps/admin/online/views.py

@@ -1,43 +1,43 @@
-from django.utils.translation import ugettext as _
-from misago.admin import site
-from misago.apps.admin.widgets import ListWidget
-from misago.apps.admin.online.forms import SearchSessionsForm
-
-class List(ListWidget):
-    admin = site.get_action('online')
-    id = 'list'
-    columns = (
-               ('owner', _("Session Owner")),
-               ('start', _("Session Start"), 25),
-               ('last', _("Last Click"), 25),
-               )
-    default_sorting = 'start'
-    sortables = {
-                 'start': 0,
-                 'last': 0,
-                }
-    hide_actions = True
-    pagination = 50
-    search_form = SearchSessionsForm
-    empty_message = _('Looks like nobody is currently online on forums.')
-
-    def set_filters(self, model, filters):
-        if 'username' in filters:
-            model = model.filter(user__username__istartswith=filters['username'])
-        if 'ip_address' in filters:
-            model = model.filter(ip__startswith=filters['ip_address'])
-        if 'useragent' in filters:
-            model = model.filter(agent__icontains=filters['useragent'])
-        if filters['type'] == 'registered':
-            model = model.filter(user__isnull=False)
-        if filters['type'] == 'guest':
-            model = model.filter(user__isnull=True)
-        if filters['type'] == 'crawler':
-            model = model.filter(crawler__isnull=False)
-        return model
-
-    def prefetch_related(self, items):
-        return items.prefetch_related('user')
-
-    def select_items(self, items):
-        return items.filter(matched=1).filter(admin=0)
+from django.utils.translation import ugettext as _
+from misago.admin import site
+from misago.apps.admin.widgets import ListWidget
+from misago.apps.admin.online.forms import SearchSessionsForm
+
+class List(ListWidget):
+    admin = site.get_action('online')
+    id = 'list'
+    columns = (
+               ('owner', _("Session Owner")),
+               ('start', _("Session Start"), 25),
+               ('last', _("Last Click"), 25),
+               )
+    default_sorting = 'start'
+    sortables = {
+                 'start': 0,
+                 'last': 0,
+                }
+    hide_actions = True
+    pagination = 50
+    search_form = SearchSessionsForm
+    empty_message = _('Looks like nobody is currently online on forums.')
+
+    def set_filters(self, model, filters):
+        if 'username' in filters:
+            model = model.filter(user__username__istartswith=filters['username'])
+        if 'ip_address' in filters:
+            model = model.filter(ip__startswith=filters['ip_address'])
+        if 'useragent' in filters:
+            model = model.filter(agent__icontains=filters['useragent'])
+        if filters['type'] == 'registered':
+            model = model.filter(user__isnull=False)
+        if filters['type'] == 'guest':
+            model = model.filter(user__isnull=True)
+        if filters['type'] == 'crawler':
+            model = model.filter(crawler__isnull=False)
+        return model
+
+    def prefetch_related(self, items):
+        return items.prefetch_related('user')
+
+    def select_items(self, items):
+        return items.filter(matched=1).filter(admin=0)

+ 30 - 30
misago/apps/admin/sections/__init__.py

@@ -1,30 +1,30 @@
-from django.utils.translation import ugettext_lazy as _
-from misago.admin import AdminSection
-
-ADMIN_SECTIONS = (
-    AdminSection(
-                 id='overview',
-                 name=_("Overview"),
-                 icon='signal',
-                 ),
-    AdminSection(
-                 id='users',
-                 name=_("Users"),
-                 icon='user',
-                 ),
-    AdminSection(
-                 id='forums',
-                 name=_("Forums"),
-                 icon='comment',
-                 ),
-    AdminSection(
-                 id='perms',
-                 name=_("Permissions"),
-                 icon='adjust',
-                 ),
-    AdminSection(
-                 id='system',
-                 name=_("System"),
-                 icon='cog',
-                 ),
-)
+from django.utils.translation import ugettext_lazy as _
+from misago.admin import AdminSection
+
+ADMIN_SECTIONS = (
+    AdminSection(
+                 id='overview',
+                 name=_("Overview"),
+                 icon='signal',
+                 ),
+    AdminSection(
+                 id='users',
+                 name=_("Users"),
+                 icon='user',
+                 ),
+    AdminSection(
+                 id='forums',
+                 name=_("Forums"),
+                 icon='comment',
+                 ),
+    AdminSection(
+                 id='perms',
+                 name=_("Permissions"),
+                 icon='adjust',
+                 ),
+    AdminSection(
+                 id='system',
+                 name=_("System"),
+                 icon='cog',
+                 ),
+)

+ 16 - 16
misago/apps/admin/team.py

@@ -1,16 +1,16 @@
-from django.utils.translation import ugettext as _
-from misago.admin import site
-from misago.apps.admin.widgets import ListWidget
-
-class List(ListWidget):
-    admin = site.get_action('team')
-    id = 'list'
-    columns = (
-             ('username', _("Team Member")),
-             )
-    default_sorting = 'username_slug'
-    hide_actions = True
-    pagination = 50
-
-    def select_items(self, items):
-        return items.filter(is_team=1)
+from django.utils.translation import ugettext as _
+from misago.admin import site
+from misago.apps.admin.widgets import ListWidget
+
+class List(ListWidget):
+    admin = site.get_action('team')
+    id = 'list'
+    columns = (
+             ('username', _("Team Member")),
+             )
+    default_sorting = 'username_slug'
+    hide_actions = True
+    pagination = 50
+
+    def select_items(self, items):
+        return items.filter(is_team=1)

+ 14 - 14
misago/apps/privatethreads/changelog.py

@@ -1,15 +1,15 @@
-from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
-                                              ChangelogDiffBaseView,
-                                              ChangelogRevertBaseView)
-from misago.apps.privatethreads.mixins import TypeMixin
-
-class ChangelogView(ChangelogChangesBaseView, TypeMixin):
-    pass
-
-
-class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
-    pass
-
-
-class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
+from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
+                                              ChangelogDiffBaseView,
+                                              ChangelogRevertBaseView)
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class ChangelogView(ChangelogChangesBaseView, TypeMixin):
+    pass
+
+
+class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
+    pass
+
+
+class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
     pass

+ 36 - 36
misago/apps/privatethreads/delete.py

@@ -1,37 +1,37 @@
-from misago.apps.threadtype.delete import *
-from misago.apps.privatethreads.mixins import TypeMixin
-
-class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
-    pass
-
-
-class HideThreadView(HideThreadBaseView, TypeMixin):
-    pass
-
-
-class ShowThreadView(ShowThreadBaseView, TypeMixin):
-    pass
-
-
-class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
-    pass
-
-
-class HideReplyView(HideReplyBaseView, TypeMixin):
-    pass
-
-
-class ShowReplyView(ShowReplyBaseView, TypeMixin):
-    pass
-
-
-class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
-    pass
-
-
-class HideCheckpointView(HideCheckpointBaseView, TypeMixin):
-    pass
-
-
-class ShowCheckpointView(ShowCheckpointBaseView, TypeMixin):
+from misago.apps.threadtype.delete import *
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
+    pass
+
+
+class HideThreadView(HideThreadBaseView, TypeMixin):
+    pass
+
+
+class ShowThreadView(ShowThreadBaseView, TypeMixin):
+    pass
+
+
+class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
+    pass
+
+
+class HideReplyView(HideReplyBaseView, TypeMixin):
+    pass
+
+
+class ShowReplyView(ShowReplyBaseView, TypeMixin):
+    pass
+
+
+class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
+    pass
+
+
+class HideCheckpointView(HideCheckpointBaseView, TypeMixin):
+    pass
+
+
+class ShowCheckpointView(ShowCheckpointBaseView, TypeMixin):
     pass

+ 8 - 8
misago/apps/privatethreads/details.py

@@ -1,9 +1,9 @@
-from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
-from misago.apps.privatethreads.mixins import TypeMixin
-
-class DetailsView(DetailsBaseView, TypeMixin):
-    pass
-
-
-class KarmaVotesView(KarmaVotesBaseView, TypeMixin):
+from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class DetailsView(DetailsBaseView, TypeMixin):
+    pass
+
+
+class KarmaVotesView(KarmaVotesBaseView, TypeMixin):
     pass

+ 40 - 40
misago/apps/privatethreads/urls.py

@@ -1,40 +1,40 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.privatethreads',
-    url(r'^$', 'list.ThreadsListView', name="private_threads"),
-    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'list.ThreadsListView', name="private_threads"),
-    url(r'^start/$', 'posting.NewThreadView', name="private_thread_start"),
-    url(r'^start/(?P<username>\w+)-(?P<user>\d+)/$', 'posting.NewThreadView', name="private_thread_start_with"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'posting.EditThreadView', name="private_thread_edit"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="private_thread_reply"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="private_thread_reply"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="private_post_edit"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="private_thread"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'thread.ThreadView', name="private_thread"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="private_thread_last"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="private_thread_find"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="private_thread_new"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'jumps.FirstReportedView', name="private_thread_reported"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="private_thread_show_hidden"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/report/$', 'jumps.ReportPostView', name="private_post_report"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show-report/$', 'jumps.ShowPostReportView', name="private_post_report_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="private_thread_watch"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="private_thread_watch_email"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="private_thread_unwatch"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="private_thread_unwatch_email"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/invite/$', 'jumps.InviteUserView', name="private_thread_invite_user"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/remove/$', 'jumps.RemoveUserView', name="private_thread_remove_user"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="private_thread_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="private_thread_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="private_post_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="private_post_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="private_post_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="private_post_checkpoint_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="private_post_checkpoint_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="private_post_checkpoint_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="private_post_info"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="private_thread_changelog"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="private_thread_changelog_diff"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="private_thread_changelog_revert"),
-)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.privatethreads',
+    url(r'^$', 'list.ThreadsListView', name="private_threads"),
+    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'list.ThreadsListView', name="private_threads"),
+    url(r'^start/$', 'posting.NewThreadView', name="private_thread_start"),
+    url(r'^start/(?P<username>\w+)-(?P<user>\d+)/$', 'posting.NewThreadView', name="private_thread_start_with"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'posting.EditThreadView', name="private_thread_edit"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="private_thread_reply"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="private_thread_reply"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="private_post_edit"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="private_thread"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'thread.ThreadView', name="private_thread"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="private_thread_last"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="private_thread_find"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="private_thread_new"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'jumps.FirstReportedView', name="private_thread_reported"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="private_thread_show_hidden"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/report/$', 'jumps.ReportPostView', name="private_post_report"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show-report/$', 'jumps.ShowPostReportView', name="private_post_report_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="private_thread_watch"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="private_thread_watch_email"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="private_thread_unwatch"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="private_thread_unwatch_email"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/invite/$', 'jumps.InviteUserView', name="private_thread_invite_user"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/remove/$', 'jumps.RemoveUserView', name="private_thread_remove_user"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="private_thread_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="private_thread_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="private_post_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="private_post_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="private_post_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="private_post_checkpoint_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="private_post_checkpoint_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="private_post_checkpoint_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="private_post_info"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="private_thread_changelog"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="private_thread_changelog_diff"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="private_thread_changelog_revert"),
+)

+ 40 - 40
misago/apps/profiles/decorators.py

@@ -1,41 +1,41 @@
-from functools import wraps
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from misago.apps.errors import error404
-from misago.models import User
-from misago.utils.strings import slugify
-
-def profile_view(fallback='user'):
-    def outer_decorator(f):
-        def inner_decorator(request, user, username, *args, **kwargs):
-            request = request
-            user_pk = int(user)
-            user_slug = username
-            try:
-                user = User.objects
-                if settings.PROFILE_EXTENSIONS_PRELOAD:
-                    user = user.select_related(*settings.PROFILE_EXTENSIONS_PRELOAD)
-                user = user.get(pk=user_pk)
-                if user.username_slug != user_slug:
-                    # Force crawlers to take notice of updated username
-                    return redirect(reverse(fallback, args=(user.username_slug, user.pk)), permanent=True)
-                return f(request, user, *args, **kwargs)
-            except User.DoesNotExist:
-                return error404(request)
-    
-        return wraps(f)(inner_decorator)
-    return outer_decorator
-
-
-def user_view(f):
-    def inner_decorator(request, user, *args, **kwargs):
-        request = request
-        user_pk = int(user)
-        try:
-            user = User.objects.get(pk=user_pk)
-            return f(request, user, *args, **kwargs)
-        except User.DoesNotExist:
-            return error404(request)
-
+from functools import wraps
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from misago.apps.errors import error404
+from misago.models import User
+from misago.utils.strings import slugify
+
+def profile_view(fallback='user'):
+    def outer_decorator(f):
+        def inner_decorator(request, user, username, *args, **kwargs):
+            request = request
+            user_pk = int(user)
+            user_slug = username
+            try:
+                user = User.objects
+                if settings.PROFILE_EXTENSIONS_PRELOAD:
+                    user = user.select_related(*settings.PROFILE_EXTENSIONS_PRELOAD)
+                user = user.get(pk=user_pk)
+                if user.username_slug != user_slug:
+                    # Force crawlers to take notice of updated username
+                    return redirect(reverse(fallback, args=(user.username_slug, user.pk)), permanent=True)
+                return f(request, user, *args, **kwargs)
+            except User.DoesNotExist:
+                return error404(request)
+    
+        return wraps(f)(inner_decorator)
+    return outer_decorator
+
+
+def user_view(f):
+    def inner_decorator(request, user, *args, **kwargs):
+        request = request
+        user_pk = int(user)
+        try:
+            user = User.objects.get(pk=user_pk)
+            return f(request, user, *args, **kwargs)
+        except User.DoesNotExist:
+            return error404(request)
+
     return wraps(f)(inner_decorator)

+ 4 - 4
misago/apps/profiles/details/profile.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_profile_extension(request):
-    return (('user_details', _('Profile Details')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_details', _('Profile Details')),)

+ 13 - 13
misago/apps/profiles/details/urls.py

@@ -1,14 +1,14 @@
-from django.conf.urls import patterns, url
-
-def register_profile_urls(first=False):
-    urlpatterns = []
-    if first:
-        urlpatterns += patterns('misago.apps.profiles.details.views',
-            url(r'^$', 'details', name="user"),
-            url(r'^$', 'details', name="user_details"),
-        )
-    else:
-        urlpatterns += patterns('misago.apps.profiles.details.views',
-            url(r'^details/$', 'details', name="user_details"),
-        )
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.apps.profiles.details.views',
+            url(r'^$', 'details', name="user"),
+            url(r'^$', 'details', name="user_details"),
+        )
+    else:
+        urlpatterns += patterns('misago.apps.profiles.details.views',
+            url(r'^details/$', 'details', name="user_details"),
+        )
     return urlpatterns

+ 4 - 4
misago/apps/profiles/followers/profile.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_profile_extension(request):
-    return (('user_followers', _('Followers')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_followers', _('Followers')),)

+ 15 - 15
misago/apps/profiles/followers/urls.py

@@ -1,16 +1,16 @@
-from django.conf.urls import patterns, url
-
-def register_profile_urls(first=False):
-    urlpatterns = []
-    if first:
-        urlpatterns += patterns('misago.apps.profiles.followers.views',
-            url(r'^$', 'followers', name="user"),
-            url(r'^$', 'followers', name="user_followers"),
-            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'followers', name="user_followers"),
-        )
-    else:
-        urlpatterns += patterns('misago.apps.profiles.followers.views',
-            url(r'^followers/$', 'followers', name="user_followers"),
-            url(r'^followers/(?P<page>[1-9]([0-9]+)?)/$', 'followers', name="user_followers"),
-        )
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.apps.profiles.followers.views',
+            url(r'^$', 'followers', name="user"),
+            url(r'^$', 'followers', name="user_followers"),
+            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'followers', name="user_followers"),
+        )
+    else:
+        urlpatterns += patterns('misago.apps.profiles.followers.views',
+            url(r'^followers/$', 'followers', name="user_followers"),
+            url(r'^followers/(?P<page>[1-9]([0-9]+)?)/$', 'followers', name="user_followers"),
+        )
     return urlpatterns

+ 4 - 4
misago/apps/profiles/follows/profile.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_profile_extension(request):
-    return (('user_follows', _('Follows')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_follows', _('Follows')),)

+ 15 - 15
misago/apps/profiles/follows/urls.py

@@ -1,16 +1,16 @@
-from django.conf.urls import patterns, url
-
-def register_profile_urls(first=False):
-    urlpatterns = []
-    if first:
-        urlpatterns += patterns('misago.apps.profiles.follows.views',
-            url(r'^$', 'follows', name="user"),
-            url(r'^$', 'follows', name="user_follows"),
-            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'follows', name="user_follows"),
-        )
-    else:
-        urlpatterns += patterns('misago.apps.profiles.follows.views',
-            url(r'^follows/$', 'follows', name="user_follows"),
-            url(r'^follows/(?P<page>[1-9]([0-9]+)?)/$', 'follows', name="user_follows"),
-        )
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.apps.profiles.follows.views',
+            url(r'^$', 'follows', name="user"),
+            url(r'^$', 'follows', name="user_follows"),
+            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'follows', name="user_follows"),
+        )
+    else:
+        urlpatterns += patterns('misago.apps.profiles.follows.views',
+            url(r'^follows/$', 'follows', name="user_follows"),
+            url(r'^follows/(?P<page>[1-9]([0-9]+)?)/$', 'follows', name="user_follows"),
+        )
     return urlpatterns

+ 4 - 4
misago/apps/profiles/posts/profile.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_profile_extension(request):
-    return (('user_posts', _('Posts')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_posts', _('Posts')),)

+ 15 - 15
misago/apps/profiles/posts/urls.py

@@ -1,16 +1,16 @@
-from django.conf.urls import patterns, url
-
-def register_profile_urls(first=False):
-    urlpatterns = []
-    if first:
-        urlpatterns += patterns('misago.apps.profiles.posts.views',
-            url(r'^$', 'posts', name="user"),
-            url(r'^$', 'posts', name="user_posts"),
-            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'posts', name="user_posts"),
-        )
-    else:
-        urlpatterns += patterns('misago.apps.profiles.posts.views',
-            url(r'^posts/$', 'posts', name="user_posts"),
-            url(r'^posts/(?P<page>[1-9]([0-9]+)?)/$', 'posts', name="user_posts"),
-        )
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.apps.profiles.posts.views',
+            url(r'^$', 'posts', name="user"),
+            url(r'^$', 'posts', name="user_posts"),
+            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'posts', name="user_posts"),
+        )
+    else:
+        urlpatterns += patterns('misago.apps.profiles.posts.views',
+            url(r'^posts/$', 'posts', name="user_posts"),
+            url(r'^posts/(?P<page>[1-9]([0-9]+)?)/$', 'posts', name="user_posts"),
+        )
     return urlpatterns

+ 59 - 59
misago/apps/profiles/template.py

@@ -1,59 +1,59 @@
-from datetime import timedelta
-from django.conf import settings
-from django.template import RequestContext as DjangoRequestContext
-from django.utils import timezone
-from django.utils.importlib import import_module
-from misago.models import User
-
-def RequestContext(request, context=None):
-    if not context:
-        context = {}
-    context['fallback'] = request.path
-        
-    # Find out if we ignore or follow this user
-    context['follows'] = False
-    context['ignores'] = False
-    if request.user.is_authenticated() and request.user.pk != context['profile'].pk:
-        context['follows'] = request.user.is_following(context['profile'])
-        context['ignores'] = request.user.is_ignoring(context['profile'])
-    
-    # Find out if this user allows us to see his activity
-    if request.user.pk != context['profile'].pk:
-        if context['profile'].hide_activity == 2:
-            context['hidden'] = True
-        if context['profile'].hide_activity == 1:
-            context['hidden'] = context['profile'].is_following(request.user)
-    else:
-        context['hidden'] = False
-
-    # Find out if this user is online:
-    if request.user.pk != context['profile'].pk:
-        try:
-            context['online'] = context['profile'].sessions.filter(admin=False).filter(last__gt=(timezone.now() - timedelta(minutes=10))).order_by('-last')[0:1][0]
-        except IndexError:
-            context['online'] = False
-    else:
-        # Fake "right now" time
-        context['online'] = {'last': timezone.now()}
-
-    # Sync member
-    if context['profile'].sync_profile():
-        context['profile'].save(force_update=True)
-
-    context['tabs'] = []
-    for extension in settings.PROFILE_EXTENSIONS:
-        profile_module = import_module(extension + '.profile')
-        try:
-            append_links = profile_module.register_profile_extension(request)
-            if append_links:
-                for link in append_links:
-                    link = list(link)
-                    token = link[0][link[0].find('_') + 1:]
-                    context['tabs'].append({
-                                            'route': link[0],
-                                            'active': context['tab'] == token,
-                                            'name': link[1],
-                                            })
-        except AttributeError:
-            pass
-    return DjangoRequestContext(request, context)
+from datetime import timedelta
+from django.conf import settings
+from django.template import RequestContext as DjangoRequestContext
+from django.utils import timezone
+from django.utils.importlib import import_module
+from misago.models import User
+
+def RequestContext(request, context=None):
+    if not context:
+        context = {}
+    context['fallback'] = request.path
+        
+    # Find out if we ignore or follow this user
+    context['follows'] = False
+    context['ignores'] = False
+    if request.user.is_authenticated() and request.user.pk != context['profile'].pk:
+        context['follows'] = request.user.is_following(context['profile'])
+        context['ignores'] = request.user.is_ignoring(context['profile'])
+    
+    # Find out if this user allows us to see his activity
+    if request.user.pk != context['profile'].pk:
+        if context['profile'].hide_activity == 2:
+            context['hidden'] = True
+        if context['profile'].hide_activity == 1:
+            context['hidden'] = context['profile'].is_following(request.user)
+    else:
+        context['hidden'] = False
+
+    # Find out if this user is online:
+    if request.user.pk != context['profile'].pk:
+        try:
+            context['online'] = context['profile'].sessions.filter(admin=False).filter(last__gt=(timezone.now() - timedelta(minutes=10))).order_by('-last')[0:1][0]
+        except IndexError:
+            context['online'] = False
+    else:
+        # Fake "right now" time
+        context['online'] = {'last': timezone.now()}
+
+    # Sync member
+    if context['profile'].sync_profile():
+        context['profile'].save(force_update=True)
+
+    context['tabs'] = []
+    for extension in settings.PROFILE_EXTENSIONS:
+        profile_module = import_module(extension + '.profile')
+        try:
+            append_links = profile_module.register_profile_extension(request)
+            if append_links:
+                for link in append_links:
+                    link = list(link)
+                    token = link[0][link[0].find('_') + 1:]
+                    context['tabs'].append({
+                                            'route': link[0],
+                                            'active': context['tab'] == token,
+                                            'name': link[1],
+                                            })
+        except AttributeError:
+            pass
+    return DjangoRequestContext(request, context)

+ 4 - 4
misago/apps/profiles/threads/profile.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_profile_extension(request):
-    return (('user_threads', _('Threads')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_threads', _('Threads')),)

+ 15 - 15
misago/apps/profiles/threads/urls.py

@@ -1,16 +1,16 @@
-from django.conf.urls import patterns, url
-
-def register_profile_urls(first=False):
-    urlpatterns = []
-    if first:
-        urlpatterns += patterns('misago.apps.profiles.threads.views',
-            url(r'^$', 'threads', name="user"),
-            url(r'^$', 'threads', name="user_threads"),
-            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'threads', name="user_threads"),
-        )
-    else:
-        urlpatterns += patterns('misago.apps.profiles.threads.views',
-            url(r'^threads/$', 'threads', name="user_threads"),
-            url(r'^threads/(?P<page>[1-9]([0-9]+)?)/$', 'threads', name="user_threads"),
-        )
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.apps.profiles.threads.views',
+            url(r'^$', 'threads', name="user"),
+            url(r'^$', 'threads', name="user_threads"),
+            url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'threads', name="user_threads"),
+        )
+    else:
+        urlpatterns += patterns('misago.apps.profiles.threads.views',
+            url(r'^threads/$', 'threads', name="user_threads"),
+            url(r'^threads/(?P<page>[1-9]([0-9]+)?)/$', 'threads', name="user_threads"),
+        )
     return urlpatterns

+ 24 - 24
misago/apps/profiles/urls.py

@@ -1,25 +1,25 @@
-from django.conf import settings
-from django.conf.urls import patterns, include, url
-from django.utils.importlib import import_module
-
-urlpatterns = patterns('misago.apps.profiles.views',
-    url(r'^$', 'list', name="users"),
-    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'list', name="users"),
-)
-
-# Build extensions URLs
-iteration = 0
-for extension in settings.PROFILE_EXTENSIONS:
-    iteration += 1
-    profile_extension = import_module(extension + '.urls')
-    try:
-        urlpatterns += patterns('',
-            (r'^(?P<username>\w+)-(?P<user>\d+)/', include(profile_extension.register_profile_urls(iteration == 1))),
-        )
-    except AttributeError:
-        pass
-
-urlpatterns += patterns('misago.apps.profiles.views',
-    url(r'^(?P<slug>(\w|-)+)/$', 'list', name="users"),
-    url(r'^(?P<slug>(\w|-)+)/(?P<page>[1-9]([0-9]+)?)/$', 'list', name="users"),
+from django.conf import settings
+from django.conf.urls import patterns, include, url
+from django.utils.importlib import import_module
+
+urlpatterns = patterns('misago.apps.profiles.views',
+    url(r'^$', 'list', name="users"),
+    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'list', name="users"),
+)
+
+# Build extensions URLs
+iteration = 0
+for extension in settings.PROFILE_EXTENSIONS:
+    iteration += 1
+    profile_extension = import_module(extension + '.urls')
+    try:
+        urlpatterns += patterns('',
+            (r'^(?P<username>\w+)-(?P<user>\d+)/', include(profile_extension.register_profile_urls(iteration == 1))),
+        )
+    except AttributeError:
+        pass
+
+urlpatterns += patterns('misago.apps.profiles.views',
+    url(r'^(?P<slug>(\w|-)+)/$', 'list', name="users"),
+    url(r'^(?P<slug>(\w|-)+)/(?P<page>[1-9]([0-9]+)?)/$', 'list', name="users"),
 )

+ 20 - 20
misago/apps/redirect.py

@@ -1,21 +1,21 @@
-from django.shortcuts import redirect as django_redirect
-from django.utils.translation import ugettext as _
-from misago.apps.errors import error403, error404
-from misago.models import Forum
-
-def redirect(request, forum, slug):
-    if not request.acl.forums.can_see(forum):
-        return error404(request)
-    try:
-        forum = Forum.objects.get(pk=forum, type='redirect')
-        if not request.acl.forums.can_browse(forum):
-            return error403(request, _("You don't have permission to follow this redirect."))
-        redirects_tracker = request.session.get('redirects', [])
-        if forum.pk not in redirects_tracker:
-            redirects_tracker.append(forum.pk)
-            request.session['redirects'] = redirects_tracker
-            forum.redirects += 1
-            forum.save(force_update=True)
-        return django_redirect(forum.redirect)
-    except Forum.DoesNotExist:
+from django.shortcuts import redirect as django_redirect
+from django.utils.translation import ugettext as _
+from misago.apps.errors import error403, error404
+from misago.models import Forum
+
+def redirect(request, forum, slug):
+    if not request.acl.forums.can_see(forum):
+        return error404(request)
+    try:
+        forum = Forum.objects.get(pk=forum, type='redirect')
+        if not request.acl.forums.can_browse(forum):
+            return error403(request, _("You don't have permission to follow this redirect."))
+        redirects_tracker = request.session.get('redirects', [])
+        if forum.pk not in redirects_tracker:
+            redirects_tracker.append(forum.pk)
+            request.session['redirects'] = redirects_tracker
+            forum.redirects += 1
+            forum.save(force_update=True)
+        return django_redirect(forum.redirect)
+    except Forum.DoesNotExist:
         return error404(request)

+ 14 - 14
misago/apps/reports/changelog.py

@@ -1,15 +1,15 @@
-from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
-                                              ChangelogDiffBaseView,
-                                              ChangelogRevertBaseView)
-from misago.apps.reports.mixins import TypeMixin
-
-class ChangelogView(ChangelogChangesBaseView, TypeMixin):
-    pass
-
-
-class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
-    pass
-
-
-class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
+from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
+                                              ChangelogDiffBaseView,
+                                              ChangelogRevertBaseView)
+from misago.apps.reports.mixins import TypeMixin
+
+class ChangelogView(ChangelogChangesBaseView, TypeMixin):
+    pass
+
+
+class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
+    pass
+
+
+class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
     pass

+ 36 - 36
misago/apps/reports/delete.py

@@ -1,37 +1,37 @@
-from misago.apps.threadtype.delete import *
-from misago.apps.reports.mixins import TypeMixin
-
-class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
-    pass
-
-
-class HideThreadView(HideThreadBaseView, TypeMixin):
-    pass
-
-
-class ShowThreadView(ShowThreadBaseView, TypeMixin):
-    pass
-
-
-class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
-    pass
-
-
-class HideReplyView(HideReplyBaseView, TypeMixin):
-    pass
-
-
-class ShowReplyView(ShowReplyBaseView, TypeMixin):
-    pass
-
-
-class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
-    pass
-
-
-class HideCheckpointView(HideCheckpointBaseView, TypeMixin):
-    pass
-
-
-class ShowCheckpointView(ShowCheckpointBaseView, TypeMixin):
+from misago.apps.threadtype.delete import *
+from misago.apps.reports.mixins import TypeMixin
+
+class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
+    pass
+
+
+class HideThreadView(HideThreadBaseView, TypeMixin):
+    pass
+
+
+class ShowThreadView(ShowThreadBaseView, TypeMixin):
+    pass
+
+
+class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
+    pass
+
+
+class HideReplyView(HideReplyBaseView, TypeMixin):
+    pass
+
+
+class ShowReplyView(ShowReplyBaseView, TypeMixin):
+    pass
+
+
+class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
+    pass
+
+
+class HideCheckpointView(HideCheckpointBaseView, TypeMixin):
+    pass
+
+
+class ShowCheckpointView(ShowCheckpointBaseView, TypeMixin):
     pass

+ 4 - 4
misago/apps/reports/details.py

@@ -1,5 +1,5 @@
-from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
-from misago.apps.reports.mixins import TypeMixin
-
-class DetailsView(DetailsBaseView, TypeMixin):
+from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
+from misago.apps.reports.mixins import TypeMixin
+
+class DetailsView(DetailsBaseView, TypeMixin):
     pass

+ 29 - 29
misago/apps/reports/jumps.py

@@ -1,29 +1,29 @@
-from misago.apps.threadtype.jumps import *
-from misago.apps.reports.mixins import TypeMixin
-
-class LastReplyView(LastReplyBaseView, TypeMixin):
-    pass
-
-
-class FindReplyView(FindReplyBaseView, TypeMixin):
-    pass
-
-
-class NewReplyView(NewReplyBaseView, TypeMixin):
-    pass
-
-
-class WatchThreadView(WatchThreadBaseView, TypeMixin):
-    pass
-
-
-class WatchEmailThreadView(WatchEmailThreadBaseView, TypeMixin):
-    pass
-
-
-class UnwatchThreadView(UnwatchThreadBaseView, TypeMixin):
-    pass
-
-
-class UnwatchEmailThreadView(UnwatchEmailThreadBaseView, TypeMixin):
-    pass
+from misago.apps.threadtype.jumps import *
+from misago.apps.reports.mixins import TypeMixin
+
+class LastReplyView(LastReplyBaseView, TypeMixin):
+    pass
+
+
+class FindReplyView(FindReplyBaseView, TypeMixin):
+    pass
+
+
+class NewReplyView(NewReplyBaseView, TypeMixin):
+    pass
+
+
+class WatchThreadView(WatchThreadBaseView, TypeMixin):
+    pass
+
+
+class WatchEmailThreadView(WatchEmailThreadBaseView, TypeMixin):
+    pass
+
+
+class UnwatchThreadView(UnwatchThreadBaseView, TypeMixin):
+    pass
+
+
+class UnwatchEmailThreadView(UnwatchEmailThreadBaseView, TypeMixin):
+    pass

+ 8 - 8
misago/apps/reports/mixins.py

@@ -1,8 +1,8 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-
-class TypeMixin(object):
-    type_prefix = 'report'
-
-    def threads_list_redirect(self):
-        return redirect(reverse('reports'))
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+
+class TypeMixin(object):
+    type_prefix = 'report'
+
+    def threads_list_redirect(self):
+        return redirect(reverse('reports'))

+ 32 - 32
misago/apps/reports/urls.py

@@ -1,32 +1,32 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.reports',
-    url(r'^$', 'list.ThreadsListView', name="reports"),
-    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'list.ThreadsListView', name="reports"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'posting.EditThreadView', name="report_edit"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="report_reply"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="report_reply"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="report_post_edit"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="report"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'thread.ThreadView', name="report"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="report_last"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="report_find"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="report_new"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="report_watch"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="report_watch_email"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="report_unwatch"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="report_unwatch_email"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="report_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="report_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="report_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="report_post_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="report_post_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="report_post_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="report_post_checkpoint_delete"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="report_post_checkpoint_hide"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="report_post_checkpoint_show"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="report_post_info"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="report_changelog"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="report_changelog_diff"),
-    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="report_changelog_revert"),
-)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.reports',
+    url(r'^$', 'list.ThreadsListView', name="reports"),
+    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'list.ThreadsListView', name="reports"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'posting.EditThreadView', name="report_edit"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="report_reply"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="report_reply"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="report_post_edit"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="report"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'thread.ThreadView', name="report"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="report_last"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="report_find"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="report_new"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="report_watch"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="report_watch_email"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="report_unwatch"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="report_unwatch_email"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="report_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="report_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="report_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="report_post_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="report_post_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="report_post_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="report_post_checkpoint_delete"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="report_post_checkpoint_hide"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="report_post_checkpoint_show"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="report_post_info"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="report_changelog"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="report_changelog_diff"),
+    url(r'^(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="report_changelog_revert"),
+)

+ 5 - 5
misago/apps/resetpswd/urls.py

@@ -1,6 +1,6 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.resetpswd.views',
-    url(r'^$', 'form', name="forgot_password"),
-    url(r'^(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'reset', name="reset_password"),
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.resetpswd.views',
+    url(r'^$', 'form', name="forgot_password"),
+    url(r'^(?P<username>[a-z0-9]+)-(?P<user>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'reset', name="reset_password"),
 )

+ 7 - 7
misago/apps/search/urls.py

@@ -1,7 +1,7 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.search.views',
-    url(r'^$', 'QuickSearchView', name="search"),
-    url(r'^results/$', 'SearchResultsView', name="search_results"),
-    url(r'^results/(?P<page>[1-9]([0-9]+)?)/$', 'SearchResultsView', name="search_results"),
-)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.search.views',
+    url(r'^$', 'QuickSearchView', name="search"),
+    url(r'^results/$', 'SearchResultsView', name="search_results"),
+    url(r'^results/(?P<page>[1-9]([0-9]+)?)/$', 'SearchResultsView', name="search_results"),
+)

+ 13 - 13
misago/apps/signin/urls.py

@@ -1,13 +1,13 @@
-from django.conf.urls import patterns, url
-from misago.admin import ADMIN_PATH
-
-urlpatterns = patterns('misago.apps.signin.views',
-    url(r'^signin/$', 'signin', name="sign_in"),
-    url(r'^signout/$', 'signout', name="sign_out"),
-)
-
-# Include admin patterns
-if ADMIN_PATH:
-    urlpatterns += patterns('misago.apps.signin.views',
-        url(r'^' + ADMIN_PATH + 'signout/$', 'signout', name="admin_sign_out"),
-    )
+from django.conf.urls import patterns, url
+from misago.admin import ADMIN_PATH
+
+urlpatterns = patterns('misago.apps.signin.views',
+    url(r'^signin/$', 'signin', name="sign_in"),
+    url(r'^signout/$', 'signout', name="sign_out"),
+)
+
+# Include admin patterns
+if ADMIN_PATH:
+    urlpatterns += patterns('misago.apps.signin.views',
+        url(r'^' + ADMIN_PATH + 'signout/$', 'signout', name="admin_sign_out"),
+    )

+ 14 - 14
misago/apps/threads/changelog.py

@@ -1,15 +1,15 @@
-from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
-                                              ChangelogDiffBaseView,
-                                              ChangelogRevertBaseView)
-from misago.apps.threads.mixins import TypeMixin
-
-class ChangelogView(ChangelogChangesBaseView, TypeMixin):
-    pass
-
-
-class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
-    pass
-
-
-class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
+from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
+                                              ChangelogDiffBaseView,
+                                              ChangelogRevertBaseView)
+from misago.apps.threads.mixins import TypeMixin
+
+class ChangelogView(ChangelogChangesBaseView, TypeMixin):
+    pass
+
+
+class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
+    pass
+
+
+class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
     pass

+ 36 - 36
misago/apps/threads/delete.py

@@ -1,37 +1,37 @@
-from misago.apps.threadtype.delete import *
-from misago.apps.threads.mixins import TypeMixin
-
-class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
-    pass
-
-
-class HideThreadView(HideThreadBaseView, TypeMixin):
-    pass
-
-
-class ShowThreadView(ShowThreadBaseView, TypeMixin):
-    pass
-
-
-class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
-    pass
-
-
-class HideReplyView(HideReplyBaseView, TypeMixin):
-    pass
-
-
-class ShowReplyView(ShowReplyBaseView, TypeMixin):
-    pass
-
-
-class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
-    pass
-
-
-class HideCheckpointView(HideCheckpointBaseView, TypeMixin):
-    pass
-
-
-class ShowCheckpointView(ShowCheckpointBaseView, TypeMixin):
+from misago.apps.threadtype.delete import *
+from misago.apps.threads.mixins import TypeMixin
+
+class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
+    pass
+
+
+class HideThreadView(HideThreadBaseView, TypeMixin):
+    pass
+
+
+class ShowThreadView(ShowThreadBaseView, TypeMixin):
+    pass
+
+
+class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
+    pass
+
+
+class HideReplyView(HideReplyBaseView, TypeMixin):
+    pass
+
+
+class ShowReplyView(ShowReplyBaseView, TypeMixin):
+    pass
+
+
+class DeleteCheckpointView(DeleteCheckpointBaseView, TypeMixin):
+    pass
+
+
+class HideCheckpointView(HideCheckpointBaseView, TypeMixin):
+    pass
+
+
+class ShowCheckpointView(ShowCheckpointBaseView, TypeMixin):
     pass

+ 8 - 8
misago/apps/threads/details.py

@@ -1,9 +1,9 @@
-from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
-from misago.apps.threads.mixins import TypeMixin
-
-class DetailsView(DetailsBaseView, TypeMixin):
-    pass
-
-
-class KarmaVotesView(KarmaVotesBaseView, TypeMixin):
+from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
+from misago.apps.threads.mixins import TypeMixin
+
+class DetailsView(DetailsBaseView, TypeMixin):
+    pass
+
+
+class KarmaVotesView(KarmaVotesBaseView, TypeMixin):
     pass

+ 56 - 56
misago/apps/threads/jumps.py

@@ -1,57 +1,57 @@
-from misago.apps.threadtype.jumps import *
-from misago.apps.threads.mixins import TypeMixin
-
-class LastReplyView(LastReplyBaseView, TypeMixin):
-    pass
-
-
-class FindReplyView(FindReplyBaseView, TypeMixin):
-    pass
-
-
-class NewReplyView(NewReplyBaseView, TypeMixin):
-    pass
-
-
-class FirstModeratedView(FirstModeratedBaseView, TypeMixin):
-    pass
-
-
-class FirstReportedView(FirstReportedBaseView, TypeMixin):
-    pass
-
-
-class ShowHiddenRepliesView(ShowHiddenRepliesBaseView, TypeMixin):
-    pass
-
-
-class WatchThreadView(WatchThreadBaseView, TypeMixin):
-    pass
-
-
-class WatchEmailThreadView(WatchEmailThreadBaseView, TypeMixin):
-    pass
-
-
-class UnwatchThreadView(UnwatchThreadBaseView, TypeMixin):
-    pass
-
-
-class UnwatchEmailThreadView(UnwatchEmailThreadBaseView, TypeMixin):
-    pass
-
-
-class UpvotePostView(UpvotePostBaseView, TypeMixin):
-    pass
-
-
-class DownvotePostView(DownvotePostBaseView, TypeMixin):
-    pass
-
-
-class ReportPostView(ReportPostBaseView, TypeMixin):
-    pass
-
-
-class ShowPostReportView(ShowPostReportBaseView, TypeMixin):
+from misago.apps.threadtype.jumps import *
+from misago.apps.threads.mixins import TypeMixin
+
+class LastReplyView(LastReplyBaseView, TypeMixin):
+    pass
+
+
+class FindReplyView(FindReplyBaseView, TypeMixin):
+    pass
+
+
+class NewReplyView(NewReplyBaseView, TypeMixin):
+    pass
+
+
+class FirstModeratedView(FirstModeratedBaseView, TypeMixin):
+    pass
+
+
+class FirstReportedView(FirstReportedBaseView, TypeMixin):
+    pass
+
+
+class ShowHiddenRepliesView(ShowHiddenRepliesBaseView, TypeMixin):
+    pass
+
+
+class WatchThreadView(WatchThreadBaseView, TypeMixin):
+    pass
+
+
+class WatchEmailThreadView(WatchEmailThreadBaseView, TypeMixin):
+    pass
+
+
+class UnwatchThreadView(UnwatchThreadBaseView, TypeMixin):
+    pass
+
+
+class UnwatchEmailThreadView(UnwatchEmailThreadBaseView, TypeMixin):
+    pass
+
+
+class UpvotePostView(UpvotePostBaseView, TypeMixin):
+    pass
+
+
+class DownvotePostView(DownvotePostBaseView, TypeMixin):
+    pass
+
+
+class ReportPostView(ReportPostBaseView, TypeMixin):
+    pass
+
+
+class ShowPostReportView(ShowPostReportBaseView, TypeMixin):
     pass

+ 7 - 7
misago/apps/threads/mixins.py

@@ -1,8 +1,8 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-
-class TypeMixin(object):
-    type_prefix = 'thread'
-
-    def threads_list_redirect(self):
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+
+class TypeMixin(object):
+    type_prefix = 'thread'
+
+    def threads_list_redirect(self):
         return redirect(reverse('forum', kwargs={'forum': self.forum.pk, 'slug': self.forum.slug}))

+ 60 - 60
misago/apps/threads/thread.py

@@ -1,61 +1,61 @@
-from django.utils.translation import ugettext as _
-from misago.apps.threadtype.thread import ThreadBaseView, ThreadModeration, PostsModeration
-from misago.models import Forum, Thread
-from misago.apps.threads.mixins import TypeMixin
-
-class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
-    def posts_actions(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        actions = []
-        try:
-            if acl['can_approve'] and self.thread.replies_moderated > 0:
-                actions.append(('accept', _('Accept posts')))
-            if acl['can_move_threads_posts']:
-                actions.append(('merge', _('Merge posts into one')))
-                actions.append(('split', _('Split posts to new thread')))
-                actions.append(('move', _('Move posts to other thread')))
-            if acl['can_protect_posts']:
-                actions.append(('protect', _('Protect posts')))
-                actions.append(('unprotect', _('Remove posts protection')))
-            if acl['can_delete_posts']:
-                if self.thread.replies_deleted > 0:
-                    actions.append(('undelete', _('Restore posts')))
-                actions.append(('soft', _('Hide posts')))
-            if acl['can_delete_posts'] == 2:
-                actions.append(('hard', _('Delete posts')))
-        except KeyError:
-            pass
-        return actions
-
-    def thread_actions(self):
-        acl = self.request.acl.threads.get_role(self.thread.forum_id)
-        actions = []
-        try:
-            if acl['can_approve'] and self.thread.moderated:
-                actions.append(('accept', _('Accept this thread')))
-            if acl['can_pin_threads'] == 2 and self.thread.weight < 2:
-                actions.append(('annouce', _('Change this thread to announcement')))
-            if acl['can_pin_threads'] > 0 and self.thread.weight != 1:
-                actions.append(('sticky', _('Change this thread to sticky')))
-            if acl['can_pin_threads'] > 0:
-                if self.thread.weight == 2:
-                    actions.append(('normal', _('Change this thread to normal')))
-                if self.thread.weight == 1:
-                    actions.append(('normal', _('Unpin this thread')))
-            if acl['can_move_threads_posts']:
-                actions.append(('move', _('Move this thread')))
-            if acl['can_close_threads']:
-                if self.thread.closed:
-                    actions.append(('open', _('Open this thread')))
-                else:
-                    actions.append(('close', _('Close this thread')))
-            if acl['can_delete_threads']:
-                if self.thread.deleted:
-                    actions.append(('undelete', _('Restore this thread')))
-                else:
-                    actions.append(('soft', _('Hide this thread')))
-            if acl['can_delete_threads'] == 2:
-                actions.append(('hard', _('Delete this thread')))
-        except KeyError:
-            pass
+from django.utils.translation import ugettext as _
+from misago.apps.threadtype.thread import ThreadBaseView, ThreadModeration, PostsModeration
+from misago.models import Forum, Thread
+from misago.apps.threads.mixins import TypeMixin
+
+class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
+    def posts_actions(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        actions = []
+        try:
+            if acl['can_approve'] and self.thread.replies_moderated > 0:
+                actions.append(('accept', _('Accept posts')))
+            if acl['can_move_threads_posts']:
+                actions.append(('merge', _('Merge posts into one')))
+                actions.append(('split', _('Split posts to new thread')))
+                actions.append(('move', _('Move posts to other thread')))
+            if acl['can_protect_posts']:
+                actions.append(('protect', _('Protect posts')))
+                actions.append(('unprotect', _('Remove posts protection')))
+            if acl['can_delete_posts']:
+                if self.thread.replies_deleted > 0:
+                    actions.append(('undelete', _('Restore posts')))
+                actions.append(('soft', _('Hide posts')))
+            if acl['can_delete_posts'] == 2:
+                actions.append(('hard', _('Delete posts')))
+        except KeyError:
+            pass
+        return actions
+
+    def thread_actions(self):
+        acl = self.request.acl.threads.get_role(self.thread.forum_id)
+        actions = []
+        try:
+            if acl['can_approve'] and self.thread.moderated:
+                actions.append(('accept', _('Accept this thread')))
+            if acl['can_pin_threads'] == 2 and self.thread.weight < 2:
+                actions.append(('annouce', _('Change this thread to announcement')))
+            if acl['can_pin_threads'] > 0 and self.thread.weight != 1:
+                actions.append(('sticky', _('Change this thread to sticky')))
+            if acl['can_pin_threads'] > 0:
+                if self.thread.weight == 2:
+                    actions.append(('normal', _('Change this thread to normal')))
+                if self.thread.weight == 1:
+                    actions.append(('normal', _('Unpin this thread')))
+            if acl['can_move_threads_posts']:
+                actions.append(('move', _('Move this thread')))
+            if acl['can_close_threads']:
+                if self.thread.closed:
+                    actions.append(('open', _('Open this thread')))
+                else:
+                    actions.append(('close', _('Close this thread')))
+            if acl['can_delete_threads']:
+                if self.thread.deleted:
+                    actions.append(('undelete', _('Restore this thread')))
+                else:
+                    actions.append(('soft', _('Hide this thread')))
+            if acl['can_delete_threads'] == 2:
+                actions.append(('hard', _('Delete this thread')))
+        except KeyError:
+            pass
         return actions

+ 41 - 41
misago/apps/threads/urls.py

@@ -1,41 +1,41 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.threads',
-    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'list.ThreadsListView', name="forum"),
-    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'list.ThreadsListView', name="forum"),
-    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/start/$', 'posting.NewThreadView', name="thread_start"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'posting.EditThreadView', name="thread_edit"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="thread_reply"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="thread_reply"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="post_edit"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="thread"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'thread.ThreadView', name="thread"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="thread_last"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="thread_find"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="thread_new"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/moderated/$', 'jumps.FirstModeratedView', name="thread_moderated"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'jumps.FirstReportedView', name="thread_reported"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="thread_show_hidden"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="thread_watch"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="thread_watch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="thread_unwatch"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="thread_unwatch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/upvote/$', 'jumps.UpvotePostView', name="post_upvote"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', 'jumps.DownvotePostView', name="post_downvote"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/report/$', 'jumps.ReportPostView', name="post_report"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show-report/$', 'jumps.ShowPostReportView', name="post_report_show"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="thread_delete"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="thread_hide"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="thread_show"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="post_delete"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="post_hide"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="post_show"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="post_checkpoint_delete"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="post_checkpoint_hide"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="post_checkpoint_show"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="post_info"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/votes/$', 'details.KarmaVotesView', name="post_votes"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="thread_changelog"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="thread_changelog_diff"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="thread_changelog_revert"),
-)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.threads',
+    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'list.ThreadsListView', name="forum"),
+    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'list.ThreadsListView', name="forum"),
+    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/start/$', 'posting.NewThreadView', name="thread_start"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', 'posting.EditThreadView', name="thread_edit"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'posting.NewReplyView', name="thread_reply"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<quote>\d+)/reply/$', 'posting.NewReplyView', name="thread_reply"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/edit/$', 'posting.EditReplyView', name="post_edit"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread.ThreadView', name="thread"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>[1-9]([0-9]+)?)/$', 'thread.ThreadView', name="thread"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="thread_last"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="thread_find"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="thread_new"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/moderated/$', 'jumps.FirstModeratedView', name="thread_moderated"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'jumps.FirstReportedView', name="thread_reported"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="thread_show_hidden"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="thread_watch"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="thread_watch_email"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="thread_unwatch"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="thread_unwatch_email"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/upvote/$', 'jumps.UpvotePostView', name="post_upvote"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', 'jumps.DownvotePostView', name="post_downvote"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/report/$', 'jumps.ReportPostView', name="post_report"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show-report/$', 'jumps.ShowPostReportView', name="post_report_show"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="thread_delete"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="thread_hide"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', 'delete.ShowThreadView', name="thread_show"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="post_delete"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="post_hide"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', 'delete.ShowReplyView', name="post_show"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/delete/$', 'delete.DeleteCheckpointView', name="post_checkpoint_delete"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/hide/$', 'delete.HideCheckpointView', name="post_checkpoint_hide"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/checkpoint/(?P<checkpoint>\d+)/show/$', 'delete.ShowCheckpointView', name="post_checkpoint_show"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="post_info"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/votes/$', 'details.KarmaVotesView', name="post_votes"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="thread_changelog"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="thread_changelog_diff"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="thread_changelog_revert"),
+)

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

@@ -1,2 +1,2 @@
-from misago.apps.threadtype.list.views import ThreadsListBaseView
+from misago.apps.threadtype.list.views import ThreadsListBaseView
 from misago.apps.threadtype.list.moderation import ThreadsListModeration

+ 3 - 3
misago/apps/threadtype/posting/__init__.py

@@ -1,4 +1,4 @@
-from misago.apps.threadtype.posting.newthread import NewThreadBaseView
-from misago.apps.threadtype.posting.editthread import EditThreadBaseView
-from misago.apps.threadtype.posting.newreply import NewReplyBaseView
+from misago.apps.threadtype.posting.newthread import NewThreadBaseView
+from misago.apps.threadtype.posting.editthread import EditThreadBaseView
+from misago.apps.threadtype.posting.newreply import NewReplyBaseView
 from misago.apps.threadtype.posting.editreply import EditReplyBaseView

+ 51 - 51
misago/apps/threadtype/posting/editreply.py

@@ -1,52 +1,52 @@
-from django.utils import timezone
-from misago.apps.threadtype.posting.base import PostingBaseView
-from misago.apps.threadtype.posting.forms import EditReplyForm
-from misago.markdown import post_markdown
-
-class EditReplyBaseView(PostingBaseView):
-    action = 'edit_reply'
-    form_type = EditReplyForm
-    block_flood_requests = False
-
-    def set_context(self):
-        self.set_thread_context()
-        self.post = self.thread.post_set.get(id=self.kwargs.get('post'))
-        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
-        self.request.acl.threads.allow_reply_edit(self.request.user, self.proxy, self.thread, self.post)
-
-    def form_initial_data(self):
-        return {
-                'weight': self.thread.weight,
-                'post': self.post.post,
-                }
-
-    def post_form(self, form):
-        old_post = self.post.post
-
-        changed_thread = False
-        changed_post = old_post != form.cleaned_data['post']
-
-        if self.thread.last_post_id == self.post.pk:
-            self.thread.last_post == self.post
-
-        if 'close_thread' in form.cleaned_data and form.cleaned_data['close_thread']:
-            self.thread.closed = not self.thread.closed
-            changed_thread = True
-            if self.thread.closed:
-                self.thread.set_checkpoint(self.request, 'closed')
-            else:
-                self.thread.set_checkpoint(self.request, 'opened')
-
-        if ('thread_weight' in form.cleaned_data and
-                form.cleaned_data['thread_weight'] != self.thread.weight):
-            self.thread.weight = form.cleaned_data['thread_weight']
-            changed_thread = True
-
-        if changed_thread:
-            self.thread.save(force_update=True)
-
-        if changed_post:
-            self.post.post = form.cleaned_data['post']
-            self.md, self.post.post_preparsed = post_markdown(form.cleaned_data['post'])
-            self.post.save(force_update=True)
+from django.utils import timezone
+from misago.apps.threadtype.posting.base import PostingBaseView
+from misago.apps.threadtype.posting.forms import EditReplyForm
+from misago.markdown import post_markdown
+
+class EditReplyBaseView(PostingBaseView):
+    action = 'edit_reply'
+    form_type = EditReplyForm
+    block_flood_requests = False
+
+    def set_context(self):
+        self.set_thread_context()
+        self.post = self.thread.post_set.get(id=self.kwargs.get('post'))
+        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
+        self.request.acl.threads.allow_reply_edit(self.request.user, self.proxy, self.thread, self.post)
+
+    def form_initial_data(self):
+        return {
+                'weight': self.thread.weight,
+                'post': self.post.post,
+                }
+
+    def post_form(self, form):
+        old_post = self.post.post
+
+        changed_thread = False
+        changed_post = old_post != form.cleaned_data['post']
+
+        if self.thread.last_post_id == self.post.pk:
+            self.thread.last_post == self.post
+
+        if 'close_thread' in form.cleaned_data and form.cleaned_data['close_thread']:
+            self.thread.closed = not self.thread.closed
+            changed_thread = True
+            if self.thread.closed:
+                self.thread.set_checkpoint(self.request, 'closed')
+            else:
+                self.thread.set_checkpoint(self.request, 'opened')
+
+        if ('thread_weight' in form.cleaned_data and
+                form.cleaned_data['thread_weight'] != self.thread.weight):
+            self.thread.weight = form.cleaned_data['thread_weight']
+            changed_thread = True
+
+        if changed_thread:
+            self.thread.save(force_update=True)
+
+        if changed_post:
+            self.post.post = form.cleaned_data['post']
+            self.md, self.post.post_preparsed = post_markdown(form.cleaned_data['post'])
+            self.post.save(force_update=True)
             self.record_edit(form, self.thread.name, old_post)

+ 66 - 66
misago/apps/threadtype/posting/editthread.py

@@ -1,66 +1,66 @@
-from django.utils import timezone
-from misago.apps.threadtype.posting.base import PostingBaseView
-from misago.apps.threadtype.posting.forms import EditThreadForm
-from misago.markdown import post_markdown
-from misago.utils.strings import slugify
-
-class EditThreadBaseView(PostingBaseView):
-    action = 'edit_thread'
-    form_type = EditThreadForm
-    block_flood_requests = False
-
-    def set_context(self):
-        self.set_thread_context()
-        self.post = self.thread.start_post
-        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
-        self.request.acl.threads.allow_thread_edit(self.request.user, self.proxy, self.thread, self.post)
-        
-    def form_initial_data(self):
-        return {
-                'thread_name': self.thread.name,
-                'weight': self.thread.weight,
-                'post': self.post.post,
-                }
-
-    def post_form(self, form):
-        old_name = self.thread.name
-        old_post = self.post.post
-
-        changed_thread = old_name != form.cleaned_data['thread_name']
-        changed_post = old_post != form.cleaned_data['post']
-
-        if self.thread.last_post_id == self.post.pk:
-            self.thread.last_post == self.post
-
-        if 'close_thread' in form.cleaned_data and form.cleaned_data['close_thread']:
-            self.thread.closed = not self.thread.closed
-            changed_thread = True
-            if self.thread.closed:
-                self.thread.set_checkpoint(self.request, 'closed')
-            else:
-                self.thread.set_checkpoint(self.request, 'opened')
-
-        if ('thread_weight' in form.cleaned_data and
-                form.cleaned_data['thread_weight'] != self.thread.weight):
-            self.thread.weight = form.cleaned_data['thread_weight']
-            changed_thread = True
-
-        if changed_thread:
-            self.thread.name = form.cleaned_data['thread_name']
-            self.thread.slug = slugify(form.cleaned_data['thread_name'])
-            self.thread.save(force_update=True)
-            if self.forum.last_thread_id == self.thread.pk:
-                self.forum.last_thread_name = self.thread.name
-                self.forum.last_thread_slug = self.thread.slug
-                self.forum.save(force_update=True)
-
-        if changed_post:
-            self.post.post = form.cleaned_data['post']
-            self.md, self.post.post_preparsed = post_markdown(form.cleaned_data['post'])
-            self.post.save(force_update=True)
-
-        if old_name != form.cleaned_data['thread_name']:
-            self.thread.update_current_dates()
-
-        if changed_thread or changed_post:
-            self.record_edit(form, old_name, old_post)
+from django.utils import timezone
+from misago.apps.threadtype.posting.base import PostingBaseView
+from misago.apps.threadtype.posting.forms import EditThreadForm
+from misago.markdown import post_markdown
+from misago.utils.strings import slugify
+
+class EditThreadBaseView(PostingBaseView):
+    action = 'edit_thread'
+    form_type = EditThreadForm
+    block_flood_requests = False
+
+    def set_context(self):
+        self.set_thread_context()
+        self.post = self.thread.start_post
+        self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
+        self.request.acl.threads.allow_thread_edit(self.request.user, self.proxy, self.thread, self.post)
+        
+    def form_initial_data(self):
+        return {
+                'thread_name': self.thread.name,
+                'weight': self.thread.weight,
+                'post': self.post.post,
+                }
+
+    def post_form(self, form):
+        old_name = self.thread.name
+        old_post = self.post.post
+
+        changed_thread = old_name != form.cleaned_data['thread_name']
+        changed_post = old_post != form.cleaned_data['post']
+
+        if self.thread.last_post_id == self.post.pk:
+            self.thread.last_post == self.post
+
+        if 'close_thread' in form.cleaned_data and form.cleaned_data['close_thread']:
+            self.thread.closed = not self.thread.closed
+            changed_thread = True
+            if self.thread.closed:
+                self.thread.set_checkpoint(self.request, 'closed')
+            else:
+                self.thread.set_checkpoint(self.request, 'opened')
+
+        if ('thread_weight' in form.cleaned_data and
+                form.cleaned_data['thread_weight'] != self.thread.weight):
+            self.thread.weight = form.cleaned_data['thread_weight']
+            changed_thread = True
+
+        if changed_thread:
+            self.thread.name = form.cleaned_data['thread_name']
+            self.thread.slug = slugify(form.cleaned_data['thread_name'])
+            self.thread.save(force_update=True)
+            if self.forum.last_thread_id == self.thread.pk:
+                self.forum.last_thread_name = self.thread.name
+                self.forum.last_thread_slug = self.thread.slug
+                self.forum.save(force_update=True)
+
+        if changed_post:
+            self.post.post = form.cleaned_data['post']
+            self.md, self.post.post_preparsed = post_markdown(form.cleaned_data['post'])
+            self.post.save(force_update=True)
+
+        if old_name != form.cleaned_data['thread_name']:
+            self.thread.update_current_dates()
+
+        if changed_thread or changed_post:
+            self.record_edit(form, old_name, old_post)

+ 2 - 2
misago/apps/threadtype/thread/__init__.py

@@ -1,3 +1,3 @@
-from misago.apps.threadtype.thread.views import ThreadBaseView
-from misago.apps.threadtype.thread.moderation.thread import ThreadModeration
+from misago.apps.threadtype.thread.views import ThreadBaseView
+from misago.apps.threadtype.thread.moderation.thread import ThreadModeration
 from misago.apps.threadtype.thread.moderation.posts import PostsModeration

+ 21 - 21
misago/apps/usercp/avatar/urls.py

@@ -1,21 +1,21 @@
-from django.conf.urls import patterns, url
-
-def register_usercp_urls(first=False):
-    urlpatterns = []
-    if first:
-        urlpatterns += patterns('misago.apps.usercp.avatar.views',
-            url(r'^$', 'avatar', name="usercp"),
-            url(r'^$', 'avatar', name="usercp_avatar"),
-        )
-    else:
-        urlpatterns += patterns('misago.apps.usercp.avatar.views',
-            url(r'^avatar/$', 'avatar', name="usercp_avatar"),
-        )
-    urlpatterns += patterns('misago.apps.usercp.avatar.views',
-        url(r'^avatar/gallery/$', 'gallery', name="usercp_avatar_gallery"),
-        url(r'^avatar/upload/$', 'upload', name="usercp_avatar_upload"),
-        url(r'^avatar/upload/crop/$', 'crop', name="usercp_avatar_upload_crop", kwargs={'upload': True}),
-        url(r'^avatar/crop/$', 'crop', name="usercp_avatar_crop"),
-        url(r'^avatar/gravatar/$', 'gravatar', name="usercp_avatar_gravatar"),
-    )
-    return urlpatterns
+from django.conf.urls import patterns, url
+
+def register_usercp_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.apps.usercp.avatar.views',
+            url(r'^$', 'avatar', name="usercp"),
+            url(r'^$', 'avatar', name="usercp_avatar"),
+        )
+    else:
+        urlpatterns += patterns('misago.apps.usercp.avatar.views',
+            url(r'^avatar/$', 'avatar', name="usercp_avatar"),
+        )
+    urlpatterns += patterns('misago.apps.usercp.avatar.views',
+        url(r'^avatar/gallery/$', 'gallery', name="usercp_avatar_gallery"),
+        url(r'^avatar/upload/$', 'upload', name="usercp_avatar_upload"),
+        url(r'^avatar/upload/crop/$', 'crop', name="usercp_avatar_upload_crop", kwargs={'upload': True}),
+        url(r'^avatar/crop/$', 'crop', name="usercp_avatar_crop"),
+        url(r'^avatar/gravatar/$', 'gravatar', name="usercp_avatar_gravatar"),
+    )
+    return urlpatterns

+ 4 - 4
misago/apps/usercp/avatar/usercp.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_usercp_extension(request):
-    return (('usercp_avatar', _('Change Avatar')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_usercp_extension(request):
+    return (('usercp_avatar', _('Change Avatar')),)

+ 4 - 4
misago/apps/usercp/credentials/usercp.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_usercp_extension(request):
-    return (('usercp_credentials', _('Change E-mail or Password')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_usercp_extension(request):
+    return (('usercp_credentials', _('Change E-mail or Password')),)

+ 11 - 11
misago/apps/usercp/options/urls.py

@@ -1,11 +1,11 @@
-from django.conf.urls import patterns, url
-
-def register_usercp_urls(first=False):
-    if first:
-        return patterns('misago.apps.usercp.options.views',
-            url(r'^$', 'options', name="usercp"),
-            url(r'^$', 'options', name="usercp_options"),
-        )
-    return patterns('misago.apps.usercp.options.views',
-        url(r'^options/$', 'options', name="usercp_options"),
-    )
+from django.conf.urls import patterns, url
+
+def register_usercp_urls(first=False):
+    if first:
+        return patterns('misago.apps.usercp.options.views',
+            url(r'^$', 'options', name="usercp"),
+            url(r'^$', 'options', name="usercp_options"),
+        )
+    return patterns('misago.apps.usercp.options.views',
+        url(r'^options/$', 'options', name="usercp_options"),
+    )

+ 4 - 4
misago/apps/usercp/options/usercp.py

@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_usercp_extension(request):
-    return (('usercp_options', _('Forum Options')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_usercp_extension(request):
+    return (('usercp_options', _('Forum Options')),)

+ 12 - 12
misago/apps/usercp/signature/urls.py

@@ -1,12 +1,12 @@
-from django.conf.urls import patterns, url
-
-def register_usercp_urls(first=False):
-    if first:
-        return patterns('misago.apps.usercp.signature.views',
-            url(r'^$', 'signature', name="usercp"),
-            url(r'^$', 'signature', name="usercp_signature"),
-        )
-    
-    return patterns('misago.apps.usercp.signature.views',
-        url(r'^signature/$', 'signature', name="usercp_signature"),
-    )
+from django.conf.urls import patterns, url
+
+def register_usercp_urls(first=False):
+    if first:
+        return patterns('misago.apps.usercp.signature.views',
+            url(r'^$', 'signature', name="usercp"),
+            url(r'^$', 'signature', name="usercp_signature"),
+        )
+    
+    return patterns('misago.apps.usercp.signature.views',
+        url(r'^signature/$', 'signature', name="usercp_signature"),
+    )

+ 5 - 5
misago/apps/usercp/signature/usercp.py

@@ -1,5 +1,5 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_usercp_extension(request):
-    if request.acl.usercp.can_use_signature():
-        return (('usercp_signature', _('Edit Signature')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_usercp_extension(request):
+    if request.acl.usercp.can_use_signature():
+        return (('usercp_signature', _('Edit Signature')),)

+ 25 - 25
misago/apps/usercp/template.py

@@ -1,25 +1,25 @@
-from django.conf import settings
-from django.template import RequestContext as DjangoRequestContext
-from django.utils.importlib import import_module
-
-def RequestContext(request, context=None):
-    if not context:
-        context = {}
-    context['tabs'] = []
-    for extension in settings.USERCP_EXTENSIONS:
-        usercp_module = import_module(extension + '.usercp')
-        try:
-            append_links = usercp_module.register_usercp_extension(request)
-            if append_links:
-                for link in append_links:
-                    link = list(link)
-                    token = link[0][link[0].find('_') + 1:]
-                    context['tabs'].append({
-                                            'route': link[0],
-                                            'active': context['tab'] == token,
-                                            'name': link[1],
-                                            })
-        except AttributeError:
-            pass
-
-    return DjangoRequestContext(request, context)
+from django.conf import settings
+from django.template import RequestContext as DjangoRequestContext
+from django.utils.importlib import import_module
+
+def RequestContext(request, context=None):
+    if not context:
+        context = {}
+    context['tabs'] = []
+    for extension in settings.USERCP_EXTENSIONS:
+        usercp_module = import_module(extension + '.usercp')
+        try:
+            append_links = usercp_module.register_usercp_extension(request)
+            if append_links:
+                for link in append_links:
+                    link = list(link)
+                    token = link[0][link[0].find('_') + 1:]
+                    context['tabs'].append({
+                                            'route': link[0],
+                                            'active': context['tab'] == token,
+                                            'name': link[1],
+                                            })
+        except AttributeError:
+            pass
+
+    return DjangoRequestContext(request, context)

+ 21 - 21
misago/apps/usercp/urls.py

@@ -1,22 +1,22 @@
-from django.conf import settings
-from django.conf.urls import include, patterns, url
-from django.utils.importlib import import_module
-
-urlpatterns = []
-iteration = 0
-for extension in settings.USERCP_EXTENSIONS:
-    iteration += 1
-    usercp_module = import_module(extension + '.urls')
-    try:
-        urlpatterns += patterns('',
-            (r'^', include(usercp_module.register_usercp_urls(iteration == 1))),
-        )
-    except AttributeError:
-        pass
-
-urlpatterns += patterns('misago.apps.usercp.views',
-    url(r'^follow/(?P<user>\d+)/$', 'follow', name="follow_user"),
-    url(r'^unfollow/(?P<user>\d+)/$', 'unfollow', name="unfollow_user"),
-    url(r'^ignore/(?P<user>\d+)/$', 'ignore', name="ignore_user"),
-    url(r'^unignore/(?P<user>\d+)/$', 'unignore', name="unignore_user"),
+from django.conf import settings
+from django.conf.urls import include, patterns, url
+from django.utils.importlib import import_module
+
+urlpatterns = []
+iteration = 0
+for extension in settings.USERCP_EXTENSIONS:
+    iteration += 1
+    usercp_module = import_module(extension + '.urls')
+    try:
+        urlpatterns += patterns('',
+            (r'^', include(usercp_module.register_usercp_urls(iteration == 1))),
+        )
+    except AttributeError:
+        pass
+
+urlpatterns += patterns('misago.apps.usercp.views',
+    url(r'^follow/(?P<user>\d+)/$', 'follow', name="follow_user"),
+    url(r'^unfollow/(?P<user>\d+)/$', 'unfollow', name="unfollow_user"),
+    url(r'^ignore/(?P<user>\d+)/$', 'ignore', name="ignore_user"),
+    url(r'^unignore/(?P<user>\d+)/$', 'unignore', name="unignore_user"),
 )

+ 11 - 11
misago/apps/usercp/username/urls.py

@@ -1,11 +1,11 @@
-from django.conf.urls import patterns, url
-
-def register_usercp_urls(first=False):
-    if first:
-        return patterns('misago.apps.usercp.username.views',
-            url(r'^$', 'username', name="usercp"),
-            url(r'^$', 'username', name="usercp_username"),
-        )
-    return patterns('misago.apps.usercp.username.views',
-        url(r'^username/$', 'username', name="usercp_username"),
-    )
+from django.conf.urls import patterns, url
+
+def register_usercp_urls(first=False):
+    if first:
+        return patterns('misago.apps.usercp.username.views',
+            url(r'^$', 'username', name="usercp"),
+            url(r'^$', 'username', name="usercp_username"),
+        )
+    return patterns('misago.apps.usercp.username.views',
+        url(r'^username/$', 'username', name="usercp_username"),
+    )

+ 5 - 5
misago/apps/usercp/username/usercp.py

@@ -1,5 +1,5 @@
-from django.utils.translation import ugettext_lazy as _
-
-def register_usercp_extension(request):
-    if request.acl.usercp.show_username_change():
-        return (('usercp_username', _('Change Username')),)
+from django.utils.translation import ugettext_lazy as _
+
+def register_usercp_extension(request):
+    if request.acl.usercp.show_username_change():
+        return (('usercp_username', _('Change Username')),)

+ 8 - 8
misago/apps/watchedthreads/urls.py

@@ -1,8 +1,8 @@
-from django.conf.urls import patterns, url
-
-urlpatterns = patterns('misago.apps.watchedthreads.views',
-    url(r'^$', 'watched_threads', name="watched_threads"),
-    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'watched_threads', name="watched_threads"),
-    url(r'^new/$', 'watched_threads', name="watched_threads_new", kwargs={'new': True}),
-    url(r'^new/(?P<page>[1-9]([0-9]+)?)/$', 'watched_threads', name="watched_threads_new", kwargs={'new': True}),
-)
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.apps.watchedthreads.views',
+    url(r'^$', 'watched_threads', name="watched_threads"),
+    url(r'^(?P<page>[1-9]([0-9]+)?)/$', 'watched_threads', name="watched_threads"),
+    url(r'^new/$', 'watched_threads', name="watched_threads_new", kwargs={'new': True}),
+    url(r'^new/(?P<page>[1-9]([0-9]+)?)/$', 'watched_threads', name="watched_threads_new", kwargs={'new': True}),
+)

+ 73 - 73
misago/decorators.py

@@ -1,74 +1,74 @@
-from django.utils.translation import ugettext as _
-from misago.acl.exceptions import ACLError403, ACLError404
-from misago.apps.errors import error403, error404, error_banned
-
-def acl_errors(f):
-    def decorator(*args, **kwargs):
-        try:
-            return f(*args, **kwargs)
-        except ACLError403 as e:
-            return error403(args[0], e)
-        except ACLError404 as e:
-            return error404(args[0], e)
-    return decorator
-
-
-def block_authenticated(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.firewall.admin and request.user.is_authenticated():
-            return error403(request, _("%(username)s, this page is not available to signed in users.") % {'username': request.user.username})
-        return f(*args, **kwargs)
-    return decorator
-
-
-def block_banned(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        try:
-            if request.ban.is_banned():
-                return error_banned(request);
-            return f(*args, **kwargs)
-        except AttributeError:
-            pass
-        return f(*args, **kwargs)
-    return decorator
-
-
-def block_crawlers(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if request.user.is_crawler():
-            return error403(request)
-        return f(*args, **kwargs)
-    return decorator
-
-
-def block_guest(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.user.is_authenticated():
-            return error403(request, _("Dear Guest, only signed in members are allowed to access this page. Please sign in or register and try again."))
-        return f(*args, **kwargs)
-    return decorator
-
-
-def block_jammed(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        try:
-            if not request.firewall.admin and request.jam.is_jammed():
-                return error403(request, _("You have used up allowed attempts quota and we temporarily banned you from accessing this page."))
-        except AttributeError:
-            pass
-        return f(*args, **kwargs)
-    return decorator
-
-
-def check_csrf(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.csrf.request_secure(request):
-            return error403(request, _("Request authorization is invalid. Please try again."))
-        return f(*args, **kwargs)
+from django.utils.translation import ugettext as _
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.apps.errors import error403, error404, error_banned
+
+def acl_errors(f):
+    def decorator(*args, **kwargs):
+        try:
+            return f(*args, **kwargs)
+        except ACLError403 as e:
+            return error403(args[0], e)
+        except ACLError404 as e:
+            return error404(args[0], e)
+    return decorator
+
+
+def block_authenticated(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.firewall.admin and request.user.is_authenticated():
+            return error403(request, _("%(username)s, this page is not available to signed in users.") % {'username': request.user.username})
+        return f(*args, **kwargs)
+    return decorator
+
+
+def block_banned(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        try:
+            if request.ban.is_banned():
+                return error_banned(request);
+            return f(*args, **kwargs)
+        except AttributeError:
+            pass
+        return f(*args, **kwargs)
+    return decorator
+
+
+def block_crawlers(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if request.user.is_crawler():
+            return error403(request)
+        return f(*args, **kwargs)
+    return decorator
+
+
+def block_guest(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.user.is_authenticated():
+            return error403(request, _("Dear Guest, only signed in members are allowed to access this page. Please sign in or register and try again."))
+        return f(*args, **kwargs)
+    return decorator
+
+
+def block_jammed(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        try:
+            if not request.firewall.admin and request.jam.is_jammed():
+                return error403(request, _("You have used up allowed attempts quota and we temporarily banned you from accessing this page."))
+        except AttributeError:
+            pass
+        return f(*args, **kwargs)
+    return decorator
+
+
+def check_csrf(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.csrf.request_secure(request):
+            return error403(request, _("Request authorization is invalid. Please try again."))
+        return f(*args, **kwargs)
     return decorator

+ 109 - 109
misago/fixtures/accountssetings.py

@@ -1,110 +1,110 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-    # Register and Sign-In Settings
-    ('accounts', {
-        'name': _("Users Accounts Settings"),
-        'description': _("Those settings allow you to increase security of your members accounts."),
-        'settings': (
-            ('account_activation', {
-                'value':        "none",
-                'type':         "string",
-                'input':        "choice",
-                'extra':        {'choices': [('none', _("No validation required")), ('user', _("Activation Token sent to User")), ('admin', _("Activation by Administrator")), ('block', _("Dont allow new registrations"))]},
-                'separator':    _("Users Registrations"),
-                'name':         _("New accounts validation"),
-            }),
-            ('default_timezone', {
-                'value':        "utc",
-                'type':         "string",
-                'input':        "select",
-                'extra':        {'choices': '#TZ#'},
-                'name':         _("Default Timezone"),
-                'description':  _("Used by guests, crawlers and newly registered users."),
-            }),
-            ('username_length_min', {
-                'value':        3,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 3},
-                'separator':    _("Users Names"),
-                'name':         _("Minimum allowed username length"),
-            }),
-            ('username_length_max', {
-                'value':        16,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 3},
-                'name':         _("Maxim allowed username length"),
-            }),
-            ('password_length', {
-                'value':        4,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 1},
-                'separator':    _("Users Passwords"),
-                'name':         _("Minimum user password length"),
-            }),
-            ('password_complexity', {
-                'value':        [],
-                'type':         "array",
-                'input':        "mlist",
-                'extra':        {'choices': [('case', _("Require mixed Case")), ('digits', _("Require digits")), ('special', _("Require special characters"))]},
-                'name':         _("Password Complexity"),
-            }),
-            ('password_lifetime', {
-                'value':        0,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0},
-                'name':         _("Password Lifetime"),
-                'description':  _("Enter number of days since password was set to force member to change it with new one, or 0 to dont force your members to change their passwords."),
-            }),
-            ('password_in_email', {
-                'value':        False,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Include User Password in Welcoming E-mail"),
-                'description':  _("If you want to, Misago can include new user password in welcoming e-mail that is sent to new users after successful account creation."),
-            }),
-            ('subscribe_start', {
-                'value':        2,
-                'type':         "integer",
-                'input':        "select",
-                'extra':        {'choices': ((0, _("Don't watch")),
-                                             (1, _("Put on watched threads list")),
-                                             (2, _("Put on watched threads list and e-mail user when somebody replies")),
-                                             )},
-                'separator':    _("Default Watching Preferences"),
-                'name':         _("Watch threads user started"),
-            }),
-            ('subscribe_reply', {
-                'value':        2,
-                'type':         "integer",
-                'input':        "select",
-                'extra':        {'choices': ((0, _("Don't watch")),
-                                             (1, _("Put on watched threads list")),
-                                             (2, _("Put on watched threads list and e-mail user when somebody replies")),
-                                             )},
-                'name':         _("Watch threads user replied in"),
-            }),
-            ('profiles_per_list', {
-                'value':        24,
-                'type':         "integer",
-                'input':        "string",
-                'extra':        {'min': 1, 'max': 128},
-                'separator':    _("Users List"),
-                'name':         _("Number of Profiles Per Page"),
-            }),
-        ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-    
-    
-def update():
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+    # Register and Sign-In Settings
+    ('accounts', {
+        'name': _("Users Accounts Settings"),
+        'description': _("Those settings allow you to increase security of your members accounts."),
+        'settings': (
+            ('account_activation', {
+                'value':        "none",
+                'type':         "string",
+                'input':        "choice",
+                'extra':        {'choices': [('none', _("No validation required")), ('user', _("Activation Token sent to User")), ('admin', _("Activation by Administrator")), ('block', _("Dont allow new registrations"))]},
+                'separator':    _("Users Registrations"),
+                'name':         _("New accounts validation"),
+            }),
+            ('default_timezone', {
+                'value':        "utc",
+                'type':         "string",
+                'input':        "select",
+                'extra':        {'choices': '#TZ#'},
+                'name':         _("Default Timezone"),
+                'description':  _("Used by guests, crawlers and newly registered users."),
+            }),
+            ('username_length_min', {
+                'value':        3,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 3},
+                'separator':    _("Users Names"),
+                'name':         _("Minimum allowed username length"),
+            }),
+            ('username_length_max', {
+                'value':        16,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 3},
+                'name':         _("Maxim allowed username length"),
+            }),
+            ('password_length', {
+                'value':        4,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 1},
+                'separator':    _("Users Passwords"),
+                'name':         _("Minimum user password length"),
+            }),
+            ('password_complexity', {
+                'value':        [],
+                'type':         "array",
+                'input':        "mlist",
+                'extra':        {'choices': [('case', _("Require mixed Case")), ('digits', _("Require digits")), ('special', _("Require special characters"))]},
+                'name':         _("Password Complexity"),
+            }),
+            ('password_lifetime', {
+                'value':        0,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _("Password Lifetime"),
+                'description':  _("Enter number of days since password was set to force member to change it with new one, or 0 to dont force your members to change their passwords."),
+            }),
+            ('password_in_email', {
+                'value':        False,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Include User Password in Welcoming E-mail"),
+                'description':  _("If you want to, Misago can include new user password in welcoming e-mail that is sent to new users after successful account creation."),
+            }),
+            ('subscribe_start', {
+                'value':        2,
+                'type':         "integer",
+                'input':        "select",
+                'extra':        {'choices': ((0, _("Don't watch")),
+                                             (1, _("Put on watched threads list")),
+                                             (2, _("Put on watched threads list and e-mail user when somebody replies")),
+                                             )},
+                'separator':    _("Default Watching Preferences"),
+                'name':         _("Watch threads user started"),
+            }),
+            ('subscribe_reply', {
+                'value':        2,
+                'type':         "integer",
+                'input':        "select",
+                'extra':        {'choices': ((0, _("Don't watch")),
+                                             (1, _("Put on watched threads list")),
+                                             (2, _("Put on watched threads list and e-mail user when somebody replies")),
+                                             )},
+                'name':         _("Watch threads user replied in"),
+            }),
+            ('profiles_per_list', {
+                'value':        24,
+                'type':         "integer",
+                'input':        "string",
+                'extra':        {'min': 1, 'max': 128},
+                'separator':    _("Users List"),
+                'name':         _("Number of Profiles Per Page"),
+            }),
+        ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+    
+    
+def update():
     update_settings_fixture(settings_fixture)

+ 12 - 12
misago/fixtures/aclmonitor.py

@@ -1,12 +1,12 @@
-from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
-
-monitor_fixture = {
-                   'acl_version': (0, 'int'),
-                  }
-
-def load():
-    load_monitor_fixture(monitor_fixture)
-
-
-def update():
-    update_monitor_fixture(monitor_fixture)
+from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
+
+monitor_fixture = {
+                   'acl_version': (0, 'int'),
+                  }
+
+def load():
+    load_monitor_fixture(monitor_fixture)
+
+
+def update():
+    update_monitor_fixture(monitor_fixture)

+ 13 - 13
misago/fixtures/bansmonitor.py

@@ -1,13 +1,13 @@
-from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
-
-monitor_fixture = {
-                   'bans_version': (0, 'int'),
-                  }
-
-
-def load():
-    load_monitor_fixture(monitor_fixture)
-
-
-def update():
-    update_monitor_fixture(monitor_fixture)
+from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
+
+monitor_fixture = {
+                   'bans_version': (0, 'int'),
+                  }
+
+
+def load():
+    load_monitor_fixture(monitor_fixture)
+
+
+def update():
+    update_monitor_fixture(monitor_fixture)

+ 72 - 72
misago/fixtures/basicsettings.py

@@ -1,72 +1,72 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-   # Basic options
-   ('basic', {
-        'name': _("Basic Settings"),
-        'settings': (
-            ('board_name', {
-                'value':        "Misago",
-                'type':         "string",
-                'input':        "text",
-                'separator':    _("Board Name"),
-                'name':         _("Board Name"),
-            }),
-            ('board_header', {
-                'type':         "string",
-                'input':        "text",
-                'name':         _("Board Header"),
-                'description':  _("Some themes allow you to define text in board header. Leave empty to use Board Name instead."),
-            }),
-            ('board_header_postscript', {
-                'value':        "Work in progress",
-                'type':         "string",
-                'input':        "text",
-                'name':         _("Board Header Postscript"),
-                'description':  _("Additional text displayed in some themes board header after board name."),
-            }),
-            ('board_index_title', {
-                'type':         "string",
-                'input':        "text",
-                'separator':    _("Board Index"),
-                'name':         _("Board Index Title"),
-                'description':  _("If you want to, you can replace page title content on Board Index with custom one."),
-            }),
-            ('board_index_meta', {
-                'type':         "string",
-                'input':        "text",
-                'name':         _("Board Index Meta-Description"),
-                'description':  _("Meta-Description used to describe your board's index page."),
-            }),
-            ('board_credits', {
-                'type':         "string",
-                'input':        "textarea",
-                'separator':    _("Board Footer"),
-                'name':         _("Custom Credit"),
-                'description':  _("Custom Credit to display in board footer above software and theme copyright information. You can use HTML."),
-            }),
-            ('email_footnote', {
-                'type':         "string",
-                'input':        "textarea",
-                'separator':    _("Board E-Mails"),
-                'name':         _("Custom Footnote in HTML E-mails"),
-                'description':  _("Custom Footnote to display in HTML e-mail messages sent by board."),
-            }),
-            ('email_footnote_plain', {
-                'type':         "string",
-                'input':        "textarea",
-                'name':         _("Custom Footnote in plain text E-mails"),
-                'description':  _("Custom Footnote to display in plain text e-mail messages sent by board."),
-            }),
-        ),
-   }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
-    update_settings_fixture(settings_fixture)
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+   # Basic options
+   ('basic', {
+        'name': _("Basic Settings"),
+        'settings': (
+            ('board_name', {
+                'value':        "Misago",
+                'type':         "string",
+                'input':        "text",
+                'separator':    _("Board Name"),
+                'name':         _("Board Name"),
+            }),
+            ('board_header', {
+                'type':         "string",
+                'input':        "text",
+                'name':         _("Board Header"),
+                'description':  _("Some themes allow you to define text in board header. Leave empty to use Board Name instead."),
+            }),
+            ('board_header_postscript', {
+                'value':        "Work in progress",
+                'type':         "string",
+                'input':        "text",
+                'name':         _("Board Header Postscript"),
+                'description':  _("Additional text displayed in some themes board header after board name."),
+            }),
+            ('board_index_title', {
+                'type':         "string",
+                'input':        "text",
+                'separator':    _("Board Index"),
+                'name':         _("Board Index Title"),
+                'description':  _("If you want to, you can replace page title content on Board Index with custom one."),
+            }),
+            ('board_index_meta', {
+                'type':         "string",
+                'input':        "text",
+                'name':         _("Board Index Meta-Description"),
+                'description':  _("Meta-Description used to describe your board's index page."),
+            }),
+            ('board_credits', {
+                'type':         "string",
+                'input':        "textarea",
+                'separator':    _("Board Footer"),
+                'name':         _("Custom Credit"),
+                'description':  _("Custom Credit to display in board footer above software and theme copyright information. You can use HTML."),
+            }),
+            ('email_footnote', {
+                'type':         "string",
+                'input':        "textarea",
+                'separator':    _("Board E-Mails"),
+                'name':         _("Custom Footnote in HTML E-mails"),
+                'description':  _("Custom Footnote to display in HTML e-mail messages sent by board."),
+            }),
+            ('email_footnote_plain', {
+                'type':         "string",
+                'input':        "textarea",
+                'name':         _("Custom Footnote in plain text E-mails"),
+                'description':  _("Custom Footnote to display in plain text e-mail messages sent by board."),
+            }),
+        ),
+   }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
+    update_settings_fixture(settings_fixture)

+ 45 - 45
misago/fixtures/bruteforcesettings.py

@@ -1,45 +1,45 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-    # Register and Sign-In Settings
-    ('brute-force', {
-        'name': _("Brute-force Countermeasures"),
-        'description': _("Those settings allow you to protect your forum from brute-force attacks."),
-        'settings': (
-            ('attempts_limit', {
-                'value':        3,
-                'default':      3,
-                'type':         "integer",
-                'input':        "text",
-                'separator':    _("Brute-force Countermeasures"),
-                'name':         _("IP invalid attempts limit"),
-                'description':  _('Enter maximal number of allowed attempts before IP address "jams". Defautly forum records only failed sign-in attempts.'),
-            }),
-            ('registrations_jams', {
-                'value':        False,
-                'default':      False,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Protect register form"),
-                'description':  _("Set this setting to yes if you want failed register attempts to count into limit. Majority of failed register attempts are caused by CAPTCHA protection against spam-bots, however same protection may cause problems for users with disabilities or ones that have problems understanding Q&A challenge."),
-            }),
-            ('jams_lifetime', {
-                'value':        15,
-                'default':      15,
-                'type':         "integer",
-                'input':        "text",
-                'name':         _("Automaticaly unlock jammed IPs"),
-                'description':  _('Enter number of minutes since IP address "jams" to automatically unlock it, or 0 to never unlock jammed IP adresses. Jams don\'t count as bans.'),
-            }),
-        ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
-    update_settings_fixture(settings_fixture)
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+    # Register and Sign-In Settings
+    ('brute-force', {
+        'name': _("Brute-force Countermeasures"),
+        'description': _("Those settings allow you to protect your forum from brute-force attacks."),
+        'settings': (
+            ('attempts_limit', {
+                'value':        3,
+                'default':      3,
+                'type':         "integer",
+                'input':        "text",
+                'separator':    _("Brute-force Countermeasures"),
+                'name':         _("IP invalid attempts limit"),
+                'description':  _('Enter maximal number of allowed attempts before IP address "jams". Defautly forum records only failed sign-in attempts.'),
+            }),
+            ('registrations_jams', {
+                'value':        False,
+                'default':      False,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Protect register form"),
+                'description':  _("Set this setting to yes if you want failed register attempts to count into limit. Majority of failed register attempts are caused by CAPTCHA protection against spam-bots, however same protection may cause problems for users with disabilities or ones that have problems understanding Q&A challenge."),
+            }),
+            ('jams_lifetime', {
+                'value':        15,
+                'default':      15,
+                'type':         "integer",
+                'input':        "text",
+                'name':         _("Automaticaly unlock jammed IPs"),
+                'description':  _('Enter number of minutes since IP address "jams" to automatically unlock it, or 0 to never unlock jammed IP adresses. Jams don\'t count as bans.'),
+            }),
+        ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
+    update_settings_fixture(settings_fixture)

+ 68 - 68
misago/fixtures/captchasettings.py

@@ -1,68 +1,68 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-   # Spam Countermeasures
-   ('captcha', {
-        'name': _("Spam Countermeasures"),
-        'description': _("Those settings allow you to combat automatic registrations and spam messages on your forum."),
-        'settings': (
-            ('bots_registration', {
-                'value':        'no',
-                'type':         "string",
-                'input':        "choice",
-                'extra':        {'choices': [('no', _("No protection")), ('recaptcha', _("reCaptcha")), ('qa', _("Question & Answer"))]},
-                'separator':    _("Spambots Registrations"),
-                'name':         _("CAPTCHA type"),
-                'description':  _('CAPTCHA stands for "Completely Automated Public Turing test to tell Computers and Humans Apart". Its type of test developed on purpose of blocking automatic registrations.'),
-            }),
-            ('recaptcha_public', {
-                'type':         "string",
-                'input':        "text",
-                'separator':    _("reCaptcha"),
-                'name':         _("Public Key"),
-                'description':  _("Enter public API key that you have received from reCaptcha."),
-            }),
-            ('recaptcha_private', {
-                'type':         "string",
-                'input':        "text",
-                'name':         _("Private Key"),
-                'description':  _("Enter private API key that you have received from reCaptcha."),
-            }),
-            ('recaptcha_ssl', {
-                'value':        False,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Use SSL in reCaptcha"),
-                'description':  _("Do you want forum to use SSL when making requests to reCaptha servers?"),
-            }),
-            ('qa_test', {
-                'type':         "string",
-                'input':        "text",
-                'separator':    _("Question and Answer Test"),
-                'name':         _("Question"),
-                'description':  _("Question visible to your users."),
-            }),
-            ('qa_test_help', {
-                'type':         "string",
-                'input':        "text",
-                'name':         _("Help Message"),
-                'description':  _("Optional help message displayed on form."),
-            }),
-            ('qa_test_answers', {
-                'type':         "string",
-                'input':        "textarea",
-                'name':         _("Answers"),
-                'description':  _("Enter allowed answers to this question, each in new line. Test is case-insensitive."),
-            }),
-        ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
-    update_settings_fixture(settings_fixture)
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+   # Spam Countermeasures
+   ('captcha', {
+        'name': _("Spam Countermeasures"),
+        'description': _("Those settings allow you to combat automatic registrations and spam messages on your forum."),
+        'settings': (
+            ('bots_registration', {
+                'value':        'no',
+                'type':         "string",
+                'input':        "choice",
+                'extra':        {'choices': [('no', _("No protection")), ('recaptcha', _("reCaptcha")), ('qa', _("Question & Answer"))]},
+                'separator':    _("Spambots Registrations"),
+                'name':         _("CAPTCHA type"),
+                'description':  _('CAPTCHA stands for "Completely Automated Public Turing test to tell Computers and Humans Apart". Its type of test developed on purpose of blocking automatic registrations.'),
+            }),
+            ('recaptcha_public', {
+                'type':         "string",
+                'input':        "text",
+                'separator':    _("reCaptcha"),
+                'name':         _("Public Key"),
+                'description':  _("Enter public API key that you have received from reCaptcha."),
+            }),
+            ('recaptcha_private', {
+                'type':         "string",
+                'input':        "text",
+                'name':         _("Private Key"),
+                'description':  _("Enter private API key that you have received from reCaptcha."),
+            }),
+            ('recaptcha_ssl', {
+                'value':        False,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Use SSL in reCaptcha"),
+                'description':  _("Do you want forum to use SSL when making requests to reCaptha servers?"),
+            }),
+            ('qa_test', {
+                'type':         "string",
+                'input':        "text",
+                'separator':    _("Question and Answer Test"),
+                'name':         _("Question"),
+                'description':  _("Question visible to your users."),
+            }),
+            ('qa_test_help', {
+                'type':         "string",
+                'input':        "text",
+                'name':         _("Help Message"),
+                'description':  _("Optional help message displayed on form."),
+            }),
+            ('qa_test_answers', {
+                'type':         "string",
+                'input':        "textarea",
+                'name':         _("Answers"),
+                'description':  _("Enter allowed answers to this question, each in new line. Test is case-insensitive."),
+            }),
+        ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
+    update_settings_fixture(settings_fixture)

+ 61 - 61
misago/fixtures/forums.py

@@ -1,62 +1,62 @@
-from django.utils import timezone
-from misago.models import Forum, Thread, Post 
-from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
-from misago.utils.strings import slugify
-
-monitor_fixture = {
-                   'threads': (1, 'int'),
-                   'posts': (1, 'int'),
-                  }
-
-def load():
-    Forum(special='private_threads', name='private', slug='private', type='forum').insert_at(None, save=True)
-    Forum(special='reports', name='reports', slug='reports', type='forum').insert_at(None, save=True)
-
-    root = Forum(special='root', name='root', slug='root')
-    root.insert_at(None, save=True)
-    cat = Forum(type='category', name='First Category', slug='first-category')
-    cat.insert_at(root, save=True)
-    forum = Forum(type='forum', name='First Forum', slug='first-forum', threads=1, posts=1)
-    forum.insert_at(cat, save=True)
-    Forum(type='redirect', name='Project Homepage', slug='project-homepage', redirect='http://misago-project.org').insert_at(cat, position='last-child', save=True)
-    Forum.objects.populate_tree(True)
-
-    now = timezone.now()
-    thread = Thread.objects.create(
-                                   forum=forum,
-                                   name='Welcome to Misago!',
-                                   slug=slugify('Welcome to Misago!'),
-                                   start=now,
-                                   last=now,
-                                   )
-    post = Post.objects.create(
-                               forum=forum,
-                               thread=thread,
-                               user_name='MisagoProject',
-                               ip='127.0.0.1',
-                               agent='',
-                               post='Welcome to Misago!',
-                               post_preparsed='Welcome to Misago!',
-                               date=now,
-                               )
-    thread.start_post = post
-    thread.start_poster_name = 'MisagoProject'
-    thread.start_poster_slug = 'misagoproject'
-    thread.last_post = post
-    thread.last_poster_name = 'MisagoProject'
-    thread.last_poster_slug = 'misagoproject'
-    thread.save(force_update=True)
-    forum.last_thread = thread
-    forum.last_thread_name = thread.name
-    forum.last_thread_slug = thread.slug
-    forum.last_thread_date = thread.last
-    forum.last_poster = thread.last_poster
-    forum.last_poster_name = thread.last_poster_name
-    forum.last_poster_slug = thread.last_poster_slug
-    forum.save(force_update=True)
-
-    load_monitor_fixture(monitor_fixture)
-
-
-def update():
+from django.utils import timezone
+from misago.models import Forum, Thread, Post 
+from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
+from misago.utils.strings import slugify
+
+monitor_fixture = {
+                   'threads': (1, 'int'),
+                   'posts': (1, 'int'),
+                  }
+
+def load():
+    Forum(special='private_threads', name='private', slug='private', type='forum').insert_at(None, save=True)
+    Forum(special='reports', name='reports', slug='reports', type='forum').insert_at(None, save=True)
+
+    root = Forum(special='root', name='root', slug='root')
+    root.insert_at(None, save=True)
+    cat = Forum(type='category', name='First Category', slug='first-category')
+    cat.insert_at(root, save=True)
+    forum = Forum(type='forum', name='First Forum', slug='first-forum', threads=1, posts=1)
+    forum.insert_at(cat, save=True)
+    Forum(type='redirect', name='Project Homepage', slug='project-homepage', redirect='http://misago-project.org').insert_at(cat, position='last-child', save=True)
+    Forum.objects.populate_tree(True)
+
+    now = timezone.now()
+    thread = Thread.objects.create(
+                                   forum=forum,
+                                   name='Welcome to Misago!',
+                                   slug=slugify('Welcome to Misago!'),
+                                   start=now,
+                                   last=now,
+                                   )
+    post = Post.objects.create(
+                               forum=forum,
+                               thread=thread,
+                               user_name='MisagoProject',
+                               ip='127.0.0.1',
+                               agent='',
+                               post='Welcome to Misago!',
+                               post_preparsed='Welcome to Misago!',
+                               date=now,
+                               )
+    thread.start_post = post
+    thread.start_poster_name = 'MisagoProject'
+    thread.start_poster_slug = 'misagoproject'
+    thread.last_post = post
+    thread.last_poster_name = 'MisagoProject'
+    thread.last_poster_slug = 'misagoproject'
+    thread.save(force_update=True)
+    forum.last_thread = thread
+    forum.last_thread_name = thread.name
+    forum.last_thread_slug = thread.slug
+    forum.last_thread_date = thread.last
+    forum.last_poster = thread.last_poster
+    forum.last_poster_name = thread.last_poster_name
+    forum.last_poster_slug = thread.last_poster_slug
+    forum.save(force_update=True)
+
+    load_monitor_fixture(monitor_fixture)
+
+
+def update():
     update_monitor_fixture(monitor_fixture)

+ 116 - 116
misago/fixtures/forumsroles.py

@@ -1,116 +1,116 @@
-from misago.models import ForumRole
-from misago.utils.translation import ugettext_lazy as _
-
-def load():
-    role = ForumRole()
-    role.name = _('Full Access').message
-    role.permissions = {
-                        'can_see_forum': True,
-                        'can_see_forum_contents': True,
-                        'can_read_threads': 2,
-                        'can_start_threads': 2,
-                        'can_edit_own_threads': True,
-                        'can_soft_delete_own_threads': True,
-                        'can_write_posts': 2,
-                        'can_edit_own_posts': True,
-                        'can_soft_delete_own_posts': True,
-                        'can_upvote_posts': True,
-                        'can_downvote_posts': True,
-                        'can_see_posts_scores': 2,
-                        'can_see_votes': True,
-                        'can_make_polls': True,
-                        'can_vote_in_polls': True,
-                        'can_see_poll_votes': True,
-                        'can_see_attachments': True,
-                        'can_upload_attachments': True,
-                        'can_download_attachments': True,
-                        'attachment_size': 5000,
-                        'attachment_limit': 15,
-                        'can_approve': True,
-                        'can_edit_labels': True,
-                        'can_see_changelog': True,
-                        'can_pin_threads': 2,
-                        'can_edit_threads_posts': True,
-                        'can_move_threads_posts': True,
-                        'can_close_threads': True,
-                        'can_protect_posts': True,
-                        'can_delete_threads': 2,
-                        'can_delete_posts': 2,
-                        'can_delete_polls': 2,
-                        'can_delete_attachments': True,
-                        'can_see_deleted_checkpoints': True,
-                        'can_delete_checkpoints': 2,
-                       }
-    role.save(force_insert=True)
-
-    role = ForumRole()
-    role.name = _('Standard Access and Upload').message
-    role.permissions = {
-                        'can_see_forum': True,
-                        'can_see_forum_contents': True,
-                        'can_read_threads': 2,
-                        'can_start_threads': 2,
-                        'can_edit_own_threads': True,
-                        'can_write_posts': 2,
-                        'can_edit_own_posts': True,
-                        'can_soft_delete_own_posts': True,
-                        'can_upvote_posts': True,
-                        'can_downvote_posts': True,
-                        'can_see_posts_scores': 2,
-                        'can_make_polls': True,
-                        'can_vote_in_polls': True,
-                        'can_upload_attachments': True,
-                        'can_download_attachments': True,
-                        'attachment_size': 100,
-                        'attachment_limit': 3,
-                       }
-    role.save(force_insert=True)
-
-    role = ForumRole()
-    role.name = _('Standard Access').message
-    role.permissions = {
-                        'can_see_forum': True,
-                        'can_see_forum_contents': True,
-                        'can_read_threads': 2,
-                        'can_start_threads': 2,
-                        'can_edit_own_threads': True,
-                        'can_write_posts': 2,
-                        'can_edit_own_posts': True,
-                        'can_soft_delete_own_posts': True,
-                        'can_upvote_posts': True,
-                        'can_downvote_posts': True,
-                        'can_see_posts_scores': 2,
-                        'can_make_polls': True,
-                        'can_vote_in_polls': True,
-                        'can_download_attachments': True,
-                       }
-    role.save(force_insert=True)
-
-    role = ForumRole()
-    role.name = _('Read and Download').message
-    role.permissions = {
-                        'can_see_forum': True,
-                        'can_see_forum_contents': True,
-                        'can_read_threads': 2,
-                        'can_download_attachments': True,
-                        'can_see_posts_scores': 2,
-                       }
-    role.save(force_insert=True)
-
-    role = ForumRole()
-    role.name = _('Threads list only').message
-    role.permissions = {
-                        'can_see_forum': True,
-                        'can_see_forum_contents': True,
-                       }
-    role.save(force_insert=True)
-
-    role = ForumRole()
-    role.name = _('Read only').message
-    role.permissions = {
-                        'can_see_forum': True,
-                        'can_see_forum_contents': True,
-                        'can_read_threads': 2,
-                        'can_see_posts_scores': 2,
-                       }
-    role.save(force_insert=True)
+from misago.models import ForumRole
+from misago.utils.translation import ugettext_lazy as _
+
+def load():
+    role = ForumRole()
+    role.name = _('Full Access').message
+    role.permissions = {
+                        'can_see_forum': True,
+                        'can_see_forum_contents': True,
+                        'can_read_threads': 2,
+                        'can_start_threads': 2,
+                        'can_edit_own_threads': True,
+                        'can_soft_delete_own_threads': True,
+                        'can_write_posts': 2,
+                        'can_edit_own_posts': True,
+                        'can_soft_delete_own_posts': True,
+                        'can_upvote_posts': True,
+                        'can_downvote_posts': True,
+                        'can_see_posts_scores': 2,
+                        'can_see_votes': True,
+                        'can_make_polls': True,
+                        'can_vote_in_polls': True,
+                        'can_see_poll_votes': True,
+                        'can_see_attachments': True,
+                        'can_upload_attachments': True,
+                        'can_download_attachments': True,
+                        'attachment_size': 5000,
+                        'attachment_limit': 15,
+                        'can_approve': True,
+                        'can_edit_labels': True,
+                        'can_see_changelog': True,
+                        'can_pin_threads': 2,
+                        'can_edit_threads_posts': True,
+                        'can_move_threads_posts': True,
+                        'can_close_threads': True,
+                        'can_protect_posts': True,
+                        'can_delete_threads': 2,
+                        'can_delete_posts': 2,
+                        'can_delete_polls': 2,
+                        'can_delete_attachments': True,
+                        'can_see_deleted_checkpoints': True,
+                        'can_delete_checkpoints': 2,
+                       }
+    role.save(force_insert=True)
+
+    role = ForumRole()
+    role.name = _('Standard Access and Upload').message
+    role.permissions = {
+                        'can_see_forum': True,
+                        'can_see_forum_contents': True,
+                        'can_read_threads': 2,
+                        'can_start_threads': 2,
+                        'can_edit_own_threads': True,
+                        'can_write_posts': 2,
+                        'can_edit_own_posts': True,
+                        'can_soft_delete_own_posts': True,
+                        'can_upvote_posts': True,
+                        'can_downvote_posts': True,
+                        'can_see_posts_scores': 2,
+                        'can_make_polls': True,
+                        'can_vote_in_polls': True,
+                        'can_upload_attachments': True,
+                        'can_download_attachments': True,
+                        'attachment_size': 100,
+                        'attachment_limit': 3,
+                       }
+    role.save(force_insert=True)
+
+    role = ForumRole()
+    role.name = _('Standard Access').message
+    role.permissions = {
+                        'can_see_forum': True,
+                        'can_see_forum_contents': True,
+                        'can_read_threads': 2,
+                        'can_start_threads': 2,
+                        'can_edit_own_threads': True,
+                        'can_write_posts': 2,
+                        'can_edit_own_posts': True,
+                        'can_soft_delete_own_posts': True,
+                        'can_upvote_posts': True,
+                        'can_downvote_posts': True,
+                        'can_see_posts_scores': 2,
+                        'can_make_polls': True,
+                        'can_vote_in_polls': True,
+                        'can_download_attachments': True,
+                       }
+    role.save(force_insert=True)
+
+    role = ForumRole()
+    role.name = _('Read and Download').message
+    role.permissions = {
+                        'can_see_forum': True,
+                        'can_see_forum_contents': True,
+                        'can_read_threads': 2,
+                        'can_download_attachments': True,
+                        'can_see_posts_scores': 2,
+                       }
+    role.save(force_insert=True)
+
+    role = ForumRole()
+    role.name = _('Threads list only').message
+    role.permissions = {
+                        'can_see_forum': True,
+                        'can_see_forum_contents': True,
+                       }
+    role.save(force_insert=True)
+
+    role = ForumRole()
+    role.name = _('Read only').message
+    role.permissions = {
+                        'can_see_forum': True,
+                        'can_see_forum_contents': True,
+                        'can_read_threads': 2,
+                        'can_see_posts_scores': 2,
+                       }
+    role.save(force_insert=True)

+ 13 - 13
misago/fixtures/onlinemonitor.py

@@ -1,14 +1,14 @@
-from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
-
-monitor_fixture = {
-                   'online_members': (0, 'int'),
-                   'online_all': (0, 'int'),
-                  }
-
-
-def load():
-    load_monitor_fixture(monitor_fixture)
-
-
-def update():
+from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
+
+monitor_fixture = {
+                   'online_members': (0, 'int'),
+                   'online_all': (0, 'int'),
+                  }
+
+
+def load():
+    load_monitor_fixture(monitor_fixture)
+
+
+def update():
     update_monitor_fixture(monitor_fixture)

+ 27 - 27
misago/fixtures/privatethreadssettings.py

@@ -1,27 +1,27 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-    # Threads Settings
-    ('private-threads', {
-         'name': _("Private Threads Settings"),
-         'description': _("Those settings control your forum's private threads."),
-         'settings': (
-            ('enable_private_threads', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'separator':    _("Private Threads"),
-                'name':         _("Enable Private Threads"),
-            }),
-       ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
-    update_settings_fixture(settings_fixture)
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+    # Threads Settings
+    ('private-threads', {
+         'name': _("Private Threads Settings"),
+         'description': _("Those settings control your forum's private threads."),
+         'settings': (
+            ('enable_private_threads', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'separator':    _("Private Threads"),
+                'name':         _("Enable Private Threads"),
+            }),
+       ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
+    update_settings_fixture(settings_fixture)

+ 83 - 83
misago/fixtures/rankingsettings.py

@@ -1,83 +1,83 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-    # Users Ranking Settings
-    ('ranking', {
-        'name': _("Members Ranking"),
-        'description': _("Those settings control mechanisms of members activity ranking which allows you to gamificate your forum."),
-        'settings': (
-            ('ranking_inflation', {
-                'value':        5,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0, 'max': 99},
-                'separator':    _("Basic Ranking Settings"),
-                'name':         _("Ranking Inflation"),
-                'description':  _("Enter size of ranking scores inflation in percent. Scores inflation is important mechanism that allows ranking self-regulation, punishing inactivity and requiring users to remain active in order to remain high in ranking."),
-            }),
-            ('ranking_positions_visible', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Don't Keep Users Ranking Positions Secret"),
-                'description':  _("Changing this to yes will cause forum to display user position in ranking on his profile page."),
-            }),
-            ('ranking_scores_visible', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _("Don't Keep Users Scores Secret"),
-                'description':  _("Changing this to yes will cause forum to display user score on his profile page."),
-            }),
-            ('score_reward_new_thread', {
-                'value':        50,
-                'type':         "integer",
-                'input':        "text",
-                'separator':    _("Posting Rewards"),
-                'name':         _("New Thread Reward"),
-                'description':  _("Score user will receive (or lose) whenever he posts new thread."),
-            }),
-            ('score_reward_new_post', {
-                'value':        100,
-                'type':         "integer",
-                'input':        "text",
-                'name':         _("New Reply Reward"),
-                'description':  _("Score user will receive (or lose) whenever he posts new reply in thread."),
-            }),
-            ('score_reward_new_post_cooldown', {
-                'value':        180,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0},
-                'name':         _("Reward Cooldown"),
-                'description':  _("Minimal time (in seconds) that has to pass between postings for new message to receive karma vote. This is useful to combat flood."),
-            }),
-            ('score_reward_karma_positive', {
-                'value':        20,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0},
-                'separator':    _("Karma System"),
-                'name':         _("Upvote Reward"),
-                'description':  _("Score user will receive every time his post receives upvote."),
-            }),
-            ('score_reward_karma_negative', {
-                'value':        10,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 0},
-                'name':         _("Downvote Punishment"),
-                'description':  _("Score user will lose every time his post receives downvote."),
-            }),
-        ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
-    update_settings_fixture(settings_fixture)
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+    # Users Ranking Settings
+    ('ranking', {
+        'name': _("Members Ranking"),
+        'description': _("Those settings control mechanisms of members activity ranking which allows you to gamificate your forum."),
+        'settings': (
+            ('ranking_inflation', {
+                'value':        5,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0, 'max': 99},
+                'separator':    _("Basic Ranking Settings"),
+                'name':         _("Ranking Inflation"),
+                'description':  _("Enter size of ranking scores inflation in percent. Scores inflation is important mechanism that allows ranking self-regulation, punishing inactivity and requiring users to remain active in order to remain high in ranking."),
+            }),
+            ('ranking_positions_visible', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Don't Keep Users Ranking Positions Secret"),
+                'description':  _("Changing this to yes will cause forum to display user position in ranking on his profile page."),
+            }),
+            ('ranking_scores_visible', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _("Don't Keep Users Scores Secret"),
+                'description':  _("Changing this to yes will cause forum to display user score on his profile page."),
+            }),
+            ('score_reward_new_thread', {
+                'value':        50,
+                'type':         "integer",
+                'input':        "text",
+                'separator':    _("Posting Rewards"),
+                'name':         _("New Thread Reward"),
+                'description':  _("Score user will receive (or lose) whenever he posts new thread."),
+            }),
+            ('score_reward_new_post', {
+                'value':        100,
+                'type':         "integer",
+                'input':        "text",
+                'name':         _("New Reply Reward"),
+                'description':  _("Score user will receive (or lose) whenever he posts new reply in thread."),
+            }),
+            ('score_reward_new_post_cooldown', {
+                'value':        180,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _("Reward Cooldown"),
+                'description':  _("Minimal time (in seconds) that has to pass between postings for new message to receive karma vote. This is useful to combat flood."),
+            }),
+            ('score_reward_karma_positive', {
+                'value':        20,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'separator':    _("Karma System"),
+                'name':         _("Upvote Reward"),
+                'description':  _("Score user will receive every time his post receives upvote."),
+            }),
+            ('score_reward_karma_negative', {
+                'value':        10,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 0},
+                'name':         _("Downvote Punishment"),
+                'description':  _("Score user will lose every time his post receives downvote."),
+            }),
+        ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
+    update_settings_fixture(settings_fixture)

+ 47 - 47
misago/fixtures/ranks.py

@@ -1,48 +1,48 @@
-from misago.models import Rank
-from misago.utils.translation import ugettext_lazy as _
-
-def load():
-    Rank.objects.create(
-                        name=_("Forum Team").message,
-                        slug='forum-team',
-                        title=_("Forum Team").message,
-                        style='team',
-                        special=True,
-                        order=0,
-                        as_tab=True,
-                        on_index=True,
-                        )
-
-    Rank.objects.create(
-                        name=_("Most Valuable Posters").message,
-                        slug='most-valuable-posters',
-                        title=_("MVP").message,
-                        style='mvp',
-                        special=True,
-                        order=1,
-                        as_tab=True,
-                        )
-
-    Rank.objects.create(
-                        name=_("Top Posters").message,
-                        slug='top-posters',
-                        title="Top",
-                        style='top',
-                        order=2,
-                        criteria="10%",
-                        as_tab=True,
-                        )
-
-    Rank.objects.create(
-                        name=_("Members").message,
-                        slug='members',
-                        order=4,
-                        criteria="75%"
-                        )
-
-    Rank.objects.create(
-                        name=_("Lurkers").message,
-                        slug='lurkers',
-                        order=5,
-                        criteria="100%"
+from misago.models import Rank
+from misago.utils.translation import ugettext_lazy as _
+
+def load():
+    Rank.objects.create(
+                        name=_("Forum Team").message,
+                        slug='forum-team',
+                        title=_("Forum Team").message,
+                        style='team',
+                        special=True,
+                        order=0,
+                        as_tab=True,
+                        on_index=True,
+                        )
+
+    Rank.objects.create(
+                        name=_("Most Valuable Posters").message,
+                        slug='most-valuable-posters',
+                        title=_("MVP").message,
+                        style='mvp',
+                        special=True,
+                        order=1,
+                        as_tab=True,
+                        )
+
+    Rank.objects.create(
+                        name=_("Top Posters").message,
+                        slug='top-posters',
+                        title="Top",
+                        style='top',
+                        order=2,
+                        criteria="10%",
+                        as_tab=True,
+                        )
+
+    Rank.objects.create(
+                        name=_("Members").message,
+                        slug='members',
+                        order=4,
+                        criteria="75%"
+                        )
+
+    Rank.objects.create(
+                        name=_("Lurkers").message,
+                        slug='lurkers',
+                        order=5,
+                        criteria="100%"
                         )

+ 12 - 12
misago/fixtures/reportsmonitor.py

@@ -1,13 +1,13 @@
-from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
-
-monitor_fixture = {
-                   'reported_posts': (0, 'int'),
-                  }
-
-
-def load():
-    load_monitor_fixture(monitor_fixture)
-
-
-def update():
+from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
+
+monitor_fixture = {
+                   'reported_posts': (0, 'int'),
+                  }
+
+
+def load():
+    load_monitor_fixture(monitor_fixture)
+
+
+def update():
     update_monitor_fixture(monitor_fixture)

+ 66 - 66
misago/fixtures/signingsettings.py

@@ -1,67 +1,67 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-    # Register and Sign-In Settings
-    ('signin', {
-        'name': _("Sign-In and Sessions Settings"),
-        'description': _("Those settings control behaviour of signed-in accounts."),
-        'settings': (
-            ('sessions_validate_ip', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'separator':    _("Sessions Settings"),
-                'name':         _("Check IP on session authorization"),
-                'description':  _("Makes sessions more secure, but can cause problems with proxies and VPN's."),
-            }),
-            ('remember_me_allow', {
-                'value':        True,
-                'type':         "boolean",
-                'input':        "yesno",
-                'separator':    _('"Remember Me" Feature'),
-                'name':         _('Enable "Remember Me" feature'),
-                'description':  _("Turning this option on allows users to sign in on to your board using cookie-based tokens. This may result in account compromisation when user fails to sign out on shared computer or his cookie is stolen."),
-            }),
-            ('remember_me_lifetime', {
-                'value':        90,
-                'type':         "integer",
-                'input':        "text",
-                'name':         _('"Remember Me" token lifetime'),
-                'description':  _('Number of days since either last use or creation of "Remember Me" token to its expiration.'),
-            }),
-            ('remember_me_extensible', {
-                'value':        1,
-                'type':         "boolean",
-                'input':        "yesno",
-                'name':         _('Allow "Remember Me" tokens refreshing'),
-                'description':  _('Set this setting to off if you want to force your users to periodically update their "Remember Me" tokens by signing in. If this option is on, Tokens are updated when they are used to open new session.'),
-            }),
-            ('online_counting', {
-                'value':        "real",
-                'type':         "string",
-                'input':        "choice",
-                'extra':        {'choices': [('no', _("Don't count users online")), ('snap', _("Periodically count and cache onlines")), ('real', _("Real time"))]},
-                'separator':    _("Online Counting"),
-                'name':         _("Count and display number of users online on board index."),
-                'description':  _("Online counter helps members tell how active other members are at the moment. Large forums should use periodical counting that saves resources but is not accurate while small ones can use real time counting that offers complete accuracy without putting much stress on sessions table."),
-            }),
-            ('online_counting_frequency', {
-                'value':        300,
-                'type':         "integer",
-                'input':        "text",
-                'extra':        {'min': 1},
-                'name':         _("Cache expiration"),
-                'description':  _('If you are using cache to count number of users online, here you can enter number of seconds after which cache is marked as expired and refreshed with new data.'),
-            }),
-        ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+    # Register and Sign-In Settings
+    ('signin', {
+        'name': _("Sign-In and Sessions Settings"),
+        'description': _("Those settings control behaviour of signed-in accounts."),
+        'settings': (
+            ('sessions_validate_ip', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'separator':    _("Sessions Settings"),
+                'name':         _("Check IP on session authorization"),
+                'description':  _("Makes sessions more secure, but can cause problems with proxies and VPN's."),
+            }),
+            ('remember_me_allow', {
+                'value':        True,
+                'type':         "boolean",
+                'input':        "yesno",
+                'separator':    _('"Remember Me" Feature'),
+                'name':         _('Enable "Remember Me" feature'),
+                'description':  _("Turning this option on allows users to sign in on to your board using cookie-based tokens. This may result in account compromisation when user fails to sign out on shared computer or his cookie is stolen."),
+            }),
+            ('remember_me_lifetime', {
+                'value':        90,
+                'type':         "integer",
+                'input':        "text",
+                'name':         _('"Remember Me" token lifetime'),
+                'description':  _('Number of days since either last use or creation of "Remember Me" token to its expiration.'),
+            }),
+            ('remember_me_extensible', {
+                'value':        1,
+                'type':         "boolean",
+                'input':        "yesno",
+                'name':         _('Allow "Remember Me" tokens refreshing'),
+                'description':  _('Set this setting to off if you want to force your users to periodically update their "Remember Me" tokens by signing in. If this option is on, Tokens are updated when they are used to open new session.'),
+            }),
+            ('online_counting', {
+                'value':        "real",
+                'type':         "string",
+                'input':        "choice",
+                'extra':        {'choices': [('no', _("Don't count users online")), ('snap', _("Periodically count and cache onlines")), ('real', _("Real time"))]},
+                'separator':    _("Online Counting"),
+                'name':         _("Count and display number of users online on board index."),
+                'description':  _("Online counter helps members tell how active other members are at the moment. Large forums should use periodical counting that saves resources but is not accurate while small ones can use real time counting that offers complete accuracy without putting much stress on sessions table."),
+            }),
+            ('online_counting_frequency', {
+                'value':        300,
+                'type':         "integer",
+                'input':        "text",
+                'extra':        {'min': 1},
+                'name':         _("Cache expiration"),
+                'description':  _('If you are using cache to count number of users online, here you can enter number of seconds after which cache is marked as expired and refreshed with new data.'),
+            }),
+        ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
     update_settings_fixture(settings_fixture)

+ 42 - 42
misago/fixtures/tossettings.py

@@ -1,42 +1,42 @@
-from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
-from misago.utils.translation import ugettext_lazy as _
-
-settings_fixture = (
-    # Avatars Settings
-    ('tos', {
-         'name': _("Forum Terms of Service"),
-         'description': _("Those settings allow you to set up forum terms of service."),
-         'settings': (
-            ('tos_title', {
-                'value':        "Terms of Service",
-                'type':         "string",
-                'input':        "text",
-                'separator':    _("Terms of Service Options"),
-                'name':         _("Page Title"),
-                'description':  _("Title of page community ToS are displayed on."),
-            }),
-            ('tos_url', {
-                'value':        "",
-                'type':         "string",
-                'input':        "text",
-                'name':         _("Link to remote page with ToS"),
-                'description':  _("If your forum's ToS are located on remote page, enter here its address."),
-            }),
-            ('tos_content', {
-                'value':        "",
-                'type':         "string",
-                'input':        "textarea",
-                'name':         _("OR enter ToS content"),
-                'description':  _("Instead of linking to remote page, forum can create dedicated ToS page for you. To display ToS page, enter here your forum Terms of Service."),
-            }),
-       ),
-    }),
-)
-
-
-def load():
-    load_settings_fixture(settings_fixture)
-
-
-def update():
-    update_settings_fixture(settings_fixture)
+from misago.utils.fixtures import load_settings_fixture, update_settings_fixture
+from misago.utils.translation import ugettext_lazy as _
+
+settings_fixture = (
+    # Avatars Settings
+    ('tos', {
+         'name': _("Forum Terms of Service"),
+         'description': _("Those settings allow you to set up forum terms of service."),
+         'settings': (
+            ('tos_title', {
+                'value':        "Terms of Service",
+                'type':         "string",
+                'input':        "text",
+                'separator':    _("Terms of Service Options"),
+                'name':         _("Page Title"),
+                'description':  _("Title of page community ToS are displayed on."),
+            }),
+            ('tos_url', {
+                'value':        "",
+                'type':         "string",
+                'input':        "text",
+                'name':         _("Link to remote page with ToS"),
+                'description':  _("If your forum's ToS are located on remote page, enter here its address."),
+            }),
+            ('tos_content', {
+                'value':        "",
+                'type':         "string",
+                'input':        "textarea",
+                'name':         _("OR enter ToS content"),
+                'description':  _("Instead of linking to remote page, forum can create dedicated ToS page for you. To display ToS page, enter here your forum Terms of Service."),
+            }),
+       ),
+    }),
+)
+
+
+def load():
+    load_settings_fixture(settings_fixture)
+
+
+def update():
+    update_settings_fixture(settings_fixture)

+ 88 - 88
misago/fixtures/userroles.py

@@ -1,89 +1,89 @@
-from misago.models import Role
-from misago.utils.translation import ugettext_lazy as _
-
-def load():
-    role = Role(name=_("Administrator").message, _special='admin', protected=True)
-    role.permissions = {
-                        'name_changes_allowed': 5,
-                        'changes_expire': 7,
-                        'can_search_forums': True,
-                        'search_cooldown': 0,
-                        'can_use_acp': True,
-                        'can_use_mcp': True,
-                        'can_use_signature': True,
-                        'allow_signature_links': True,
-                        'allow_signature_images': True,
-                        'can_search_users': True,
-                        'can_see_users_emails': True,
-                        'can_see_users_trails': True,
-                        'can_see_hidden_users': True,
-                        'can_use_private_threads': True,
-                        'can_start_private_threads': True,
-                        'can_upload_attachments_in_private_threads': True,
-                        'private_thread_attachment_size': 0,
-                        'private_thread_attachments_limit': 0,
-                        'can_invite_ignoring': True,
-                        'private_threads_mod': True,
-                        'can_delete_checkpoints': 2,
-                        'can_report_content': True,
-                        'can_handle_reports': True,
-                        'can_mod_reports_discussions': True,
-                        'can_delete_reports': True,
-                        'forums': {3: 1, 5: 1, 6: 1},
-                       }
-    role.save(force_insert=True)
-    
-    role = Role(name=_("Moderator").message, _special='mod', protected=True)
-    role.permissions = {
-                        'name_changes_allowed': 3,
-                        'changes_expire': 14,
-                        'can_search_forums': True,
-                        'search_cooldown': 0,
-                        'can_use_mcp': True,
-                        'can_use_signature': True,
-                        'allow_signature_links': True,
-                        'can_search_users': True,
-                        'can_see_users_emails': True,
-                        'can_see_users_trails': True,
-                        'can_see_hidden_users': True,
-                        'can_use_private_threads': True,
-                        'can_start_private_threads': True,
-                        'can_upload_attachments_in_private_threads': True,
-                        'private_thread_attachment_size': 0,
-                        'private_thread_attachments_limit': 0,
-                        'can_invite_ignoring': True,
-                        'private_threads_mod': True,
-                        'can_delete_checkpoints': 1,
-                        'can_report_content': True,
-                        'can_handle_reports': True,
-                        'forums': {3: 1, 5: 1, 6: 1},
-                       }
-    role.save(force_insert=True)
-    
-    role = Role(name=_("Registered").message, _special='registered')
-    role.permissions = {
-                        'name_changes_allowed': 2,
-                        'can_search_forums': True,
-                        'search_cooldown': 20,
-                        'can_use_signature': False,
-                        'can_search_users': True,
-                        'can_use_private_threads': True,
-                        'can_start_private_threads': True,
-                        'can_upload_attachments_in_private_threads': False,
-                        'private_thread_attachment_size': 100,
-                        'private_thread_attachments_limit': 30,
-                        'can_invite_ignoring': False,
-                        'private_threads_mod': False,
-                        'can_report_content': True,
-                        'forums': {4: 3, 5: 3, 6: 3},
-                       }
-    role.save(force_insert=True)
-    
-    role = Role(name=_("Guest").message, _special='guest')
-    role.permissions = {
-                        'can_search_forums': True,
-                        'search_cooldown': 45,
-                        'can_search_users': True,
-                        'forums': {4: 6, 5: 6, 6: 6},
-                       }
+from misago.models import Role
+from misago.utils.translation import ugettext_lazy as _
+
+def load():
+    role = Role(name=_("Administrator").message, _special='admin', protected=True)
+    role.permissions = {
+                        'name_changes_allowed': 5,
+                        'changes_expire': 7,
+                        'can_search_forums': True,
+                        'search_cooldown': 0,
+                        'can_use_acp': True,
+                        'can_use_mcp': True,
+                        'can_use_signature': True,
+                        'allow_signature_links': True,
+                        'allow_signature_images': True,
+                        'can_search_users': True,
+                        'can_see_users_emails': True,
+                        'can_see_users_trails': True,
+                        'can_see_hidden_users': True,
+                        'can_use_private_threads': True,
+                        'can_start_private_threads': True,
+                        'can_upload_attachments_in_private_threads': True,
+                        'private_thread_attachment_size': 0,
+                        'private_thread_attachments_limit': 0,
+                        'can_invite_ignoring': True,
+                        'private_threads_mod': True,
+                        'can_delete_checkpoints': 2,
+                        'can_report_content': True,
+                        'can_handle_reports': True,
+                        'can_mod_reports_discussions': True,
+                        'can_delete_reports': True,
+                        'forums': {3: 1, 5: 1, 6: 1},
+                       }
+    role.save(force_insert=True)
+    
+    role = Role(name=_("Moderator").message, _special='mod', protected=True)
+    role.permissions = {
+                        'name_changes_allowed': 3,
+                        'changes_expire': 14,
+                        'can_search_forums': True,
+                        'search_cooldown': 0,
+                        'can_use_mcp': True,
+                        'can_use_signature': True,
+                        'allow_signature_links': True,
+                        'can_search_users': True,
+                        'can_see_users_emails': True,
+                        'can_see_users_trails': True,
+                        'can_see_hidden_users': True,
+                        'can_use_private_threads': True,
+                        'can_start_private_threads': True,
+                        'can_upload_attachments_in_private_threads': True,
+                        'private_thread_attachment_size': 0,
+                        'private_thread_attachments_limit': 0,
+                        'can_invite_ignoring': True,
+                        'private_threads_mod': True,
+                        'can_delete_checkpoints': 1,
+                        'can_report_content': True,
+                        'can_handle_reports': True,
+                        'forums': {3: 1, 5: 1, 6: 1},
+                       }
+    role.save(force_insert=True)
+    
+    role = Role(name=_("Registered").message, _special='registered')
+    role.permissions = {
+                        'name_changes_allowed': 2,
+                        'can_search_forums': True,
+                        'search_cooldown': 20,
+                        'can_use_signature': False,
+                        'can_search_users': True,
+                        'can_use_private_threads': True,
+                        'can_start_private_threads': True,
+                        'can_upload_attachments_in_private_threads': False,
+                        'private_thread_attachment_size': 100,
+                        'private_thread_attachments_limit': 30,
+                        'can_invite_ignoring': False,
+                        'private_threads_mod': False,
+                        'can_report_content': True,
+                        'forums': {4: 3, 5: 3, 6: 3},
+                       }
+    role.save(force_insert=True)
+    
+    role = Role(name=_("Guest").message, _special='guest')
+    role.permissions = {
+                        'can_search_forums': True,
+                        'search_cooldown': 45,
+                        'can_search_users': True,
+                        'forums': {4: 6, 5: 6, 6: 6},
+                       }
     role.save(force_insert=True)

+ 16 - 16
misago/fixtures/usersmonitor.py

@@ -1,17 +1,17 @@
-from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
-
-monitor_fixture = {
-                   'users': (0, 'int'),
-                   'users_inactive': (0, 'int'),
-                   'last_user': ('', 'string'),
-                   'last_user_name': ('', 'string'),
-                   'last_user_slug': ('', 'string'),
-                  }
-
-
-def load():
-    load_monitor_fixture(monitor_fixture)
-
-
-def update():
+from misago.utils.fixtures import load_monitor_fixture, update_monitor_fixture
+
+monitor_fixture = {
+                   'users': (0, 'int'),
+                   'users_inactive': (0, 'int'),
+                   'last_user': ('', 'string'),
+                   'last_user_name': ('', 'string'),
+                   'last_user_slug': ('', 'string'),
+                  }
+
+
+def load():
+    load_monitor_fixture(monitor_fixture)
+
+
+def update():
     update_monitor_fixture(monitor_fixture)

+ 37 - 37
misago/management/commands/about.py

@@ -1,37 +1,37 @@
-from django.core.management.base import BaseCommand, CommandError
-from django.utils import timezone
-from misago import __version__
-
-class Command(BaseCommand):
-    """
-    Displays version number and license
-    """
-    help = 'Displays Misago Credits'
-    def handle(self, *args, **options):
-        self.stdout.write('\n')
-        self.stdout.write('                                    _\n')
-        self.stdout.write('                         ____ ___  (_)________  ____  ____ \n')
-        self.stdout.write('                        / __ `__ \/ / ___/ __ `/ __ `/ __ \ \n')
-        self.stdout.write('                       / / / / / / (__  ) /_/ / /_/ / /_/ / \n')
-        self.stdout.write('                      /_/ /_/ /_/_/____/\__,_/\__, /\____/ \n')
-        self.stdout.write('                                             /____/\n')
-        self.stdout.write('\n')
-        self.stdout.write('                    Your community is powered by Misago v.%s' % __version__)
-        self.stdout.write('\n              For help and feedback visit http://misago-project.org')
-        self.stdout.write('\n\n')
-        self.stdout.write('================================================================================')
-        self.stdout.write('\n\n')
-        self.stdout.write('Copyright (C) %s, Rafal Piton' % timezone.now().year)
-        self.stdout.write('\n')
-        self.stdout.write('\nThis program is free software; you can redistribute it and/or modify it under')
-        self.stdout.write('\nthe terms of the GNU General Public License version 3 as published by')
-        self.stdout.write('\nthe Free Software Foundation')
-        self.stdout.write('\n')
-        self.stdout.write('\nThis program is distributed in the hope that it will be useful, but')
-        self.stdout.write('\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY')
-        self.stdout.write('\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License')
-        self.stdout.write('\nfor more details.')
-        self.stdout.write('\n')
-        self.stdout.write('\nYou should have received a copy of the GNU General Public License along')
-        self.stdout.write('\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.')
-        self.stdout.write('\n\n')
+from django.core.management.base import BaseCommand, CommandError
+from django.utils import timezone
+from misago import __version__
+
+class Command(BaseCommand):
+    """
+    Displays version number and license
+    """
+    help = 'Displays Misago Credits'
+    def handle(self, *args, **options):
+        self.stdout.write('\n')
+        self.stdout.write('                                    _\n')
+        self.stdout.write('                         ____ ___  (_)________  ____  ____ \n')
+        self.stdout.write('                        / __ `__ \/ / ___/ __ `/ __ `/ __ \ \n')
+        self.stdout.write('                       / / / / / / (__  ) /_/ / /_/ / /_/ / \n')
+        self.stdout.write('                      /_/ /_/ /_/_/____/\__,_/\__, /\____/ \n')
+        self.stdout.write('                                             /____/\n')
+        self.stdout.write('\n')
+        self.stdout.write('                    Your community is powered by Misago v.%s' % __version__)
+        self.stdout.write('\n              For help and feedback visit http://misago-project.org')
+        self.stdout.write('\n\n')
+        self.stdout.write('================================================================================')
+        self.stdout.write('\n\n')
+        self.stdout.write('Copyright (C) %s, Rafal Piton' % timezone.now().year)
+        self.stdout.write('\n')
+        self.stdout.write('\nThis program is free software; you can redistribute it and/or modify it under')
+        self.stdout.write('\nthe terms of the GNU General Public License version 3 as published by')
+        self.stdout.write('\nthe Free Software Foundation')
+        self.stdout.write('\n')
+        self.stdout.write('\nThis program is distributed in the hope that it will be useful, but')
+        self.stdout.write('\nWITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY')
+        self.stdout.write('\nor FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License')
+        self.stdout.write('\nfor more details.')
+        self.stdout.write('\n')
+        self.stdout.write('\nYou should have received a copy of the GNU General Public License along')
+        self.stdout.write('\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.')
+        self.stdout.write('\n\n')

+ 38 - 38
misago/management/commands/adduser.py

@@ -1,38 +1,38 @@
-from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
-from django.core.management.base import BaseCommand, CommandError
-from django.utils import timezone
-from optparse import make_option
-from misago.models import Role, User
-
-class Command(BaseCommand):
-    args = 'username email password'
-    help = 'Creates new user account'
-    option_list = BaseCommand.option_list + (
-        make_option('--admin',
-            action='store_true',
-            dest='admin',
-            default=False,
-            help='Make a new user an administrator'),
-        )
-
-    def handle(self, *args, **options):
-        if len(args) < 3:
-            raise CommandError('adduser requires exactly three arguments: user name, e-mail address and password')
-
-        # Set user
-        try:
-            new_user = User.objects.create_user(args[0], args[1], args[2])
-        except ValidationError as e:
-            raise CommandError("New user cannot be created because of following errors:\n\n%s" % '\n'.join(e.messages))
-
-        # Set admin role
-        if options['admin']:
-            new_user.roles.add(Role.objects.get(_special='admin'))
-            new_user.make_acl_key(True)
-            new_user.save(force_update=True)
-
-        if options['admin']:
-            self.stdout.write('Successfully created new administrator "%s"' % args[0])
-        else:
-            self.stdout.write('Successfully created new user "%s"' % args[0])
-        self.stdout.write('\n\nNew user should use "%s" e-mail address and "%s" password to sign in.\n' % (args[1], args[2]))
+from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
+from django.core.management.base import BaseCommand, CommandError
+from django.utils import timezone
+from optparse import make_option
+from misago.models import Role, User
+
+class Command(BaseCommand):
+    args = 'username email password'
+    help = 'Creates new user account'
+    option_list = BaseCommand.option_list + (
+        make_option('--admin',
+            action='store_true',
+            dest='admin',
+            default=False,
+            help='Make a new user an administrator'),
+        )
+
+    def handle(self, *args, **options):
+        if len(args) < 3:
+            raise CommandError('adduser requires exactly three arguments: user name, e-mail address and password')
+
+        # Set user
+        try:
+            new_user = User.objects.create_user(args[0], args[1], args[2])
+        except ValidationError as e:
+            raise CommandError("New user cannot be created because of following errors:\n\n%s" % '\n'.join(e.messages))
+
+        # Set admin role
+        if options['admin']:
+            new_user.roles.add(Role.objects.get(_special='admin'))
+            new_user.make_acl_key(True)
+            new_user.save(force_update=True)
+
+        if options['admin']:
+            self.stdout.write('Successfully created new administrator "%s"' % args[0])
+        else:
+            self.stdout.write('Successfully created new user "%s"' % args[0])
+        self.stdout.write('\n\nNew user should use "%s" e-mail address and "%s" password to sign in.\n' % (args[1], args[2]))

+ 13 - 13
misago/management/commands/clearalerts.py

@@ -1,13 +1,13 @@
-from datetime import timedelta
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-from misago.models import Alert
-
-class Command(BaseCommand):
-    """
-    This command is intended to work as CRON job fired every few days to delete old alerts
-    """
-    help = 'Clears old alerts'
-    def handle(self, *args, **options):
-        Alert.objects.filter(date__lte=timezone.now() - timedelta(days=14)).delete()
-        self.stdout.write('Old Alerts have been cleared.\n')
+from datetime import timedelta
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from misago.models import Alert
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few days to delete old alerts
+    """
+    help = 'Clears old alerts'
+    def handle(self, *args, **options):
+        Alert.objects.filter(date__lte=timezone.now() - timedelta(days=14)).delete()
+        self.stdout.write('Old Alerts have been cleared.\n')

+ 13 - 13
misago/management/commands/clearattempts.py

@@ -1,13 +1,13 @@
-from datetime import timedelta
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-from misago.models import SignInAttempt
-
-class Command(BaseCommand):
-    """
-    This command is intended to work as CRON job fired every few days to remove failed sign-in attempts
-    """
-    help = 'Clears sign-in attempts log'
-    def handle(self, *args, **options):
-        SignInAttempt.objects.filter(date__lte=timezone.now() - timedelta(hours=24)).delete()
-        self.stdout.write('Failed Sign-In attempts older than 24h have been removed.\n')
+from datetime import timedelta
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from misago.models import SignInAttempt
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few days to remove failed sign-in attempts
+    """
+    help = 'Clears sign-in attempts log'
+    def handle(self, *args, **options):
+        SignInAttempt.objects.filter(date__lte=timezone.now() - timedelta(hours=24)).delete()
+        self.stdout.write('Failed Sign-In attempts older than 24h have been removed.\n')

+ 9 - 9
misago/management/commands/clearmonitor.py

@@ -1,9 +1,9 @@
-from django.core.management.base import BaseCommand
-from misago.models import MonitorItem
-
-class Command(BaseCommand):
-    help = 'Clears forum monitor'
-
-    def handle(self, *args, **options):
-        MonitorItem.objects.filter(_value__isnull=True).delete()
-        self.stdout.write('\nForum monitor has been cleared.\n')
+from django.core.management.base import BaseCommand
+from misago.models import MonitorItem
+
+class Command(BaseCommand):
+    help = 'Clears forum monitor'
+
+    def handle(self, *args, **options):
+        MonitorItem.objects.filter(_value__isnull=True).delete()
+        self.stdout.write('\nForum monitor has been cleared.\n')

+ 14 - 14
misago/management/commands/clearsessions.py

@@ -1,14 +1,14 @@
-from datetime import timedelta
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-from misago.models import Session
-
-class Command(BaseCommand):
-    """
-    This command is intended to work as CRON job fired every few hours to keep sessions table reasonable 
-    """
-    help = 'Clears users sessions'
-    def handle(self, *args, **options):
-        Session.objects.filter(last__lte=timezone.now() - timedelta(seconds=settings.SESSION_LIFETIME)).delete()
-        self.stdout.write('\nSessions have been cleared.\n')
+from datetime import timedelta
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from misago.models import Session
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few hours to keep sessions table reasonable 
+    """
+    help = 'Clears users sessions'
+    def handle(self, *args, **options):
+        Session.objects.filter(last__lte=timezone.now() - timedelta(seconds=settings.SESSION_LIFETIME)).delete()
+        self.stdout.write('\nSessions have been cleared.\n')

+ 13 - 13
misago/management/commands/cleartokens.py

@@ -1,13 +1,13 @@
-from datetime import timedelta
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-from misago.models import Token
-
-class Command(BaseCommand):
-    """
-    This command is intended to work as CRON job fired every few days to remove unused tokens 
-    """
-    help = 'Clears "Remember Me" tokens'
-    def handle(self, *args, **options):
-        Token.objects.filter(accessed__lte=timezone.now() - timedelta(days=5)).delete()
-        self.stdout.write('\nSessions tokens have been cleared.\n')
+from datetime import timedelta
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from misago.models import Token
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few days to remove unused tokens 
+    """
+    help = 'Clears "Remember Me" tokens'
+    def handle(self, *args, **options):
+        Token.objects.filter(accessed__lte=timezone.now() - timedelta(days=5)).delete()
+        self.stdout.write('\nSessions tokens have been cleared.\n')

+ 14 - 14
misago/management/commands/cleartracker.py

@@ -1,15 +1,15 @@
-from datetime import timedelta
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-from misago.models import ForumRead, ThreadRead
-
-class Command(BaseCommand):
-    """
-    This command is intended to work as CRON job fired every few days to remove old reads tracker entries
-    """
-    help = 'Clears Reads Tracker memory'
-    def handle(self, *args, **options):
-        ForumRead.objects.filter(updated__lte=timezone.now() - timedelta(days=settings.READS_TRACKER_LENGTH)).delete()
-        ThreadRead.objects.filter(updated__lte=timezone.now() - timedelta(days=settings.READS_TRACKER_LENGTH)).delete()
+from datetime import timedelta
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from misago.models import ForumRead, ThreadRead
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few days to remove old reads tracker entries
+    """
+    help = 'Clears Reads Tracker memory'
+    def handle(self, *args, **options):
+        ForumRead.objects.filter(updated__lte=timezone.now() - timedelta(days=settings.READS_TRACKER_LENGTH)).delete()
+        ThreadRead.objects.filter(updated__lte=timezone.now() - timedelta(days=settings.READS_TRACKER_LENGTH)).delete()
         self.stdout.write('Reads tracker has been cleared.\n')        

+ 9 - 9
misago/management/commands/forcepdssync.py

@@ -1,9 +1,9 @@
-from django.core.management.base import BaseCommand
-from misago.models import User
-
-class Command(BaseCommand):
-    help = 'Updates unread Private Threads counters update for all users'
-
-    def handle(self, *args, **options):
-        User.objects.update(sync_pds=True)
-        self.stdout.write('\nUsers accounts were set to sync unread private threads stat on next visit.\n')
+from django.core.management.base import BaseCommand
+from misago.models import User
+
+class Command(BaseCommand):
+    help = 'Updates unread Private Threads counters update for all users'
+
+    def handle(self, *args, **options):
+        User.objects.update(sync_pds=True)
+        self.stdout.write('\nUsers accounts were set to sync unread private threads stat on next visit.\n')

+ 67 - 67
misago/management/commands/genavatars.py

@@ -1,67 +1,67 @@
-from django.core.management.base import BaseCommand, CommandError
-from django.conf import settings
-from path import path
-try:
-    from PIL import Image
-    has_pil = True
-except ImportError:
-    has_pil = False
-from misago.models import User
-from misago.utils.avatars import resizeimage
-
-class Command(BaseCommand):
-    help = 'Regenerates avatar images for new dimensions'
-    def handle(self, *args, **options):
-        if not has_pil:
-            raise CommandError('genavatars requires Python Imaging Library to be installed in order to run')
-        self.scale_user_avatars()
-        self.scale_gallery_avatars()
-        self.stdout.write('\n\nAvatar images have been regenerated.\n')
-
-    def scale_image(self, image_src, image_dir=None):
-        image_name = path.basename(path(image_src))
-        if not image_dir:
-            image_dir = path.dirname(path(image_src)) + '/%s_'
-        for size in settings.AVATAR_SIZES[1:]:
-            resizeimage(image_src, size, image_dir % size + image_name)
-
-    def scale_user_avatars(self):
-        for user in User.objects.filter(avatar_type='upload').iterator():
-            for image in path(settings.MEDIA_ROOT).joinpath('avatars').files('*_%s' % user.avatar_image):
-                if not image.isdir():
-                    image.remove()
-            self.scale_image(settings.MEDIA_ROOT + 'avatars/' + user.avatar_image)
-
-    def scale_gallery_avatars(self):
-        try:
-            thumb_dir = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_thumbs')
-            items = [thumb_dir]
-            for item in thumb_dir.walk():
-                items.append(item)
-            for item in reversed(items):
-                if item.isdir():
-                    item.rmdir()
-                else:
-                    item.remove()
-        except Exception:
-            pass
-        avatars_dir = path(settings.STATICFILES_DIRS[0]).joinpath('avatars')
-        avatars_len = len(avatars_dir)
-        avatars_list = []
-        for directory in avatars_dir.dirs():
-            avatars_list += directory.files('*.gif')
-            avatars_list += directory.files('*.jpg')
-            avatars_list += directory.files('*.jpeg')
-            avatars_list += directory.files('*.png')
-        thumb_dir = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_thumbs')
-        thumb_dir.mkdir(777)
-        for size in settings.AVATAR_SIZES[1:]:
-            thumb_dir.joinpath(str(size)).mkdir(777)
-        for directory in avatars_dir.dirs():
-            dirname = path(directory[avatars_len:]).basename()
-            if dirname != '_thumbs':
-                for size in settings.AVATAR_SIZES[1:]:
-                    thumb_dir.joinpath(str(size)).joinpath(dirname).mkdir(777)
-        for avatar in avatars_list:
-            self.scale_image(avatar,
-                             thumb_dir + '/%s' + avatar.dirname()[avatars_len:] + '/')
+from django.core.management.base import BaseCommand, CommandError
+from django.conf import settings
+from path import path
+try:
+    from PIL import Image
+    has_pil = True
+except ImportError:
+    has_pil = False
+from misago.models import User
+from misago.utils.avatars import resizeimage
+
+class Command(BaseCommand):
+    help = 'Regenerates avatar images for new dimensions'
+    def handle(self, *args, **options):
+        if not has_pil:
+            raise CommandError('genavatars requires Python Imaging Library to be installed in order to run')
+        self.scale_user_avatars()
+        self.scale_gallery_avatars()
+        self.stdout.write('\n\nAvatar images have been regenerated.\n')
+
+    def scale_image(self, image_src, image_dir=None):
+        image_name = path.basename(path(image_src))
+        if not image_dir:
+            image_dir = path.dirname(path(image_src)) + '/%s_'
+        for size in settings.AVATAR_SIZES[1:]:
+            resizeimage(image_src, size, image_dir % size + image_name)
+
+    def scale_user_avatars(self):
+        for user in User.objects.filter(avatar_type='upload').iterator():
+            for image in path(settings.MEDIA_ROOT).joinpath('avatars').files('*_%s' % user.avatar_image):
+                if not image.isdir():
+                    image.remove()
+            self.scale_image(settings.MEDIA_ROOT + 'avatars/' + user.avatar_image)
+
+    def scale_gallery_avatars(self):
+        try:
+            thumb_dir = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_thumbs')
+            items = [thumb_dir]
+            for item in thumb_dir.walk():
+                items.append(item)
+            for item in reversed(items):
+                if item.isdir():
+                    item.rmdir()
+                else:
+                    item.remove()
+        except Exception:
+            pass
+        avatars_dir = path(settings.STATICFILES_DIRS[0]).joinpath('avatars')
+        avatars_len = len(avatars_dir)
+        avatars_list = []
+        for directory in avatars_dir.dirs():
+            avatars_list += directory.files('*.gif')
+            avatars_list += directory.files('*.jpg')
+            avatars_list += directory.files('*.jpeg')
+            avatars_list += directory.files('*.png')
+        thumb_dir = path(settings.STATICFILES_DIRS[0]).joinpath('avatars').joinpath('_thumbs')
+        thumb_dir.mkdir(777)
+        for size in settings.AVATAR_SIZES[1:]:
+            thumb_dir.joinpath(str(size)).mkdir(777)
+        for directory in avatars_dir.dirs():
+            dirname = path(directory[avatars_len:]).basename()
+            if dirname != '_thumbs':
+                for size in settings.AVATAR_SIZES[1:]:
+                    thumb_dir.joinpath(str(size)).joinpath(dirname).mkdir(777)
+        for avatar in avatars_list:
+            self.scale_image(avatar,
+                             thumb_dir + '/%s' + avatar.dirname()[avatars_len:] + '/')

+ 22 - 22
misago/management/commands/reparseposts.py

@@ -1,22 +1,22 @@
-from django.core.management.base import BaseCommand
-from misago.markdown import post_markdown
-from misago.models import Post
-
-class Command(BaseCommand):
-    help = 'Reparse markdown for all forum posts'
-
-    def handle(self, *args, **options):
-        count = 0
-        total = Post.objects.count()
-        last = 0
-
-        self.stdout.write('\nReparsing posts...')
-        for post in Post.objects.iterator():
-            md, post.post_preparsed = post_markdown(post.post)
-            post.save(force_update=True)
-            count += 1
-            progress = (count * 100 / total)
-            if not progress % 10 and progress > last and progress < 100:
-                self.stdout.write('Reparsed %s out of %s posts...' % (count, total))
-                last = progress
-        self.stdout.write('\n%s posts have been reparsed.\n' % count)
+from django.core.management.base import BaseCommand
+from misago.markdown import post_markdown
+from misago.models import Post
+
+class Command(BaseCommand):
+    help = 'Reparse markdown for all forum posts'
+
+    def handle(self, *args, **options):
+        count = 0
+        total = Post.objects.count()
+        last = 0
+
+        self.stdout.write('\nReparsing posts...')
+        for post in Post.objects.iterator():
+            md, post.post_preparsed = post_markdown(post.post)
+            post.save(force_update=True)
+            count += 1
+            progress = (count * 100 / total)
+            if not progress % 10 and progress > last and progress < 100:
+                self.stdout.write('Reparsed %s out of %s posts...' % (count, total))
+                last = progress
+        self.stdout.write('\n%s posts have been reparsed.\n' % count)

+ 31 - 31
misago/management/commands/startmisago.py

@@ -1,32 +1,32 @@
-from optparse import make_option
-from django.core.management import call_command
-from django.core.management.base import BaseCommand, CommandError
-
-class Command(BaseCommand):
-    """
-    Builds Misago database from scratch
-    """
-    help = 'Install Misago to database'
-    option_list = BaseCommand.option_list + (
-        make_option('--quiet',
-            action='store_true',
-            dest='quiet',
-            default=False,
-            help='Dont display output from this message'),
-        )
-    
-    def handle(self, *args, **options):
-        if not options['quiet']:
-            self.stdout.write('\nInstalling Misago to database...')
-
-        if options['quiet']:
-            call_command('syncdb', verbosity=0)
-            call_command('migrate', verbosity=0)
-            call_command('syncfixtures', quiet=1)
-        else:
-            call_command('syncdb')
-            call_command('migrate')
-            call_command('syncfixtures')
-
-        if not options['quiet']:
+from optparse import make_option
+from django.core.management import call_command
+from django.core.management.base import BaseCommand, CommandError
+
+class Command(BaseCommand):
+    """
+    Builds Misago database from scratch
+    """
+    help = 'Install Misago to database'
+    option_list = BaseCommand.option_list + (
+        make_option('--quiet',
+            action='store_true',
+            dest='quiet',
+            default=False,
+            help='Dont display output from this message'),
+        )
+    
+    def handle(self, *args, **options):
+        if not options['quiet']:
+            self.stdout.write('\nInstalling Misago to database...')
+
+        if options['quiet']:
+            call_command('syncdb', verbosity=0)
+            call_command('migrate', verbosity=0)
+            call_command('syncfixtures', quiet=1)
+        else:
+            call_command('syncdb')
+            call_command('migrate')
+            call_command('syncfixtures')
+
+        if not options['quiet']:
             self.stdout.write('\nInstallation complete! Don\'t forget to run adduser to create first admin!\n')

+ 12 - 12
misago/management/commands/syncdeltas.py

@@ -1,12 +1,12 @@
-from django.core.management.base import BaseCommand
-from django.db.models import F
-from misago.models import Forum
-
-class Command(BaseCommand):
-    """
-    This command is intended to work as CRON job fired every few hours to update threads/posts/clicks history on forum
-    """
-    help = 'Clears users sessions'
-    def handle(self, *args, **options):
-        Forum.objects.all().update(threads_delta=F('threads'), posts_delta=F('posts'), redirects_delta=F('redirects'))
-        self.stdout.write('Forums deltas have been synced.\n')
+from django.core.management.base import BaseCommand
+from django.db.models import F
+from misago.models import Forum
+
+class Command(BaseCommand):
+    """
+    This command is intended to work as CRON job fired every few hours to update threads/posts/clicks history on forum
+    """
+    help = 'Clears users sessions'
+    def handle(self, *args, **options):
+        Forum.objects.all().update(threads_delta=F('threads'), posts_delta=F('posts'), redirects_delta=F('redirects'))
+        self.stdout.write('Forums deltas have been synced.\n')

+ 52 - 52
misago/management/commands/syncfixtures.py

@@ -1,52 +1,52 @@
-from optparse import make_option
-import traceback
-import os.path
-import pkgutil
-from django.core.management.base import BaseCommand
-from misago.models import Fixture
-from misago.utils.fixtures import load_fixture, update_fixture
-import misago.fixtures
-
-class Command(BaseCommand):
-    """
-    Loads Misago fixtures
-    """
-    help = 'Load Misago fixtures'
-    option_list = BaseCommand.option_list + (
-        make_option('--quiet',
-            action='store_true',
-            dest='quiet',
-            default=False,
-            help='Dont display output from this message'),
-        )
-    
-    def handle(self, *args, **options):
-        if not options['quiet']:
-            self.stdout.write('\nLoading data from fixtures...')
-            
-        fixture_data = {}
-        for fixture in Fixture.objects.all():
-            fixture_data[fixture.name] = fixture
-
-        loaded = 0
-        updated = 0
-        
-        fixtures_path = os.path.dirname(misago.fixtures.__file__)
-        try:
-            for _, name, _ in pkgutil.iter_modules([fixtures_path]):
-                if name in fixture_data:
-                    if update_fixture('misago.fixtures.' + name):
-                        updated += 1
-                        if not options['quiet']:
-                            self.stdout.write('Updating "%s" fixture...' % name)
-                else:
-                    if load_fixture('misago.fixtures.' + name):
-                        loaded += 1
-                        Fixture.objects.create(name=name)
-                        if not options['quiet']:
-                            self.stdout.write('Loading "%s" fixture...' % name)
-        except:
-            self.stderr.write(traceback.format_exc())
-
-        if not options['quiet']:
-            self.stdout.write('\nLoaded %s fixtures and updated %s fixtures.\n' % (loaded, updated))
+from optparse import make_option
+import traceback
+import os.path
+import pkgutil
+from django.core.management.base import BaseCommand
+from misago.models import Fixture
+from misago.utils.fixtures import load_fixture, update_fixture
+import misago.fixtures
+
+class Command(BaseCommand):
+    """
+    Loads Misago fixtures
+    """
+    help = 'Load Misago fixtures'
+    option_list = BaseCommand.option_list + (
+        make_option('--quiet',
+            action='store_true',
+            dest='quiet',
+            default=False,
+            help='Dont display output from this message'),
+        )
+    
+    def handle(self, *args, **options):
+        if not options['quiet']:
+            self.stdout.write('\nLoading data from fixtures...')
+            
+        fixture_data = {}
+        for fixture in Fixture.objects.all():
+            fixture_data[fixture.name] = fixture
+
+        loaded = 0
+        updated = 0
+        
+        fixtures_path = os.path.dirname(misago.fixtures.__file__)
+        try:
+            for _, name, _ in pkgutil.iter_modules([fixtures_path]):
+                if name in fixture_data:
+                    if update_fixture('misago.fixtures.' + name):
+                        updated += 1
+                        if not options['quiet']:
+                            self.stdout.write('Updating "%s" fixture...' % name)
+                else:
+                    if load_fixture('misago.fixtures.' + name):
+                        loaded += 1
+                        Fixture.objects.create(name=name)
+                        if not options['quiet']:
+                            self.stdout.write('Loading "%s" fixture...' % name)
+        except:
+            self.stderr.write(traceback.format_exc())
+
+        if not options['quiet']:
+            self.stdout.write('\nLoaded %s fixtures and updated %s fixtures.\n' % (loaded, updated))

+ 13 - 13
misago/management/commands/updatemisago.py

@@ -1,14 +1,14 @@
-from django.core.management import call_command
-from django.core.management.base import BaseCommand, CommandError
-
-class Command(BaseCommand):
-    """
-    Updates Misago to latest version
-    """
-    help = 'Update Misago database to latest version'
-    
-    def handle(self, *args, **options):
-        self.stdout.write('\nUpdating Misago database to latest version...')
-        call_command('migrate')
-        call_command('syncfixtures')
+from django.core.management import call_command
+from django.core.management.base import BaseCommand, CommandError
+
+class Command(BaseCommand):
+    """
+    Updates Misago to latest version
+    """
+    help = 'Update Misago database to latest version'
+    
+    def handle(self, *args, **options):
+        self.stdout.write('\nUpdating Misago database to latest version...')
+        call_command('migrate')
+        call_command('syncfixtures')
         self.stdout.write('\nUpdate complete!\n')

+ 59 - 59
misago/markdown/__init__.py

@@ -1,60 +1,60 @@
-from misago.markdown.factory import *
-
-# Monkeypatch blockquote parser to handle codes
-from markdown import util
-import markdown.blockprocessors
-from markdown.extensions.fenced_code import FENCED_BLOCK_RE, CODE_WRAP, LANG_TAG
-
-class MisagoBlockQuoteProcessor(markdown.blockprocessors.BlockQuoteProcessor):
-    def run(self, parent, blocks):
-        block = blocks.pop(0)
-        m = self.RE.search(block)
-        if m:
-            before = block[:m.start()] # Lines before blockquote
-            # Pass lines before blockquote in recursively for parsing forst.
-            self.parser.parseBlocks(parent, [before])
-            # Remove ``> `` from begining of each line.
-            block = '\n'.join([self.clean(line) for line in 
-                            block[m.start():].split('\n')])
-
-        sibling = self.lastChild(parent)
-        if sibling and sibling.tag == "blockquote":
-            # Previous block was a blockquote so set that as this blocks parent
-            quote = sibling
-        else:
-            # This is a new blockquote. Create a new parent element.
-            quote = util.etree.SubElement(parent, 'blockquote')
-        # Recursively parse block with blockquote as parent.
-        # change parser state so blockquotes embedded in lists use p tags
-        self.parser.state.set('blockquote')
-        # MONKEYPATCH START
-        block = self.clear_codes(block)
-        # MONKEYPATCH END
-        self.parser.parseChunk(quote, block)
-        self.parser.state.reset()
-
-    # MONKEYPATCH START
-    def clear_codes(self, text):
-        while 1:
-            m = FENCED_BLOCK_RE.search(text)
-            if m:
-                lang = ''
-                if m.group('lang'):
-                    lang = LANG_TAG % m.group('lang')
-                code = CODE_WRAP % (lang, self._escape(m.group('code')))
-                placeholder = self.parser.markdown.htmlStash.store(code, safe=True)
-                text = '%s\n\n%s\n\n%s' % (text[:m.start()].strip(), placeholder.strip(), text[m.end():].strip())
-            else:
-                break
-        return text.strip()
-
-    def _escape(self, txt):
-        """ basic html escaping """
-        txt = txt.replace('&', '&amp;')
-        txt = txt.replace('<', '&lt;')
-        txt = txt.replace('>', '&gt;')
-        txt = txt.replace('"', '&quot;')
-        return txt.strip()
-    # MONKEYPATCH END
-
+from misago.markdown.factory import *
+
+# Monkeypatch blockquote parser to handle codes
+from markdown import util
+import markdown.blockprocessors
+from markdown.extensions.fenced_code import FENCED_BLOCK_RE, CODE_WRAP, LANG_TAG
+
+class MisagoBlockQuoteProcessor(markdown.blockprocessors.BlockQuoteProcessor):
+    def run(self, parent, blocks):
+        block = blocks.pop(0)
+        m = self.RE.search(block)
+        if m:
+            before = block[:m.start()] # Lines before blockquote
+            # Pass lines before blockquote in recursively for parsing forst.
+            self.parser.parseBlocks(parent, [before])
+            # Remove ``> `` from begining of each line.
+            block = '\n'.join([self.clean(line) for line in 
+                            block[m.start():].split('\n')])
+
+        sibling = self.lastChild(parent)
+        if sibling and sibling.tag == "blockquote":
+            # Previous block was a blockquote so set that as this blocks parent
+            quote = sibling
+        else:
+            # This is a new blockquote. Create a new parent element.
+            quote = util.etree.SubElement(parent, 'blockquote')
+        # Recursively parse block with blockquote as parent.
+        # change parser state so blockquotes embedded in lists use p tags
+        self.parser.state.set('blockquote')
+        # MONKEYPATCH START
+        block = self.clear_codes(block)
+        # MONKEYPATCH END
+        self.parser.parseChunk(quote, block)
+        self.parser.state.reset()
+
+    # MONKEYPATCH START
+    def clear_codes(self, text):
+        while 1:
+            m = FENCED_BLOCK_RE.search(text)
+            if m:
+                lang = ''
+                if m.group('lang'):
+                    lang = LANG_TAG % m.group('lang')
+                code = CODE_WRAP % (lang, self._escape(m.group('code')))
+                placeholder = self.parser.markdown.htmlStash.store(code, safe=True)
+                text = '%s\n\n%s\n\n%s' % (text[:m.start()].strip(), placeholder.strip(), text[m.end():].strip())
+            else:
+                break
+        return text.strip()
+
+    def _escape(self, txt):
+        """ basic html escaping """
+        txt = txt.replace('&', '&amp;')
+        txt = txt.replace('<', '&lt;')
+        txt = txt.replace('>', '&gt;')
+        txt = txt.replace('"', '&quot;')
+        return txt.strip()
+    # MONKEYPATCH END
+
 markdown.blockprocessors.BlockQuoteProcessor = MisagoBlockQuoteProcessor

+ 20 - 20
misago/markdown/extensions/bbcodes.py

@@ -1,20 +1,20 @@
-import re
-import markdown
-from markdown.inlinepatterns import SimpleTagPattern
-
-EMPHASIS_RE = r'\[i\]([^*]+)\[/i\]'
-STRONG_RE = r'\[b\]([^*]+)\[/b\]'
-STRONG_ALT_RE = r'\[u\]([^*]+)\[/u\]'
-
-class BBCodesExtension(markdown.Extension):
-    def extendMarkdown(self, md):
-        md.registerExtension(self)
-        md.inlinePatterns.add('mi_bb_bold_alt',
-                              SimpleTagPattern(STRONG_ALT_RE, 'strong'),
-                              '>strong')
-        md.inlinePatterns.add('mi_bb_bold',
-                              SimpleTagPattern(STRONG_RE, 'strong'),
-                              '>strong')
-        md.inlinePatterns.add('mi_bb_italics',
-                              SimpleTagPattern(EMPHASIS_RE, 'em'),
-                              '>emphasis')
+import re
+import markdown
+from markdown.inlinepatterns import SimpleTagPattern
+
+EMPHASIS_RE = r'\[i\]([^*]+)\[/i\]'
+STRONG_RE = r'\[b\]([^*]+)\[/b\]'
+STRONG_ALT_RE = r'\[u\]([^*]+)\[/u\]'
+
+class BBCodesExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.inlinePatterns.add('mi_bb_bold_alt',
+                              SimpleTagPattern(STRONG_ALT_RE, 'strong'),
+                              '>strong')
+        md.inlinePatterns.add('mi_bb_bold',
+                              SimpleTagPattern(STRONG_RE, 'strong'),
+                              '>strong')
+        md.inlinePatterns.add('mi_bb_italics',
+                              SimpleTagPattern(EMPHASIS_RE, 'em'),
+                              '>emphasis')

+ 37 - 37
misago/markdown/extensions/cleanlinks.py

@@ -1,37 +1,37 @@
-import markdown
-from markdown.util import etree
-from misago.utils.urls import is_url, is_inner, clean_inner
-
-class CleanLinksExtension(markdown.Extension):
-    def extendMarkdown(self, md):
-        md.registerExtension(self)
-        md.treeprocessors.add('mi_cleanlinks',
-                              CleanLinksTreeprocessor(md),
-                              '_end')
-
-
-class CleanLinksTreeprocessor(markdown.treeprocessors.Treeprocessor):
-    def run(self, root):
-        self.inurl = False
-        return self.walk_tree(root)
-
-    def walk_tree(self, node):
-        if node.tag == 'a':
-            self.inurl = True
-            if is_inner(node.get('href')):
-                node.set('href', clean_inner(node.get('href')))
-            else:
-                node.set('rel', 'nofollow')
-        if node.tag == 'img':
-            if is_inner(node.get('src')):
-                node.set('src', '%s' % clean_inner(node.get('src')))
-
-        try:
-            if self.inurl and node.text and is_url(node.text) and is_inner(node.text):
-                node.text = clean_inner(node.text)[1:]
-        except TypeError:
-            pass
-            
-        for i in node:
-            self.walk_tree(i)
-        self.inurl = False
+import markdown
+from markdown.util import etree
+from misago.utils.urls import is_url, is_inner, clean_inner
+
+class CleanLinksExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.treeprocessors.add('mi_cleanlinks',
+                              CleanLinksTreeprocessor(md),
+                              '_end')
+
+
+class CleanLinksTreeprocessor(markdown.treeprocessors.Treeprocessor):
+    def run(self, root):
+        self.inurl = False
+        return self.walk_tree(root)
+
+    def walk_tree(self, node):
+        if node.tag == 'a':
+            self.inurl = True
+            if is_inner(node.get('href')):
+                node.set('href', clean_inner(node.get('href')))
+            else:
+                node.set('rel', 'nofollow')
+        if node.tag == 'img':
+            if is_inner(node.get('src')):
+                node.set('src', '%s' % clean_inner(node.get('src')))
+
+        try:
+            if self.inurl and node.text and is_url(node.text) and is_inner(node.text):
+                node.text = clean_inner(node.text)[1:]
+        except TypeError:
+            pass
+            
+        for i in node:
+            self.walk_tree(i)
+        self.inurl = False

+ 43 - 43
misago/markdown/extensions/magiclinks.py

@@ -1,44 +1,44 @@
-#-*- coding: utf-8 -*-
-import re
-import markdown
-from markdown.inlinepatterns import LinkPattern
-from markdown.postprocessors import RawHtmlPostprocessor
-from markdown.util import etree
-from misago.utils.strings import html_escape
-from misago.utils.urls import is_inner, clean_inner
-
-# Global vars
-MAGICLINKS_RE = re.compile(r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', re.UNICODE)
-
-class MagicLinksExtension(markdown.Extension):
-    def extendMarkdown(self, md):
-        md.registerExtension(self)
-        md.treeprocessors.add('mi_magiclinks',
-                              MagicLinksTreeprocessor(md),
-                              '_end')
-
-
-class MagicLinksTreeprocessor(markdown.treeprocessors.Treeprocessor):
-    def run(self, root):
-        return self.walk_tree(root)
-
-    def walk_tree(self, node):
-        def parse_link(matchobj):
-            link = LinkPattern(MAGICLINKS_RE, self.markdown)
-            href = link.sanitize_url(link.unescape(matchobj.group(0).strip()))
-            if href:
-                if is_inner(href):
-                    clean = clean_inner(href)
-                    return self.markdown.htmlStash.store('<a href="%s">%s</a>' % (clean, clean[1:]), safe=True)
-                else:
-                    return self.markdown.htmlStash.store('<a href="%(href)s" rel="nofollow">%(href)s</a>' % {'href': href}, safe=True)
-            else:
-                return matchobj.group(0)
-
-        if node.tag not in ['code', 'pre', 'a', 'img']:
-            if node.text and unicode(node.text).strip():
-                node.text = MAGICLINKS_RE.sub(parse_link, unicode(node.text))
-            if node.tail and unicode(node.tail).strip():
-                node.tail = MAGICLINKS_RE.sub(parse_link, unicode(node.tail))
-            for i in node:
+#-*- coding: utf-8 -*-
+import re
+import markdown
+from markdown.inlinepatterns import LinkPattern
+from markdown.postprocessors import RawHtmlPostprocessor
+from markdown.util import etree
+from misago.utils.strings import html_escape
+from misago.utils.urls import is_inner, clean_inner
+
+# Global vars
+MAGICLINKS_RE = re.compile(r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', re.UNICODE)
+
+class MagicLinksExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.treeprocessors.add('mi_magiclinks',
+                              MagicLinksTreeprocessor(md),
+                              '_end')
+
+
+class MagicLinksTreeprocessor(markdown.treeprocessors.Treeprocessor):
+    def run(self, root):
+        return self.walk_tree(root)
+
+    def walk_tree(self, node):
+        def parse_link(matchobj):
+            link = LinkPattern(MAGICLINKS_RE, self.markdown)
+            href = link.sanitize_url(link.unescape(matchobj.group(0).strip()))
+            if href:
+                if is_inner(href):
+                    clean = clean_inner(href)
+                    return self.markdown.htmlStash.store('<a href="%s">%s</a>' % (clean, clean[1:]), safe=True)
+                else:
+                    return self.markdown.htmlStash.store('<a href="%(href)s" rel="nofollow">%(href)s</a>' % {'href': href}, safe=True)
+            else:
+                return matchobj.group(0)
+
+        if node.tag not in ['code', 'pre', 'a', 'img']:
+            if node.text and unicode(node.text).strip():
+                node.text = MAGICLINKS_RE.sub(parse_link, unicode(node.text))
+            if node.tail and unicode(node.tail).strip():
+                node.tail = MAGICLINKS_RE.sub(parse_link, unicode(node.tail))
+            for i in node:
                 self.walk_tree(i)

+ 59 - 59
misago/markdown/extensions/mentions.py

@@ -1,59 +1,59 @@
-import re
-import markdown
-from markdown.util import etree
-from django.core.urlresolvers import reverse
-from misago.models import User
-from misago.utils.strings import slugify
-
-# Global vars
-MENTION_RE = re.compile(r'([^\w]?)@(?P<username>(\w)+)', re.UNICODE)
-
-
-class MentionsExtension(markdown.Extension):
-    def extendMarkdown(self, md):
-        md.mentions = {}
-        md.registerExtension(self)
-        md.preprocessors.add('mi_mentions',
-                             MentionsPreprocessor(md),
-                             '>mi_quote_title')
-        md.postprocessors.add('mi_mentions',
-                              MentionsPostprocessor(md),
-                              '>mi_quote_title')
-
-
-class MentionsPreprocessor(markdown.preprocessors.Preprocessor):
-    def __init__(self, md):
-        markdown.preprocessors.Preprocessor.__init__(self, md)
-        self.md = md
-
-    def run(self, lines):
-        def mention(match):
-            slug = slugify(match.group(0)[1:])
-            if slug in self.md.mentions:
-                user = self.md.mentions[slug]
-                return '%s[@%s](%s)' % (match.group(1), user.username, reverse('user', kwargs={
-                                                                                              'user': user.pk,
-                                                                                              'username': user.username_slug,
-                                                                                              }))
-            elif len(self.md.mentions) < 32:
-                try:
-                    user = User.objects.get(username_slug=slug)
-                    self.md.mentions[slug] = user
-                    return '%s[@%s](%s)' % (match.group(1), user.username, reverse('user', kwargs={
-                                                                                                  'user': user.pk,
-                                                                                                  'username': user.username_slug,
-                                                                                                  }))
-                except User.DoesNotExist:
-                    pass
-            return match.group(0)
-        clean = []
-        for l, line in enumerate(lines):
-            if line.strip():
-                line = MENTION_RE.sub(mention, line)
-            clean.append(line)
-        return clean
-
-
-class MentionsPostprocessor(markdown.postprocessors.Postprocessor):
-    def run(self, text):
-        return text
+import re
+import markdown
+from markdown.util import etree
+from django.core.urlresolvers import reverse
+from misago.models import User
+from misago.utils.strings import slugify
+
+# Global vars
+MENTION_RE = re.compile(r'([^\w]?)@(?P<username>(\w)+)', re.UNICODE)
+
+
+class MentionsExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.mentions = {}
+        md.registerExtension(self)
+        md.preprocessors.add('mi_mentions',
+                             MentionsPreprocessor(md),
+                             '>mi_quote_title')
+        md.postprocessors.add('mi_mentions',
+                              MentionsPostprocessor(md),
+                              '>mi_quote_title')
+
+
+class MentionsPreprocessor(markdown.preprocessors.Preprocessor):
+    def __init__(self, md):
+        markdown.preprocessors.Preprocessor.__init__(self, md)
+        self.md = md
+
+    def run(self, lines):
+        def mention(match):
+            slug = slugify(match.group(0)[1:])
+            if slug in self.md.mentions:
+                user = self.md.mentions[slug]
+                return '%s[@%s](%s)' % (match.group(1), user.username, reverse('user', kwargs={
+                                                                                              'user': user.pk,
+                                                                                              'username': user.username_slug,
+                                                                                              }))
+            elif len(self.md.mentions) < 32:
+                try:
+                    user = User.objects.get(username_slug=slug)
+                    self.md.mentions[slug] = user
+                    return '%s[@%s](%s)' % (match.group(1), user.username, reverse('user', kwargs={
+                                                                                                  'user': user.pk,
+                                                                                                  'username': user.username_slug,
+                                                                                                  }))
+                except User.DoesNotExist:
+                    pass
+            return match.group(0)
+        clean = []
+        for l, line in enumerate(lines):
+            if line.strip():
+                line = MENTION_RE.sub(mention, line)
+            clean.append(line)
+        return clean
+
+
+class MentionsPostprocessor(markdown.postprocessors.Postprocessor):
+    def run(self, text):
+        return text

+ 31 - 31
misago/markdown/extensions/shorthandimgs.py

@@ -1,32 +1,32 @@
-#-*- coding: utf-8 -*-
-import re
-import markdown
-from markdown.inlinepatterns import LinkPattern
-from misago.utils.strings import html_escape
-from misago.utils.urls import is_inner, clean_inner
-from markdown.util import etree
-
-IMAGES_RE =  r'\!(\s?)\((<.*?>|([^\)]*))\)'
-
-class ShorthandImagesExtension(markdown.Extension):
-    def extendMarkdown(self, md):
-        md.registerExtension(self)
-        md.inlinePatterns.add('mi_shorthand_imgs',
-                              ShorthandImagePattern(IMAGES_RE, md),
-                              '_end')
-
-
-class ShorthandImagePattern(LinkPattern):
-    def handleMatch(self, m):
-        img_src = m.groups()[2].strip()
-        if is_inner(img_src):
-            img_src = self.sanitize_url(clean_inner(img_src))
-        if img_src:
-            a = etree.Element("a")
-            a.set('href', img_src)
-            a.set('rel', 'nofollow')
-            a.set('target', '_blank')
-            img = etree.SubElement(a, "img")
-            img.set('src', img_src)
-            img.set('alt', img_src)
+#-*- coding: utf-8 -*-
+import re
+import markdown
+from markdown.inlinepatterns import LinkPattern
+from misago.utils.strings import html_escape
+from misago.utils.urls import is_inner, clean_inner
+from markdown.util import etree
+
+IMAGES_RE =  r'\!(\s?)\((<.*?>|([^\)]*))\)'
+
+class ShorthandImagesExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.inlinePatterns.add('mi_shorthand_imgs',
+                              ShorthandImagePattern(IMAGES_RE, md),
+                              '_end')
+
+
+class ShorthandImagePattern(LinkPattern):
+    def handleMatch(self, m):
+        img_src = m.groups()[2].strip()
+        if is_inner(img_src):
+            img_src = self.sanitize_url(clean_inner(img_src))
+        if img_src:
+            a = etree.Element("a")
+            a.set('href', img_src)
+            a.set('rel', 'nofollow')
+            a.set('target', '_blank')
+            img = etree.SubElement(a, "img")
+            img.set('src', img_src)
+            img.set('alt', img_src)
             return a

+ 13 - 13
misago/markdown/extensions/strikethrough.py

@@ -1,13 +1,13 @@
-import re
-import markdown
-from markdown.inlinepatterns import SimpleTagPattern
-
-# Global vars
-STRIKETHROUGH_RE = r'(~{2})(.+?)\2'
-
-class StrikethroughExtension(markdown.Extension):
-    def extendMarkdown(self, md):
-        md.registerExtension(self)
-        md.inlinePatterns.add('mi_strikethrough',
-                              SimpleTagPattern(STRIKETHROUGH_RE, 'del'),
-                              '_end')
+import re
+import markdown
+from markdown.inlinepatterns import SimpleTagPattern
+
+# Global vars
+STRIKETHROUGH_RE = r'(~{2})(.+?)\2'
+
+class StrikethroughExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.inlinePatterns.add('mi_strikethrough',
+                              SimpleTagPattern(STRIKETHROUGH_RE, 'del'),
+                              '_end')