Browse Source

wip #839 and #838: tests are locally passing with latest deps and django 1.11

Rafał Pitoń 8 years ago
parent
commit
bead1f4ae0
38 changed files with 339 additions and 122 deletions
  1. 0 6
      docs/settings/Core.md
  2. 8 2
      misago/admin/urlpatterns.py
  3. 16 4
      misago/categories/migrations/0001_initial.py
  4. 12 3
      misago/categories/models.py
  5. 0 8
      misago/conf/defaults.py
  6. 6 1
      misago/conf/migrations/0001_initial.py
  7. 1 1
      misago/conf/models.py
  8. 2 2
      misago/core/errorpages.py
  9. 4 2
      misago/core/management/commands/misagodbrelations.py
  10. 16 3
      misago/core/testproject/urls.py
  11. 0 18
      misago/core/views.py
  12. 7 1
      misago/markup/parser.py
  13. 18 0
      misago/markup/tests/test_parser.py
  14. 16 4
      misago/project_template/project_name/urls.py
  15. 31 5
      misago/readtracker/migrations/0001_initial.py
  16. 20 5
      misago/readtracker/models.py
  17. 1 1
      misago/templates/misago/admin/users/edit.html
  18. 1 1
      misago/templates/misago/categories/list_item.html
  19. 1 1
      misago/templates/misago/errorpages/403.html
  20. 12 12
      misago/templates/misago/profile/ban_details.html
  21. 2 2
      misago/templates/misago/threadslist/category.html
  22. 2 2
      misago/templates/misago/userslists/rank.html
  23. 44 8
      misago/threads/migrations/0001_initial.py
  24. 1 1
      misago/threads/models/attachment.py
  25. 8 2
      misago/threads/models/poll.py
  26. 12 3
      misago/threads/models/pollvote.py
  27. 8 2
      misago/threads/models/post.py
  28. 7 3
      misago/threads/models/postedit.py
  29. 12 3
      misago/threads/models/postlike.py
  30. 9 3
      misago/threads/models/subscription.py
  31. 4 1
      misago/threads/models/thread.py
  32. 8 2
      misago/threads/models/threadparticipant.py
  33. 7 2
      misago/urls.py
  34. 14 3
      misago/users/migrations/0001_initial.py
  35. 5 1
      misago/users/models/activityranking.py
  36. 7 1
      misago/users/models/ban.py
  37. 12 2
      misago/users/models/user.py
  38. 5 1
      misago/users/views/auth.py

+ 0 - 6
docs/settings/Core.md

@@ -123,12 +123,6 @@ In case of more events than specified being found, oldest events will be truncat
 Hourly limit of posts that may be posted from single account. Fail-safe for situations when forum is flooded by spam bot. Change to 0 to lift this restriction.
 
 
-## `MISAGO_JS_CATALOG_PACKAGES`
-
-List of packages that should be included in Misago's default JS I18n catalog, available under the `/django-i18n.js` url. Defaults to `['misago']`. See [Django documentation](https://docs.djangoproject.com/en/1.10/topics/i18n/translation/#internationalization-in-javascript-code) for further explanation.
-
-
-
 ## `MISAGO_LOGIN_API_URL`
 URL to API endpoint used to authenticate sign-in credentials. Musn't contain api prefix or wrapping slashes. Defaults to 'auth/login'.
 

+ 8 - 2
misago/admin/urlpatterns.py

@@ -27,7 +27,10 @@ class URLPatterns(object):
             if namespace['parent'] == parent:
                 prefixed_namespace = prefix + namespace['namespace']
                 child_patterns = self.get_child_patterns(prefixed_namespace)
-                included_patterns = include(child_patterns, namespace=namespace['namespace'])
+                included_patterns = include(
+                    (child_patterns, namespace['namespace']),
+                    namespace=namespace['namespace'],
+                )
                 namespace_urlpatterns.append(url(namespace['path'], included_patterns))
 
         return namespace_urlpatterns
@@ -46,7 +49,10 @@ class URLPatterns(object):
         for namespace in self._namespaces:
             if not namespace['parent']:
                 child_patterns = self.get_child_patterns(namespace['namespace'])
-                included_patterns = include(child_patterns, namespace=namespace['namespace'])
+                included_patterns = include(
+                    (child_patterns, namespace['namespace']),
+                    namespace=namespace['namespace'],
+                )
                 root_urlpatterns.append(url(namespace['path'], included_patterns))
 
         return root_urlpatterns

+ 16 - 4
misago/categories/migrations/0001_initial.py

@@ -67,6 +67,7 @@ class Migration(migrations.Migration):
                 (
                     'parent', mptt.fields.TreeForeignKey(
                         related_name='children',
+                        on_delete=django.db.models.deletion.CASCADE,
                         blank=True,
                         to='misago_categories.Category',
                         null=True
@@ -105,14 +106,25 @@ class Migration(migrations.Migration):
                 ),
                 (
                     'category', models.ForeignKey(
-                        related_name='category_role_set', to='misago_categories.Category'
+                        related_name='category_role_set',
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_categories.Category',
                     )
                 ),
                 (
-                    'category_role',
-                    models.ForeignKey(to='misago_categories.CategoryRole', to_field='id')
+                    'category_role', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_categories.CategoryRole',
+                        to_field='id',
+                    )
+                ),
+                (
+                    'role', models.ForeignKey(
+                        related_name='categories_acls',
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_acl.Role',
+                    )
                 ),
-                ('role', models.ForeignKey(related_name='categories_acls', to='misago_acl.Role')),
             ],
             options={},
             bases=(models.Model, ),

+ 12 - 3
misago/categories/models.py

@@ -65,6 +65,7 @@ class Category(MPTTModel):
         null=True,
         blank=True,
         related_name='children',
+        on_delete=models.CASCADE,
     )
     special_role = models.CharField(max_length=255, null=True, blank=True)
     name = models.CharField(max_length=255)
@@ -191,6 +192,14 @@ class CategoryRole(BaseRole):
 
 
 class RoleCategoryACL(models.Model):
-    role = models.ForeignKey('misago_acl.Role', related_name='categories_acls')
-    category = models.ForeignKey('Category', related_name='category_role_set')
-    category_role = models.ForeignKey(CategoryRole)
+    role = models.ForeignKey(
+        'misago_acl.Role',
+        related_name='categories_acls',
+        on_delete=models.CASCADE,
+    )
+    category = models.ForeignKey(
+        'Category',
+        related_name='category_role_set',
+        on_delete=models.CASCADE,
+    )
+    category_role = models.ForeignKey(CategoryRole, on_delete=models.CASCADE)

+ 0 - 8
misago/conf/defaults.py

@@ -244,14 +244,6 @@ MISAGO_USERS_PER_PAGE = 12
 MISAGO_READTRACKER_CUTOFF = 40
 
 
-# List of packages that should be included in Misago's default JS I18n catalog
-# https://docs.djangoproject.com/en/1.10/topics/i18n/translation/#internationalization-in-javascript-code
-
-MISAGO_JS_CATALOG_PACKAGES = [
-    'misago',
-]
-
-
 # Available Moment.js locales
 
 MISAGO_MOMENT_JS_LOCALES = [

+ 6 - 1
misago/conf/migrations/0001_initial.py

@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
+import django.db.models.deletion
 from django.contrib.postgres.fields import JSONField
 from django.db import migrations, models
 
@@ -52,7 +53,11 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='setting',
             name='group',
-            field=models.ForeignKey(to='misago_conf.SettingsGroup', to_field='id'),
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                to='misago_conf.SettingsGroup',
+                to_field='id',
+            ),
             preserve_default=True,
         ),
     ]

+ 1 - 1
misago/conf/models.py

@@ -41,7 +41,7 @@ class SettingsManager(models.Manager):
 
 
 class Setting(models.Model):
-    group = models.ForeignKey(SettingsGroup)
+    group = models.ForeignKey(SettingsGroup, on_delete=models.CASCADE)
     setting = models.CharField(max_length=255, unique=True)
     name = models.CharField(max_length=255)
     description = models.TextField(null=True, blank=True)

+ 2 - 2
misago/core/errorpages.py

@@ -37,14 +37,14 @@ def banned(request, ban):
     )
 
 
-def permission_denied(request, message=None):
+def permission_denied(request, message=None, exception=None):
     if request.is_ajax():
         return _ajax_error(403, message or _("Permission denied."))
     else:
         return _error_page(request, 403, message)
 
 
-def page_not_found(request):
+def page_not_found(request, exception=None):
     if request.is_ajax():
         return _ajax_error(404, "Not found.")
     else:

+ 4 - 2
misago/core/management/commands/misagodbrelations.py

@@ -34,8 +34,10 @@ class Command(BaseCommand):
                         for field in model_relations:
                             self.stdout.write(
                                 field_pattern % (
-                                    field.name, field.__class__.__name__,
-                                    field.related_model.__name__, field.rel.on_delete.__name__,
+                                    field.name,
+                                    field.__class__.__name__,
+                                    field.related_model.__name__,
+                                    field.remote_field.on_delete.__name__,
                                 )
                             )
 

+ 16 - 3
misago/core/testproject/urls.py

@@ -1,8 +1,11 @@
 from django.conf.urls import include, url
 # Setup Django admin to work with Misago auth
 from django.contrib import admin
+from django.utils import timezone
+from django.views.decorators.cache import cache_page
+from django.views.decorators.http import last_modified
+from django.views.i18n import JavaScriptCatalog
 
-from misago.core.views import javascript_catalog
 from misago.users.forms.auth import AdminAuthenticationForm
 
 from . import views
@@ -13,8 +16,18 @@ admin.site.login_form = AdminAuthenticationForm
 
 urlpatterns = [
     url(r'^forum/', include('misago.urls', namespace='misago')),
-    url(r'^django-admin/', include(admin.site.urls)),
-    url(r'^django-i18n.js$', javascript_catalog, name='django-i18n'),
+    url(r'^django-admin/', admin.site.urls),
+    url(
+        r'^django-i18n.js$',
+        cache_page(86400 * 2, key_prefix='misagojsi18n')(
+            last_modified(lambda req, **kw: timezone.now())(
+                JavaScriptCatalog.as_view(
+                    packages=['misago'],
+                ),
+            ),
+        ),
+        name='django-i18n'
+    ),
     url(r'^forum/test-mail-user/$', views.test_mail_user, name='test-mail-user'),
     url(r'^forum/test-mail-users/$', views.test_mail_users, name='test-mail-users'),
     url(r'^forum/test-pagination/$', views.test_pagination, name='test-pagination'),

+ 0 - 18
misago/core/views.py

@@ -1,10 +1,4 @@
 from django.shortcuts import redirect
-from django.utils import timezone
-from django.views import i18n
-from django.views.decorators.cache import cache_page
-from django.views.decorators.http import last_modified
-
-from misago.conf import settings
 
 
 def forum_index(request):
@@ -13,15 +7,3 @@ def forum_index(request):
 
 def home_redirect(*args, **kwargs):
     return redirect('misago:index')
-
-
-@cache_page(86400 * 2, key_prefix='misagojsi18n')
-@last_modified(lambda req, **kw: timezone.now())
-def javascript_catalog(request):
-    return i18n.javascript_catalog(
-        request,
-        'djangojs',
-        {
-            'packages': settings.MISAGO_JS_CATALOG_PACKAGES,
-        },
-    )

+ 7 - 1
misago/markup/parser.py

@@ -83,7 +83,13 @@ def parse(
 
 def md_factory(allow_links=True, allow_images=True, allow_blocks=True):
     """creates and configures markdown object"""
-    md = markdown.Markdown(safe_mode='escape', extensions=['nl2br'])
+    md = markdown.Markdown(extensions=[
+        'markdown.extensions.nl2br',
+    ])
+
+    # Remove HTML allowances
+    del md.preprocessors['html_block']
+    del md.inlinePatterns['html']
 
     # Remove references
     del md.preprocessors['reference']

+ 18 - 0
misago/markup/tests/test_parser.py

@@ -25,6 +25,24 @@ class MockPoster(object):
     slug = 'loremipsum'
 
 
+class HTMLTests(TestCase):
+    def test_html_escaped(self):
+        """parser escapes all html"""
+        test_text = """
+Lorem <strong>ipsum!</strong>
+""".strip()
+
+        expected_result = """
+<p>Lorem &lt;strong&gt;ipsum!&lt;/strong&gt;</p>
+""".strip()
+
+        result = parse(test_text, MockRequest(), MockPoster(), minify=True)
+        self.assertEqual(expected_result, result['parsed_text'])
+        self.assertEqual(result['internal_links'], [])
+        self.assertEqual(result['images'], [])
+        self.assertEqual(result['outgoing_links'], [])
+
+
 class BBCodeTests(TestCase):
     def test_inline_text(self):
         """inline elements are correctly parsed"""

+ 16 - 4
misago/project_template/project_name/urls.py

@@ -17,9 +17,11 @@ from django.conf import settings
 from django.conf.urls import include, url
 from django.conf.urls.static import static
 from django.contrib import admin
-from django.views.generic import TemplateView
+from django.utils import timezone
+from django.views.decorators.cache import cache_page
+from django.views.decorators.http import last_modified
+from django.views.i18n import JavaScriptCatalog
 
-from misago.core.views import javascript_catalog
 from misago.users.forms.auth import AdminAuthenticationForm
 
 
@@ -31,10 +33,20 @@ urlpatterns = [
     url(r'^', include('misago.urls', namespace='misago')),
 
     # Javascript translations
-    url(r'^django-i18n.js$', javascript_catalog, name='django-i18n'),
+    url(
+        r'^django-i18n.js$',
+        cache_page(86400 * 2, key_prefix='misagojsi18n')(
+            last_modified(lambda req, **kw: timezone.now())(
+                JavaScriptCatalog.as_view(
+                    packages=['misago'],
+                ),
+            ),
+        ),
+        name='django-i18n'
+    ),
 
     # Uncomment next line if you plan to use Django admin for 3rd party apps
-    #url(r'^django-admin/', include(admin.site.urls)),
+    #url(r'^django-admin/', admin.site.urls),
 ]
 
 

+ 31 - 5
misago/readtracker/migrations/0001_initial.py

@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
+import django.db.models.deletion
 from django.conf import settings
 from django.db import migrations, models
 
@@ -22,8 +23,18 @@ class Migration(migrations.Migration):
                     )
                 ),
                 ('last_read_on', models.DateTimeField()),
-                ('category', models.ForeignKey(to='misago_categories.Category')),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+                (
+                    'category', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_categories.Category',
+                    )
+                ),
+                (
+                    'user', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    )
+                ),
             ],
             options={},
             bases=(models.Model, ),
@@ -37,9 +48,24 @@ class Migration(migrations.Migration):
                     )
                 ),
                 ('last_read_on', models.DateTimeField()),
-                ('category', models.ForeignKey(to='misago_categories.Category')),
-                ('thread', models.ForeignKey(to='misago_threads.Thread')),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+                (
+                    'category', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_categories.Category',
+                    )
+                ),
+                (
+                    'thread', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_threads.Thread',
+                    )
+                ),
+                (
+                    'user', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    )
+                ),
             ],
             options={},
             bases=(models.Model, ),

+ 20 - 5
misago/readtracker/models.py

@@ -3,13 +3,28 @@ from django.db import models
 
 
 class CategoryRead(models.Model):
-    user = models.ForeignKey(settings.AUTH_USER_MODEL)
-    category = models.ForeignKey('misago_categories.Category')
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.CASCADE,
+    )
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
     last_read_on = models.DateTimeField()
 
 
 class ThreadRead(models.Model):
-    user = models.ForeignKey(settings.AUTH_USER_MODEL)
-    category = models.ForeignKey('misago_categories.Category')
-    thread = models.ForeignKey('misago_threads.Thread')
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.CASCADE,
+    )
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
+    thread = models.ForeignKey(
+        'misago_threads.Thread',
+        on_delete=models.CASCADE,
+    )
     last_read_on = models.DateTimeField()

+ 1 - 1
misago/templates/misago/admin/users/edit.html

@@ -161,7 +161,7 @@ class="form-horizontal"
         <div class="{{ field_class }}">
           <div class="form-control-static">
             {% if target.is_active_staff_message %}
-              {{ target.is_active_staff_message|escape|linebreaks }}
+              {{ target.is_active_staff_message|force_escape|urlize|linebreaks }}
             {% else %}
               <em>{% trans "No staff message is available." %}</em>
             {% endif %}

+ 1 - 1
misago/templates/misago/categories/list_item.html

@@ -14,7 +14,7 @@
           </h4>
           {% if category.description %}
             <div class="category-description">
-              {{ category.description|escape|urlize|linebreaks }}
+              {{ category.description|force_escape|urlize|linebreaks }}
             </div>
           {% endif %}
         </div>

+ 1 - 1
misago/templates/misago/errorpages/403.html

@@ -38,7 +38,7 @@
       <div class="message-body">
         <p class="lead">{% trans "This page is not available." %}</p>
         {% if message %}
-          <p>{{ message|escape|urlize|linebreaksbr }}</p>
+          <p>{{ message|force_escape|urlize|linebreaksbr }}</p>
         {% else %}
           <p>{% trans "You don't have permission to access this page." %}</p>
         {% endif %}

+ 12 - 12
misago/templates/misago/profile/ban_details.html

@@ -17,25 +17,25 @@
     <div>
 
       {% if ban.user_message %}
-      <div class="panel-body ban-message ban-user-message">
+        <div class="panel-body ban-message ban-user-message">
 
-        <h4>{% trans "User-shown ban message" %}</h4>
-        <div class="lead">
-          {{ ban.user_message|escape|urlize|linebreaks }}
-        </div>
+          <h4>{% trans "User-shown ban message" %}</h4>
+          <div class="lead">
+            {{ ban.user_message|force_escape|urlize|linebreaks }}
+          </div>
 
-      </div>
+        </div>
       {% endif %}
 
       {% if ban.staff_message %}
-      <div class="panel-body ban-message ban-staff-message">
+        <div class="panel-body ban-message ban-staff-message">
 
-        <h4>{% trans "Team-shown ban message" %}</h4>
-        <div class="lead">
-          {{ ban.staff_message|escape|urlize|linebreaks }}
-        </div>
+          <h4>{% trans "Team-shown ban message" %}</h4>
+          <div class="lead">
+            {{ ban.staff_message|force_escape|urlize|linebreaks }}
+          </div>
 
-      </div>
+        </div>
       {% endif %}
 
       <div class="panel-body ban-expires">

+ 2 - 2
misago/templates/misago/threadslist/category.html

@@ -82,8 +82,8 @@
 {% block list-container %}
   {% if category.description %}
     <div class="category-description">
-      <div class="page-lead {{ category.description|escape|linebreaks|isdescriptionshort|iftrue:"lead" }}">
-        {{ category.description|escape|urlize|linebreaks|safe }}
+      <div class="page-lead {{ category.description|force_escape|linebreaks|isdescriptionshort|iftrue:"lead" }}">
+        {{ category.description|force_escape|urlize|linebreaks|safe }}
       </div>
     </div>
   {% endif %}

+ 2 - 2
misago/templates/misago/userslists/rank.html

@@ -58,8 +58,8 @@
 
     {% if rank.description %}
       <section class="rank-description">
-        <div class="page-lead {{ rank.description|escape|linebreaks|isdescriptionshort|iftrue:"lead" }}">
-          {{ rank.description|escape|urlize|linebreaks|safe }}
+        <div class="page-lead {{ rank.description|force_escape|linebreaks|isdescriptionshort|iftrue:"lead" }}">
+          {{ rank.description|force_escape|urlize|linebreaks|safe }}
         </div>
       </section>
     {% endif %}

+ 44 - 8
misago/threads/migrations/0001_initial.py

@@ -55,7 +55,12 @@ class Migration(migrations.Migration):
                 ('is_unapproved', models.BooleanField(default=False, db_index=True)),
                 ('is_hidden', models.BooleanField(default=False)),
                 ('is_protected', models.BooleanField(default=False)),
-                ('category', models.ForeignKey(to='misago_categories.Category')),
+                (
+                    'category', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_categories.Category',
+                    )
+                ),
                 (
                     'last_editor', models.ForeignKey(
                         related_name='+',
@@ -149,8 +154,18 @@ class Migration(migrations.Migration):
                         verbose_name='ID', serialize=False, auto_created=True, primary_key=True
                     )
                 ),
-                ('thread', models.ForeignKey(to='misago_threads.Thread')),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+                (
+                    'thread', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_threads.Thread',
+                    )
+                ),
+                (
+                    'user', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    )
+                ),
                 ('is_owner', models.BooleanField(default=False)),
             ],
             options={},
@@ -199,7 +214,10 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='post',
             name='thread',
-            field=models.ForeignKey(to='misago_threads.Thread'),
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                to='misago_threads.Thread',
+            ),
             preserve_default=True,
         ),
         migrations.AddField(
@@ -217,7 +235,10 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='thread',
             name='category',
-            field=models.ForeignKey(to='misago_categories.Category'),
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                to='misago_categories.Category',
+            ),
             preserve_default=True,
         ),
         migrations.AddField(
@@ -281,9 +302,24 @@ class Migration(migrations.Migration):
                 ),
                 ('last_read_on', models.DateTimeField(default=django.utils.timezone.now)),
                 ('send_email', models.BooleanField(default=False)),
-                ('category', models.ForeignKey(to='misago_categories.Category')),
-                ('thread', models.ForeignKey(to='misago_threads.Thread')),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+                (
+                    'category', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_categories.Category',
+                    )
+                ),
+                (
+                    'thread', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to='misago_threads.Thread',
+                    )
+                ),
+                (
+                    'user', models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    )
+                ),
             ],
             options={},
             bases=(models.Model, ),

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

@@ -33,7 +33,7 @@ def upload_to(instance, filename):
 @python_2_unicode_compatible
 class Attachment(models.Model):
     secret = models.CharField(max_length=64)
-    filetype = models.ForeignKey('AttachmentType')
+    filetype = models.ForeignKey('AttachmentType', on_delete=models.CASCADE)
     post = models.ForeignKey('Post', blank=True, null=True, on_delete=models.SET_NULL)
 
     uploaded_on = models.DateTimeField(default=timezone.now, db_index=True)

+ 8 - 2
misago/threads/models/poll.py

@@ -8,8 +8,14 @@ from django.utils import timezone
 
 
 class Poll(models.Model):
-    category = models.ForeignKey('misago_categories.Category')
-    thread = models.OneToOneField('misago_threads.Thread')
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
+    thread = models.OneToOneField(
+        'misago_threads.Thread',
+        on_delete=models.CASCADE,
+    )
     poster = models.ForeignKey(
         settings.AUTH_USER_MODEL,
         blank=True,

+ 12 - 3
misago/threads/models/pollvote.py

@@ -4,9 +4,18 @@ from django.utils import timezone
 
 
 class PollVote(models.Model):
-    category = models.ForeignKey('misago_categories.Category')
-    thread = models.ForeignKey('misago_threads.Thread')
-    poll = models.ForeignKey('misago_threads.Poll')
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
+    thread = models.ForeignKey(
+        'misago_threads.Thread',
+        on_delete=models.CASCADE,
+    )
+    poll = models.ForeignKey(
+        'misago_threads.Poll',
+        on_delete=models.CASCADE,
+    )
     voter = models.ForeignKey(
         settings.AUTH_USER_MODEL,
         blank=True,

+ 8 - 2
misago/threads/models/post.py

@@ -17,8 +17,14 @@ from misago.threads.filtersearch import filter_search
 
 @python_2_unicode_compatible
 class Post(models.Model):
-    category = models.ForeignKey('misago_categories.Category')
-    thread = models.ForeignKey('misago_threads.Thread')
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
+    thread = models.ForeignKey(
+        'misago_threads.Thread',
+        on_delete=models.CASCADE,
+    )
     poster = models.ForeignKey(
         settings.AUTH_USER_MODEL,
         blank=True,

+ 7 - 3
misago/threads/models/postedit.py

@@ -6,9 +6,13 @@ from django.utils import timezone
 
 
 class PostEdit(models.Model):
-    category = models.ForeignKey('misago_categories.Category')
-    thread = models.ForeignKey('misago_threads.Thread')
-    post = models.ForeignKey('misago_threads.Post', related_name='edits_record')
+    category = models.ForeignKey('misago_categories.Category', on_delete=models.CASCADE)
+    thread = models.ForeignKey('misago_threads.Thread', on_delete=models.CASCADE)
+    post = models.ForeignKey(
+        'misago_threads.Post',
+        related_name='edits_record',
+        on_delete=models.CASCADE,
+    )
 
     edited_on = models.DateTimeField(default=timezone.now)
 

+ 12 - 3
misago/threads/models/postlike.py

@@ -4,9 +4,18 @@ from django.utils import timezone
 
 
 class PostLike(models.Model):
-    category = models.ForeignKey('misago_categories.Category')
-    thread = models.ForeignKey('misago_threads.Thread')
-    post = models.ForeignKey('misago_threads.Post')
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
+    thread = models.ForeignKey(
+        'misago_threads.Thread',
+        on_delete=models.CASCADE,
+    )
+    post = models.ForeignKey(
+        'misago_threads.Post',
+        on_delete=models.CASCADE,
+    )
 
     liker = models.ForeignKey(
         settings.AUTH_USER_MODEL,

+ 9 - 3
misago/threads/models/subscription.py

@@ -5,9 +5,15 @@ from misago.conf import settings
 
 
 class Subscription(models.Model):
-    user = models.ForeignKey(settings.AUTH_USER_MODEL)
-    thread = models.ForeignKey('Thread')
-    category = models.ForeignKey('misago_categories.Category')
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.CASCADE,
+    )
+    thread = models.ForeignKey('Thread', on_delete=models.CASCADE)
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
 
     last_read_on = models.DateTimeField(default=timezone.now)
     send_email = models.BooleanField(default=False)

+ 4 - 1
misago/threads/models/thread.py

@@ -19,7 +19,10 @@ class Thread(models.Model):
         (WEIGHT_GLOBAL, _("Pin thread globally")),
     ]
 
-    category = models.ForeignKey('misago_categories.Category')
+    category = models.ForeignKey(
+        'misago_categories.Category',
+        on_delete=models.CASCADE,
+    )
     title = models.CharField(max_length=255)
     slug = models.CharField(max_length=255)
     replies = models.PositiveIntegerField(default=0, db_index=True)

+ 8 - 2
misago/threads/models/threadparticipant.py

@@ -23,8 +23,14 @@ class ThreadParticipantManager(models.Manager):
 
 
 class ThreadParticipant(models.Model):
-    thread = models.ForeignKey('misago_threads.Thread')
-    user = models.ForeignKey(settings.AUTH_USER_MODEL)
+    thread = models.ForeignKey(
+        'misago_threads.Thread',
+        on_delete=models.CASCADE,
+    )
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.CASCADE,
+    )
     is_owner = models.BooleanField(default=False)
 
     objects = ThreadParticipantManager()

+ 7 - 2
misago/urls.py

@@ -5,6 +5,8 @@ from misago.conf import settings
 from misago.core.views import forum_index
 
 
+app_name = 'misago'
+
 # Register Misago Apps
 urlpatterns = [
     url(r'^', include('misago.legal.urls')),
@@ -26,6 +28,7 @@ urlpatterns = [
     url(r'^$', forum_index, name='index'),
 ]
 
+
 # Register API
 apipatterns = [
     url(r'^', include('misago.categories.urls.api')),
@@ -36,9 +39,10 @@ apipatterns = [
 ]
 
 urlpatterns += [
-    url(r'^api/', include(apipatterns, namespace='api')),
+    url(r'^api/', include((apipatterns, 'api'), namespace='api')),
 ]
 
+
 # Register Misago ACP
 if settings.MISAGO_ADMIN_PATH:
     # Admin patterns recognised by Misago
@@ -48,9 +52,10 @@ if settings.MISAGO_ADMIN_PATH:
 
     admin_prefix = r'^%s/' % settings.MISAGO_ADMIN_PATH
     urlpatterns += [
-        url(admin_prefix, include(adminpatterns, namespace='admin')),
+        url(admin_prefix, include((adminpatterns, 'admin'), namespace='admin')),
     ]
 
+
 # Make error pages accessible casually in DEBUG
 if settings.DEBUG:
     from misago.core import errorpages

+ 14 - 3
misago/users/migrations/0001_initial.py

@@ -155,6 +155,7 @@ class Migration(migrations.Migration):
                 (
                     'user', models.OneToOneField(
                         related_name='online_tracker',
+                        on_delete=django.db.models.deletion.CASCADE,
                         primary_key=True,
                         serialize=False,
                         to=settings.AUTH_USER_MODEL
@@ -186,8 +187,11 @@ class Migration(migrations.Migration):
                     )
                 ),
                 (
-                    'user',
-                    models.ForeignKey(related_name='namechanges', to=settings.AUTH_USER_MODEL)
+                    'user', models.ForeignKey(
+                        related_name='namechanges',
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    )
                 ),
             ],
             options={
@@ -250,7 +254,13 @@ class Migration(migrations.Migration):
                         verbose_name='ID', serialize=False, auto_created=True, primary_key=True
                     )
                 ),
-                ('user', models.ForeignKey(related_name='+', to=settings.AUTH_USER_MODEL)),
+                (
+                    'user', models.ForeignKey(
+                        related_name='+',
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    )
+                ),
                 ('score', models.PositiveIntegerField(default=0, db_index=True)),
             ],
             options={},
@@ -331,6 +341,7 @@ class Migration(migrations.Migration):
                 (
                     'user', models.OneToOneField(
                         related_name='ban_cache',
+                        on_delete=django.db.models.deletion.CASCADE,
                         primary_key=True,
                         serialize=False,
                         to=settings.AUTH_USER_MODEL

+ 5 - 1
misago/users/models/activityranking.py

@@ -3,5 +3,9 @@ from django.db import models
 
 
 class ActivityRanking(models.Model):
-    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+')
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        related_name='+',
+        on_delete=models.CASCADE,
+    )
     score = models.PositiveIntegerField(default=0, db_index=True)

+ 7 - 1
misago/users/models/ban.py

@@ -124,8 +124,14 @@ class BanCache(models.Model):
         settings.AUTH_USER_MODEL,
         primary_key=True,
         related_name='ban_cache',
+        on_delete=models.CASCADE,
+    )
+    ban = models.ForeignKey(
+        Ban,
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
     )
-    ban = models.ForeignKey(Ban, null=True, blank=True, on_delete=models.SET_NULL)
     bans_version = models.PositiveIntegerField(default=0)
     user_message = models.TextField(null=True, blank=True)
     staff_message = models.TextField(null=True, blank=True)

+ 12 - 2
misago/users/models/user.py

@@ -174,7 +174,12 @@ class User(AbstractBaseUser, PermissionsMixin):
     last_ip = models.GenericIPAddressField(null=True, blank=True)
     is_hiding_presence = models.BooleanField(default=False)
 
-    rank = models.ForeignKey('Rank', null=True, blank=True, on_delete=models.deletion.PROTECT)
+    rank = models.ForeignKey(
+        'Rank',
+        null=True,
+        blank=True,
+        on_delete=models.deletion.PROTECT,
+    )
     title = models.CharField(max_length=255, null=True, blank=True)
     requires_activation = models.PositiveIntegerField(default=ACTIVATION_NONE)
 
@@ -425,6 +430,7 @@ class Online(models.Model):
         settings.AUTH_USER_MODEL,
         primary_key=True,
         related_name='online_tracker',
+        on_delete=models.CASCADE,
     )
     current_ip = models.GenericIPAddressField()
     last_click = models.DateTimeField(default=timezone.now)
@@ -437,7 +443,11 @@ class Online(models.Model):
 
 
 class UsernameChange(models.Model):
-    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='namechanges')
+    user = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        related_name='namechanges',
+        on_delete=models.CASCADE,
+    )
     changed_by = models.ForeignKey(
         settings.AUTH_USER_MODEL,
         null=True,

+ 5 - 1
misago/users/views/auth.py

@@ -15,7 +15,11 @@ def login(request):
     if request.method == 'POST':
         redirect_to = request.POST.get('redirect_to')
         if redirect_to:
-            is_redirect_safe = is_safe_url(url=redirect_to, host=request.get_host())
+            is_redirect_safe = is_safe_url(
+                url=redirect_to,
+                allowed_hosts={request.get_host()},
+                require_https=request.is_secure(),
+            )
             if is_redirect_safe:
                 redirect_to_path = urlparse(redirect_to).path
                 return redirect(redirect_to_path)