Browse Source

Replace MISAGO_ADDRESS with forum_address (#1236)

* Add forum_address setting to DB

* Add forum_address setting

* Remove SITE_ADDRESS from most of places

* Remove MISAGO_ADDRESS from codebase

* Remove unused context variables

* Fix misago.conf tests

* Fix errors in misago.core

* Fix misago.threads tests

* Fix misago.admin tests
Rafał Pitoń 6 years ago
parent
commit
d614318805
41 changed files with 293 additions and 255 deletions
  1. 1 9
      devproject/settings.py
  2. 0 3
      devproject/test_settings.py
  3. 30 21
      misago/admin/tests/test_admin_system_checks.py
  4. 4 4
      misago/admin/views/index.py
  5. 18 0
      misago/conf/admin/forms.py
  6. 2 2
      misago/conf/admin/tests/test_change_settings_form.py
  7. 43 14
      misago/conf/admin/tests/test_image_setting_handling.py
  8. 4 2
      misago/conf/admin/views.py
  9. 0 5
      misago/conf/defaults.py
  10. 6 1
      misago/conf/migrations/0004_create_settings.py
  11. 4 0
      misago/conf/tests/test_hydrators.py
  12. 2 16
      misago/core/context_processors.py
  13. 6 5
      misago/core/mail.py
  14. 5 6
      misago/core/templatetags/misago_absoluteurl.py
  15. 23 0
      misago/core/tests/test_absoluteurl_templatetag.py
  16. 0 28
      misago/core/tests/test_context_processors.py
  17. 8 6
      misago/core/tests/test_mail.py
  18. 1 31
      misago/core/tests/test_templatetags.py
  19. 2 2
      misago/core/utils.py
  20. 1 0
      misago/templates/misago/admin/conf/general_settings.html
  21. 43 25
      misago/templates/misago/admin/dashboard/checks.html
  22. 2 2
      misago/templates/misago/base.html
  23. 2 2
      misago/templates/misago/categories/base.html
  24. 1 1
      misago/templates/misago/emails/base.html
  25. 1 1
      misago/templates/misago/emails/base.txt
  26. 2 2
      misago/templates/misago/privacy_policy.html
  27. 2 2
      misago/templates/misago/profile/base.html
  28. 2 2
      misago/templates/misago/search.html
  29. 2 2
      misago/templates/misago/terms_of_service.html
  30. 2 2
      misago/templates/misago/thread/thread.html
  31. 3 3
      misago/templates/misago/threadslist/base.html
  32. 2 2
      misago/templates/misago/threadslist/category.html
  33. 2 2
      misago/templates/misago/threadslist/threads.html
  34. 2 2
      misago/templates/misago/userslists/active_posters.html
  35. 2 2
      misago/templates/misago/userslists/rank.html
  36. 11 8
      misago/threads/tests/test_emailnotification_middleware.py
  37. 11 9
      misago/threads/tests/test_privatethread_start_api.py
  38. 13 8
      misago/users/tests/test_options_views.py
  39. 5 6
      misago/users/tests/test_prepareuserdatadownloads.py
  40. 11 8
      misago/users/tests/test_user_changeemail_api.py
  41. 12 9
      misago/users/tests/test_user_changepassword_api.py

+ 1 - 9
devproject/settings.py

@@ -269,7 +269,7 @@ TEMPLATES = [
                 "misago.acl.context_processors.user_acl",
                 "misago.conf.context_processors.conf",
                 "misago.core.context_processors.misago_version",
-                "misago.core.context_processors.site_address",
+                "misago.core.context_processors.request_path",
                 "misago.core.context_processors.momentjs_locale",
                 "misago.search.context_processors.search_providers",
                 "misago.themes.context_processors.theme",
@@ -342,14 +342,6 @@ CELERY_WORKER_MAX_TASKS_PER_CHILD = 10
 # Misago specific settings
 # https://misago.readthedocs.io/en/latest/developers/settings.html
 
-# Complete HTTP address to your Misago site homepage. Misago relies on this address to create
-# links in e-mails that are sent to site users.
-# On Misago admin panel home page you will find a message telling you if you have entered the
-# correct value, or what value is correct in case you've didn't.
-
-MISAGO_ADDRESS = "http://my-misago-site.com/"
-
-
 # On dev instance, generate only three sizes of avatars instead of default 6 sizes.
 
 MISAGO_AVATARS_SIZES = [400, 200, 100]

+ 0 - 3
devproject/test_settings.py

@@ -43,9 +43,6 @@ AUTH_PASSWORD_VALIDATORS = [
     {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
 ]
 
-# Default misago address to test address
-MISAGO_ADDRESS = "http://testserver/"
-
 # Use english search config
 MISAGO_SEARCH_CONFIG = "english"
 

+ 30 - 21
misago/admin/tests/test_admin_system_checks.py

@@ -1,11 +1,13 @@
 from datetime import timedelta
 from unittest.mock import Mock
 
+import pytest
 from django.contrib.auth import get_user_model
 from django.test import override_settings
 from django.urls import reverse
 from django.utils import timezone
 
+from ...conf.test import override_dynamic_settings
 from ...test import assert_contains
 from ...users.datadownloads import request_user_data_download
 from ...users.models import DataDownload
@@ -14,9 +16,9 @@ from ..views.index import (
     check_cache,
     check_data_downloads,
     check_debug_status,
+    check_forum_address,
     check_https,
     check_inactive_users,
-    check_misago_address,
 )
 
 User = get_user_model()
@@ -111,58 +113,65 @@ def test_warning_about_unprocessed_data_downloads_is_displayed_on_checks_list(
 class RequestMock:
     absolute_uri = "https://misago-project.org/somewhere/"
 
+    def __init__(self, settings):
+        self.settings = settings
+
     def build_absolute_uri(self, location):
         assert location == "/"
         return self.absolute_uri
 
 
-request = RequestMock()
+@pytest.fixture
+def request_mock(dynamic_settings):
+    return RequestMock(dynamic_settings)
+
+
 incorrect_address = "http://somewhere.com"
-correct_address = request.absolute_uri
+correct_address = RequestMock.absolute_uri
 
 
-@override_settings(MISAGO_ADDRESS=None)
-def test_misago_address_check_handles_setting_not_configured():
-    result = check_misago_address(request)
+@override_dynamic_settings(forum_address=None)
+def test_forum_address_check_handles_setting_not_configured(request_mock):
+    result = check_forum_address(request_mock)
     assert result == {
         "is_ok": False,
         "set_address": None,
-        "correct_address": request.absolute_uri,
+        "correct_address": request_mock.absolute_uri,
     }
 
 
-@override_settings(MISAGO_ADDRESS=incorrect_address)
-def test_misago_address_check_detects_invalid_address_configuration():
-    result = check_misago_address(request)
+@override_dynamic_settings(forum_address=incorrect_address)
+def test_forum_address_check_detects_invalid_address_configuration(request_mock):
+    result = check_forum_address(request_mock)
     assert result == {
         "is_ok": False,
         "set_address": incorrect_address,
-        "correct_address": request.absolute_uri,
+        "correct_address": request_mock.absolute_uri,
     }
 
 
-@override_settings(MISAGO_ADDRESS=correct_address)
-def test_misago_address_check_detects_valid_address_configuration():
-    result = check_misago_address(request)
+@override_dynamic_settings(forum_address=correct_address)
+def test_forum_address_check_detects_valid_address_configuration(request_mock):
+    result = check_forum_address(request_mock)
     assert result == {
         "is_ok": True,
         "set_address": correct_address,
-        "correct_address": request.absolute_uri,
+        "correct_address": request_mock.absolute_uri,
     }
 
 
-@override_settings(MISAGO_ADDRESS=None)
-def test_warning_about_unset_misago_address_is_displayed_on_checks_list(admin_client):
+@override_dynamic_settings(forum_address=None)
+def test_warning_about_unset_forum_address_is_displayed_on_checks_list(admin_client):
     response = admin_client.get(admin_link)
-    assert_contains(response, "MISAGO_ADDRESS")
+    assert_contains(response, "address")
 
 
-@override_settings(MISAGO_ADDRESS=incorrect_address)
-def test_warning_about_incorrect_misago_address_is_displayed_on_checks_list(
+@override_dynamic_settings(forum_address=incorrect_address)
+def test_warning_about_incorrect_forum_address_is_displayed_on_checks_list(
     admin_client
 ):
     response = admin_client.get(admin_link)
-    assert_contains(response, "MISAGO_ADDRESS")
+    assert_contains(response, "address")
 
 
 @override_settings(DEBUG=False)

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

@@ -1,11 +1,11 @@
 from datetime import timedelta
 
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core.cache import cache
 from django.utils import timezone
 
 from . import render
-from ...conf import settings
 from ...threads.models import Post, Thread, Attachment
 from ...users.models import DataDownload
 
@@ -17,7 +17,7 @@ User = get_user_model()
 def admin_index(request):
     totals = count_db_items()
     checks = {
-        "address": check_misago_address(request),
+        "address": check_forum_address(request),
         "cache": check_cache(),
         "data_downloads": check_data_downloads(),
         "debug": check_debug_status(),
@@ -45,8 +45,8 @@ def check_https(request):
     return {"is_ok": request.is_secure()}
 
 
-def check_misago_address(request):
-    set_address = settings.MISAGO_ADDRESS
+def check_forum_address(request):
+    set_address = request.settings.forum_address
     correct_address = request.build_absolute_uri("/")
 
     return {

+ 18 - 0
misago/conf/admin/forms.py

@@ -8,6 +8,10 @@ from ..cache import clear_settings_cache
 class ChangeSettingsForm(forms.Form):
     settings = []
 
+    def __init__(self, *args, **kwargs):
+        self.request = kwargs.pop("request")
+        super().__init__(*args, **kwargs)
+
     def save(self, settings):
         self.save_settings(settings)
         self.clear_cache()
@@ -125,6 +129,7 @@ class ChangeCaptchaSettingsForm(ChangeSettingsForm):
 class ChangeGeneralSettingsForm(ChangeSettingsForm):
     settings = [
         "forum_name",
+        "forum_address",
         "index_header",
         "index_title",
         "index_meta_description",
@@ -136,6 +141,7 @@ class ChangeGeneralSettingsForm(ChangeSettingsForm):
     ]
 
     forum_name = forms.CharField(label=_("Forum name"), min_length=2, max_length=255)
+    forum_address = forms.URLField(label=_("Forum address"), max_length=255)
 
     index_header = forms.CharField(
         label=_("Header text"),
@@ -196,6 +202,18 @@ class ChangeGeneralSettingsForm(ChangeSettingsForm):
         required=False,
     )
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        address = self.request.build_absolute_uri("/")
+        self["forum_address"].help_text = _(
+            "Misago uses this setting to build links in e-mails sent to site "
+            'users. Address under which site is running appears to be "%(address)s".'
+        ) % {"address": address}
+
+    def clean_forum_address(self):
+        return self.cleaned_data["forum_address"].lower()
+
 
 class ChangeThreadsSettingsForm(ChangeSettingsForm):
     settings = [

+ 2 - 2
misago/conf/admin/tests/test_change_settings_form.py

@@ -12,7 +12,7 @@ class Form(ChangeSettingsForm):
 
 
 def test_form_updates_setting_on_save(setting):
-    form = Form({"forum_name": "New Value"})
+    form = Form({"forum_name": "New Value"}, request=None)
     assert form.is_valid()
     form.save({"forum_name": setting})
 
@@ -22,6 +22,6 @@ def test_form_updates_setting_on_save(setting):
 
 def test_form_invalidates_settings_cache_on_save(setting):
     with assert_invalidates_cache(SETTINGS_CACHE):
-        form = Form({"forum_name": "New Value"})
+        form = Form({"forum_name": "New Value"}, request=None)
         assert form.is_valid()
         form.save({"forum_name": setting})

+ 43 - 14
misago/conf/admin/tests/test_image_setting_handling.py

@@ -25,7 +25,7 @@ def test_image_setting_can_be_set(admin_client, setting):
     with open(IMAGE, "rb") as image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": image},
+            {"forum_name": "Misago", "forum_address": "http://test.com", "logo": image},
         )
 
     setting.refresh_from_db()
@@ -36,7 +36,7 @@ def test_setting_image_also_sets_its_dimensions(admin_client, setting):
     with open(IMAGE, "rb") as image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": image},
+            {"forum_name": "Misago", "forum_address": "http://test.com", "logo": image},
         )
 
     setting.refresh_from_db()
@@ -48,7 +48,7 @@ def test_setting_image_filename_is_prefixed_with_setting_name(admin_client, sett
     with open(IMAGE, "rb") as image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": image},
+            {"forum_name": "Misago", "forum_address": "http://test.com", "logo": image},
         )
 
     setting.refresh_from_db()
@@ -59,7 +59,7 @@ def test_setting_image_filename_is_hashed(admin_client, setting):
     with open(IMAGE, "rb") as image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": image},
+            {"forum_name": "Misago", "forum_address": "http://test.com", "logo": image},
         )
 
     setting.refresh_from_db()
@@ -70,7 +70,11 @@ def test_image_setting_rejects_non_image_file(admin_client, setting):
     with open(OTHER_FILE, "rb") as not_image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     setting.refresh_from_db()
@@ -82,7 +86,11 @@ def setting_with_value(admin_client, setting):
     with open(IMAGE, "rb") as not_image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     setting.refresh_from_db()
@@ -98,7 +106,11 @@ def test_invalid_file_is_not_set_as_value(admin_client, setting):
     with open(OTHER_FILE, "rb") as not_image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     setting.refresh_from_db()
@@ -111,7 +123,11 @@ def test_uploading_invalid_file_doesnt_remove_already_set_image(
     with open(OTHER_FILE, "rb") as not_image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     setting_with_value.refresh_from_db()
@@ -124,7 +140,11 @@ def test_set_image_is_still_rendered_when_invalid_file_was_uploaded(
     with open(OTHER_FILE, "rb") as not_image:
         response = admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     assert_contains(response, setting_with_value.image.url)
@@ -136,7 +156,11 @@ def test_uploading_new_image_replaces_already_set_image(
     with open(OTHER_IMAGE, "rb") as not_image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     setting_with_value.refresh_from_db()
@@ -147,7 +171,11 @@ def test_uploading_new_image_deletes_image_file(admin_client, setting_with_value
     with open(OTHER_IMAGE, "rb") as not_image:
         admin_client.post(
             reverse("misago:admin:settings:general:index"),
-            {"forum_name": "Misago", "logo": not_image},
+            {
+                "forum_name": "Misago",
+                "forum_address": "http://test.com",
+                "logo": not_image,
+            },
         )
 
     assert not os.path.exists(setting_with_value.image.path)
@@ -157,7 +185,8 @@ def test_omitting_setting_value_doesnt_remove_already_set_image(
     admin_client, setting_with_value
 ):
     admin_client.post(
-        reverse("misago:admin:settings:general:index"), {"forum_name": "Misago"}
+        reverse("misago:admin:settings:general:index"),
+        {"forum_name": "Misago", "forum_address": "http://test.com"},
     )
 
     setting_with_value.refresh_from_db()
@@ -167,7 +196,7 @@ def test_omitting_setting_value_doesnt_remove_already_set_image(
 def test_set_image_can_be_deleted_by_extra_option(admin_client, setting_with_value):
     admin_client.post(
         reverse("misago:admin:settings:general:index"),
-        {"forum_name": "Misago", "logo_delete": 1},
+        {"forum_name": "Misago", "forum_address": "http://test.com", "logo_delete": 1},
     )
 
     setting_with_value.refresh_from_db()
@@ -179,7 +208,7 @@ def test_using_image_deletion_option_deletes_image_file(
 ):
     admin_client.post(
         reverse("misago:admin:settings:general:index"),
-        {"forum_name": "Misago", "logo_delete": 1},
+        {"forum_name": "Misago", "forum_address": "http://test.com", "logo_delete": 1},
     )
 
     assert not os.path.exists(setting_with_value.image.path)

+ 4 - 2
misago/conf/admin/views.py

@@ -28,9 +28,11 @@ class ChangeSettingsView(AdminView):
     def dispatch(self, request, *args, **kwargs):
         settings = self.get_settings(self.form_class.settings)
         initial = self.get_initial_form_data(settings)
-        form = self.form_class(initial=initial)
+        form = self.form_class(request=request, initial=initial)
         if request.method == "POST":
-            form = self.form_class(request.POST, request.FILES, initial=initial)
+            form = self.form_class(
+                request.POST, request.FILES, request=request, initial=initial
+            )
             if form.is_valid():
                 form.save(settings)
                 messages.success(request, _("Settings have been saved."))

+ 0 - 5
misago/conf/defaults.py

@@ -7,11 +7,6 @@ If you rely on any of those in your code, make sure you use `misago.conf.setting
 instead of Django's `django.conf.settings`.
 """
 
-# Complete HTTP address of your Misago installation
-
-MISAGO_ADDRESS = None
-
-
 # Permissions system extensions
 # https://misago.readthedocs.io/en/latest/developers/acls.html#extending-permissions-system
 

+ 6 - 1
misago/conf/migrations/0004_create_settings.py

@@ -1,10 +1,10 @@
 # Generated by Django 2.2.1 on 2019-05-19 00:16
 
+from django.conf import settings
 from django.db import migrations
 
 from ..hydrators import dehydrate_value
 
-
 default_settings = [
     {"setting": "account_activation", "dry_value": "none", "is_public": True},
     {"setting": "allow_custom_avatars", "python_type": "bool", "dry_value": True},
@@ -18,6 +18,11 @@ default_settings = [
     {"setting": "default_avatar", "dry_value": "gravatar"},
     {"setting": "default_gravatar_fallback", "dry_value": "dynamic"},
     {"setting": "email_footer"},
+    {
+        "setting": "forum_address",
+        "dry_value": getattr(settings, "MISAGO_ADDRESS", None),
+        "is_public": True,
+    },
     {"setting": "forum_footnote", "is_public": True},
     {"setting": "forum_name", "dry_value": "Misago", "is_public": True},
     {"setting": "index_header", "is_public": True},

+ 4 - 0
misago/conf/tests/test_hydrators.py

@@ -13,6 +13,10 @@ def test_int_value_is_dehydrated_to_string():
     assert dehydrate_value("string", 123) == "123"
 
 
+def test_empty_string_value_is_hydrated_to_empty_string():
+    assert hydrate_value("string", None) == ""
+
+
 def test_bool_false_value_can_be_dehydrated_and_hydrated_back():
     assert hydrate_value("bool", dehydrate_value("bool", False)) is False
 

+ 2 - 16
misago/core/context_processors.py

@@ -8,22 +8,8 @@ def misago_version(request):
     return {"MISAGO_VERSION": __version__}
 
 
-def site_address(request):
-    if request.is_secure():
-        site_protocol = "https"
-        address_template = "https://%s"
-    else:
-        site_protocol = "http"
-        address_template = "http://%s"
-
-    host = request.get_host()
-
-    return {
-        "SITE_PROTOCOL": site_protocol,
-        "SITE_HOST": host,
-        "SITE_ADDRESS": address_template % host,
-        "REQUEST_PATH": request.path,
-    }
+def request_path(request):
+    return {"request_path": request.path}
 
 
 def current_link(request):

+ 6 - 5
misago/core/mail.py

@@ -8,21 +8,22 @@ from .utils import get_host_from_address
 
 def build_mail(recipient, subject, template, sender=None, context=None):
     context = context.copy() if context else {}
+    if not context.get("settings"):
+        raise ValueError("settings key is missing from context")
+
+    forum_address = context["settings"].forum_address
+
     context.update(
         {
-            "SITE_ADDRESS": settings.MISAGO_ADDRESS,
-            "SITE_HOST": get_host_from_address(settings.MISAGO_ADDRESS),
             "LANGUAGE_CODE": get_language()[:2],
             "LOGIN_URL": settings.LOGIN_URL,
+            "forum_host": get_host_from_address(forum_address),
             "user": recipient,
             "sender": sender,
             "subject": subject,
         }
     )
 
-    if not context.get("settings"):
-        raise ValueError("settings key is missing from context")
-
     message_plain = render_to_string("%s.txt" % template, context)
     message_html = render_to_string("%s.html" % template, context)
 

+ 5 - 6
misago/core/templatetags/misago_absoluteurl.py

@@ -1,17 +1,16 @@
 from django import template
 from django.urls import NoReverseMatch, reverse
 
-from ...conf import settings
-
 register = template.Library()
 
 
-@register.simple_tag
-def absoluteurl(url_or_name, *args, **kwargs):
-    if not settings.MISAGO_ADDRESS:
+@register.simple_tag(takes_context=True)
+def absoluteurl(context, url_or_name, *args, **kwargs):
+    address = context["settings"].forum_address
+    if not address:
         return None
 
-    absolute_url_prefix = settings.MISAGO_ADDRESS.rstrip("/")
+    absolute_url_prefix = address.rstrip("/")
 
     try:
         url_or_name = reverse(url_or_name, args=args, kwargs=kwargs)

+ 23 - 0
misago/core/tests/test_absoluteurl_templatetag.py

@@ -0,0 +1,23 @@
+from unittest.mock import Mock
+
+import pytest
+
+from ..templatetags.misago_absoluteurl import absoluteurl
+
+
+@pytest.fixture
+def context():
+    return {"settings": Mock(forum_address="http://test.com/")}
+
+
+def test_path_is_prefixed_with_forum_address(context):
+    assert absoluteurl(context, "/path/") == "http://test.com/path/"
+
+
+def test_link_is_reversed_and_prefixed_with_forum_address(context):
+    assert absoluteurl(context, "misago:index") == "http://test.com/"
+
+
+def test_absolute_url_is_not_changed(context):
+    url = "https://github.com/rafalp/Misago/issues/1067"
+    assert absoluteurl(context, url) == url

+ 0 - 28
misago/core/tests/test_context_processors.py

@@ -49,34 +49,6 @@ class MomentjsLocaleTests(TestCase):
             )
 
 
-class SiteAddressTests(TestCase):
-    def test_site_address_for_http(self):
-        """Correct SITE_ADDRESS set for HTTP request"""
-        mock_request = MockRequest(False, "somewhere.com")
-        self.assertEqual(
-            context_processors.site_address(mock_request),
-            {
-                "REQUEST_PATH": "/",
-                "SITE_ADDRESS": "http://somewhere.com",
-                "SITE_HOST": "somewhere.com",
-                "SITE_PROTOCOL": "http",
-            },
-        )
-
-    def test_site_address_for_https(self):
-        """Correct SITE_ADDRESS set for HTTPS request"""
-        mock_request = MockRequest(True, "somewhere.com")
-        self.assertEqual(
-            context_processors.site_address(mock_request),
-            {
-                "REQUEST_PATH": "/",
-                "SITE_ADDRESS": "https://somewhere.com",
-                "SITE_HOST": "somewhere.com",
-                "SITE_PROTOCOL": "https",
-            },
-        )
-
-
 class FrontendContextTests(TestCase):
     def test_frontend_context(self):
         """frontend_context is available in templates"""

+ 8 - 6
misago/core/tests/test_mail.py

@@ -4,6 +4,7 @@ from django.urls import reverse
 
 from ...cache.versions import get_cache_versions
 from ...conf.dynamicsettings import DynamicSettings
+from ...conf.test import override_dynamic_settings
 from ...users.test import create_test_user
 from ..mail import build_mail, mail_user, mail_users
 
@@ -28,12 +29,13 @@ class MailTests(TestCase):
         cache_versions = get_cache_versions()
         settings = DynamicSettings(cache_versions)
 
-        mail_user(
-            user,
-            "Misago Test Mail",
-            "misago/emails/base",
-            context={"settings": settings},
-        )
+        with override_dynamic_settings(forum_address="http://test.com"):
+            mail_user(
+                user,
+                "Misago Test Mail",
+                "misago/emails/base",
+                context={"settings": settings},
+            )
 
         self.assertEqual(mail.outbox[0].subject, "Misago Test Mail")
 

+ 1 - 31
misago/core/tests/test_templatetags.py

@@ -1,38 +1,8 @@
 from django.template import Context, Template
-from django.test import TestCase, override_settings
+from django.test import TestCase
 
-from ..templatetags.misago_absoluteurl import absoluteurl
 from ..templatetags.misago_batch import batch, batchnonefilled
 
-TEST_ADDRESS = "https://testsite.com/"
-
-
-class AbsoluteUrlTests(TestCase):
-    @override_settings(MISAGO_ADDRESS=None)
-    def test_address_is_none(self):
-        """template tag returns null if address setting is not filled"""
-        result = absoluteurl("misago:index")
-        self.assertIsNone(result)
-
-    @override_settings(MISAGO_ADDRESS=TEST_ADDRESS)
-    def test_prefix_url(self):
-        """template tag prefixes already reversed url"""
-        result = absoluteurl("/")
-        self.assertEqual(result, TEST_ADDRESS)
-
-    @override_settings(MISAGO_ADDRESS=TEST_ADDRESS)
-    def test_prefix_url_name(self):
-        """template tag reverses url name and prefixes it"""
-        result = absoluteurl("misago:index")
-        self.assertEqual(result, TEST_ADDRESS)
-
-    @override_settings(MISAGO_ADDRESS=TEST_ADDRESS)
-    def test_dont_change_absolute_url(self):
-        """template tag doesn't change already absolute urls"""
-        absolute_url = "https://github.com/rafalp/Misago/issues/1067"
-        result = absoluteurl(absolute_url)
-        self.assertEqual(result, absolute_url)
-
 
 class CaptureTests(TestCase):
     def setUp(self):

+ 2 - 2
misago/core/utils.py

@@ -163,8 +163,8 @@ def get_host_from_address(address):
         address = address[8:]
     if address.lower().startswith("http://"):
         address = address[7:]
-    if address[0] == "/":
-        address = address.lstrip("/")
+
+    address = address.lstrip("/")
     if "/" in address:
         address = address.split("/")[0] or address
     if ":" in address:

+ 1 - 0
misago/templates/misago/admin/conf/general_settings.html

@@ -8,6 +8,7 @@
     <legend>{% trans "Site details" %}</legend>
 
     {% form_row form.forum_name %}
+    {% form_row form.forum_address %}
 
   </fieldset>
 </div>

+ 43 - 25
misago/templates/misago/admin/dashboard/checks.html

@@ -38,39 +38,57 @@
   {% endif %}
   {% if not checks.address.set_address %}
     <div class="card-body border-top">
-      <div class="media media-admin-check">
-        <div class="media-check-icon media-check-icon-danger">
-          <span class="fas fa-times"></span>
+      <div class="row">
+        <div class="col">
+          <div class="media media-admin-check">
+            <div class="media-check-icon media-check-icon-danger">
+              <span class="fas fa-times"></span>
+            </div>
+            <div class="media-body">
+              <h5>{% trans "Forum address is not configured." %}</h5>
+              {% trans "Misago uses this setting to build links in e-mails sent to site users." %}
+            </div>
+          </div>
         </div>
-        <div class="media-body">
-          <h5>{% trans "The settings.py is missing MISAGO_ADDRESS value." %}</h5>
-          {% trans "Misago uses this setting to build correct links in e-mails sent to site users." %}
+        <div class="col-auto">
+          <a href="{% url 'misago:admin:settings:general:index' %}">
+            <span class="fas fa-external-link-alt"></span>
+          </a>
         </div>
       </div>
     </div>
   {% elif not checks.address.is_ok %}
     <div class="card-body border-top">
-      <div class="media media-admin-check">
-        <div class="media-check-icon media-check-icon-warning">
-          <span class="fas fa-question"></span>
-        </div>
-        <div class="media-body">
-          <h5>{% trans "The settings.py value for MISAGO_ADDRESS appears to be incorrect." %}</h5>
-          <div class="d-block">
-            {% capture trimmed as set_address %}
-              <code>{{ checks.address.set_address }}</code>
-            {% endcapture %}
-            {% capture trimmed as correct_address %}
-              <code>{{ checks.address.correct_address }}</code>
-            {% endcapture %}
-            {% blocktrans trimmed with configured_address=set_address|safe correct_address=correct_address|safe %}
-              Your MISAGO_ADDRESS is set to {{ configured_address }} while correct value appears to be {{ correct_address }}.
-            {% endblocktrans %}
-          </div>
-          <div>
-            {% trans "Misago uses this setting to build correct links in e-mails sent to site users." %}
+      <div class="row">
+        <div class="col">
+          <div class="media media-admin-check">
+            <div class="media-check-icon media-check-icon-warning">
+              <span class="fas fa-question"></span>
+            </div>
+            <div class="media-body">
+              <h5>{% trans "Configured forum address appears to be incorrect." %}</h5>
+              <div class="d-block">
+                {% capture trimmed as set_address %}
+                  <code>{{ checks.address.set_address }}</code>
+                {% endcapture %}
+                {% capture trimmed as correct_address %}
+                  <code>{{ checks.address.correct_address }}</code>
+                {% endcapture %}
+                {% blocktrans trimmed with configured_address=set_address|safe correct_address=correct_address|safe %}
+                  Your forum address is set to {{ configured_address }} while correct value appears to be {{ correct_address }}.
+                {% endblocktrans %}
+              </div>
+              <div>
+                {% trans "Misago uses this setting to build links in e-mails sent to site users." %}
+              </div>
+            </div>
           </div>
         </div>
+        <div class="col-auto">
+          <a href="{% url 'misago:admin:settings:general:index' %}">
+            <span class="fas fa-external-link-alt"></span>
+          </a>
+        </div>
       </div>
     </div>
   {% endif %}

+ 2 - 2
misago/templates/misago/base.html

@@ -14,12 +14,12 @@
         <meta property="og:title" content="{% spaceless %}{% block og-title %}{% endblock og-title %}{% endspaceless %}" />
         <meta property="og:description" content="{% spaceless %}{% block og-description %}{{ settings.forum_index_meta_description|default:'' }}{% endblock og-description %}{% endspaceless %}" />
         <meta property="og:type" content="website" />
-        <meta property="og:url" content="{% spaceless %}{% block og-url %}{{ SITE_ADDRESS }}{% endblock og-url %}{% endspaceless %}" />
+        <meta property="og:url" content="{% spaceless %}{% block og-url %}{{ settings.forum_address }}{% endblock og-url %}{% endspaceless %}" />
         <meta property="og:image" content="{% spaceless %}{% block og-image %}{% static 'og-image.jpg' %}{% endblock og-image %}{% endspaceless %}" />
       {% endblock og-tags %}
     {% endspaceless %}
     {% include "misago/head.html" %}
-    <script type="application/ld+json">{"@context":"http://schema.org","@type":"WebSite","url":"{{ SITE_ADDRESS }}"}</script>
+    <script type="application/ld+json">{"@context":"http://schema.org","@type":"WebSite","url":"{{ settings.forum_address }}"}</script>
   </head>
   <body {% if misago_agreement %}class="agreement-overlay-visible"{% endif %}>
 

+ 2 - 2
misago/templates/misago/categories/base.html

@@ -1,5 +1,5 @@
 {% extends "misago/base.html" %}
-{% load i18n misago_shorthands %}
+{% load i18n misago_absoluteurl misago_shorthands %}
 
 
 {% block title %}
@@ -46,7 +46,7 @@
 {% endblock og-description %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block content %}

+ 1 - 1
misago/templates/misago/emails/base.html

@@ -69,7 +69,7 @@
             <br>
             <div style="border-top: 1px solid #ddd; color: #666; font-size: 12px; line-height: 18px;">
               {% if settings.email_footer %}<br>{{ settings.email_footer }}{% endif %}
-              <br><a href="{{ SITE_ADDRESS }}" style="color: #888; text-decoration: underline;">Sent from {{ SITE_HOST }}</a>
+              <br><a href="{{ settings.forum_address }}" style="color: #888; text-decoration: underline;">Sent from {{ forum_host }}</a>
             </div>
 
             <!-- ### END CONTENT ### -->

+ 1 - 1
misago/templates/misago/emails/base.txt

@@ -8,4 +8,4 @@
 
 ------------------------------------------------
 {% if settings.email_footer %}{{ settings.email_footer }}{% endif %}
-Sent from {{ SITE_ADDRESS }}
+Sent from {{ settings.forum_address }}

+ 2 - 2
misago/templates/misago/privacy_policy.html

@@ -1,5 +1,5 @@
 {% extends "misago/base.html" %}
-{% load i18n %}
+{% load i18n misago_absoluteurl %}
 
 
 {% block title %}{{ title }} | {{ block.super }}{% endblock %}
@@ -8,7 +8,7 @@
 {% block og-title %}{{ title }}{% endblock %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block content %}

+ 2 - 2
misago/templates/misago/profile/base.html

@@ -1,5 +1,5 @@
 {% extends "misago/base.html" %}
-{% load i18n %}
+{% load i18n misago_absoluteurl %}
 
 
 {% block title %}{{ profile.username }} | {{ block.super }}{% endblock %}
@@ -30,7 +30,7 @@
 {% endblock og-description %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block content %}

+ 2 - 2
misago/templates/misago/search.html

@@ -1,5 +1,5 @@
 {% extends "misago/base.html" %}
-{% load i18n %}
+{% load i18n misago_absoluteurl %}
 
 
 {% block title %}{% trans "Search site" %} | {{ block.super }}{% endblock %}
@@ -8,7 +8,7 @@
 {% block og-title %}{% trans "Search site" %}{% endblock %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block content %}

+ 2 - 2
misago/templates/misago/terms_of_service.html

@@ -1,5 +1,5 @@
 {% extends "misago/base.html" %}
-{% load i18n %}
+{% load i18n misago_absoluteurl %}
 
 
 {% block title %}{{ title }} | {{ block.super }}{% endblock %}
@@ -8,7 +8,7 @@
 {% block og-title %}{{ title }}{% endblock %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block content %}

+ 2 - 2
misago/templates/misago/thread/thread.html

@@ -1,5 +1,5 @@
 {% extends "misago/base.html" %}
-{% load i18n %}
+{% load i18n misago_absoluteurl %}
 
 
 {% block title %}
@@ -26,7 +26,7 @@
 
 
 {% block og-url %}
-{{ SITE_ADDRESS }}{{ thread.get_absolute_url }}
+{% absoluteurl thread.get_absolute_url %}
 {% endblock og-url %}
 
 

+ 3 - 3
misago/templates/misago/threadslist/base.html

@@ -3,9 +3,9 @@
 
 
 {% block meta-extra %}
-<link rel="canonical" href="{{ REQUEST_PATH }}" />
+<link rel="canonical" href="{{ request_path }}" />
 {% if list_page.next %}
-<link rel="next" href="{{ REQUEST_PATH }}?start={{ list_page.next }}" />
+<link rel="next" href="{{ request_path }}?start={{ list_page.next }}" />
 {% endif %}
 {% endblock meta-extra %}
 
@@ -80,7 +80,7 @@
             <nav role="navigation" itemscope itemtype="http://schema.org/SiteNavigationElement">
               <ul class="pager">
                 <li class="next">
-                  <a href="{{ REQUEST_PATH }}?page={{ list_page.next }}" rel="next" title="{% trans 'Next page' %}">
+                  <a href="{{ request_path }}?page={{ list_page.next }}" rel="next" title="{% trans 'Next page' %}">
                     <span aria-hidden="true" class="material-icon">
                       arrow_forward
                     </span>

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

@@ -1,5 +1,5 @@
 {% extends "misago/threadslist/base.html" %}
-{% load i18n misago_shorthands misago_stringutils %}
+{% load i18n misago_absoluteurl misago_shorthands misago_stringutils %}
 
 
 {% block title %}
@@ -36,7 +36,7 @@
 {% endblock og-description %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block extra-css %}

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

@@ -1,5 +1,5 @@
 {% extends "misago/threadslist/base.html" %}
-{% load i18n %}
+{% load i18n misago_absoluteurl %}
 
 
 {% block title %}
@@ -37,7 +37,7 @@
 {% endblock og-title %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block page-header %}

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

@@ -1,5 +1,5 @@
 {% extends "misago/userslists/base.html" %}
-{% load i18n misago_avatars %}
+{% load i18n misago_absoluteurl misago_avatars %}
 
 
 {% block title %}{% trans "Active posters" %} | {{ block.super }}{% endblock %}
@@ -38,7 +38,7 @@
 {% endblock og-description %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ REQUEST_PATH }}{% endblock %}
+{% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
 {% block content %}

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

@@ -1,5 +1,5 @@
 {% extends "misago/userslists/base.html" %}
-{% load i18n misago_pagetitle misago_shorthands misago_stringutils %}
+{% load i18n misago_absoluteurl misago_pagetitle misago_shorthands misago_stringutils %}
 
 
 {% block title %}{% pagetitle rank.name page=paginator.page %} | {{ block.super }}{% endblock %}
@@ -47,7 +47,7 @@
 {% endblock og-description %}
 
 
-{% block og-url %}{{ SITE_ADDRESS }}{{ rank.get_absolute_url }}{% endblock %}
+{% block og-url %}{% absoluteurl rank.get_absolute_url %}{% endblock %}
 
 
 {% block content %}

+ 11 - 8
misago/threads/tests/test_emailnotification_middleware.py

@@ -5,6 +5,7 @@ from django.urls import reverse
 from django.utils import timezone
 from django.utils.encoding import smart_str
 
+from ...conf.test import override_dynamic_settings
 from .. import test
 from ...categories.models import Category
 from ...users.test import AuthenticatedUserTestCase, create_test_user
@@ -137,10 +138,11 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             send_email=True,
         )
 
-        response = self.client.post(
-            self.api_link, data={"post": "This is test response!"}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.api_link, data={"post": "This is test response!"}
+            )
+            self.assertEqual(response.status_code, 200)
 
         self.assertEqual(len(mail.outbox), 1)
         last_email = mail.outbox[-1]
@@ -167,10 +169,11 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             send_email=True,
         )
 
-        response = self.client.post(
-            self.api_link, data={"post": "This is test response!"}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.api_link, data={"post": "This is test response!"}
+            )
+            self.assertEqual(response.status_code, 200)
 
         self.assertEqual(len(mail.outbox), 1)
         last_email = mail.outbox[-1]

+ 11 - 9
misago/threads/tests/test_privatethread_start_api.py

@@ -5,6 +5,7 @@ from django.utils.encoding import smart_str
 
 from ...acl.test import patch_user_acl
 from ...categories.models import Category
+from ...conf.test import override_dynamic_settings
 from ...users.test import AuthenticatedUserTestCase, create_test_user
 from ..models import ThreadParticipant
 from ..test import other_user_cant_use_private_threads
@@ -309,15 +310,16 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
     def test_can_start_thread(self):
         """endpoint creates new thread"""
-        response = self.client.post(
-            self.api_link,
-            data={
-                "to": [self.other_user.username],
-                "title": "Hello, I am test thread!",
-                "post": "Lorem ipsum dolor met!",
-            },
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.api_link,
+                data={
+                    "to": [self.other_user.username],
+                    "title": "Hello, I am test thread!",
+                    "post": "Lorem ipsum dolor met!",
+                },
+            )
+            self.assertEqual(response.status_code, 200)
 
         thread = self.user.thread_set.all()[:1][0]
 

+ 13 - 8
misago/users/tests/test_options_views.py

@@ -1,6 +1,7 @@
 from django.core import mail
 from django.urls import reverse
 
+from ...conf.test import override_dynamic_settings
 from ..test import AuthenticatedUserTestCase
 
 
@@ -23,10 +24,12 @@ class ConfirmChangeEmailTests(AuthenticatedUserTestCase):
         super().setUp()
         link = "/api/users/%s/change-email/" % self.user.pk
 
-        response = self.client.post(
-            link, data={"new_email": "n3w@email.com", "password": self.USER_PASSWORD}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                link,
+                data={"new_email": "n3w@email.com", "password": self.USER_PASSWORD},
+            )
+            self.assertEqual(response.status_code, 200)
 
         for line in [l.strip() for l in mail.outbox[0].body.splitlines()]:
             if line.startswith("http://"):
@@ -58,10 +61,12 @@ class ConfirmChangePasswordTests(AuthenticatedUserTestCase):
         super().setUp()
         link = "/api/users/%s/change-password/" % self.user.pk
 
-        response = self.client.post(
-            link, data={"new_password": "n3wp4ssword", "password": self.USER_PASSWORD}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                link,
+                data={"new_password": "n3wp4ssword", "password": self.USER_PASSWORD},
+            )
+            self.assertEqual(response.status_code, 200)
 
         for line in [l.strip() for l in mail.outbox[0].body.splitlines()]:
             if line.startswith("http://"):

+ 5 - 6
misago/users/tests/test_prepareuserdatadownloads.py

@@ -3,7 +3,7 @@ from io import StringIO
 from django.core import mail
 from django.core.management import call_command
 
-from ...conf import settings
+from ...conf.test import override_dynamic_settings
 from ..datadownloads import request_user_data_download
 from ..management.commands import prepareuserdatadownloads
 from ..models import DataDownload
@@ -17,9 +17,10 @@ class PrepareUserDataDownloadsTests(AuthenticatedUserTestCase):
         self.assertEqual(data_download.status, DataDownload.STATUS_PENDING)
 
         out = StringIO()
-        call_command(prepareuserdatadownloads.Command(), stdout=out)
-        command_output = out.getvalue().splitlines()[0].strip()
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            call_command(prepareuserdatadownloads.Command(), stdout=out)
 
+        command_output = out.getvalue().splitlines()[0].strip()
         self.assertEqual(command_output, "Data downloads prepared: 1")
 
         updated_data_download = DataDownload.objects.get(pk=data_download.pk)
@@ -32,9 +33,7 @@ class PrepareUserDataDownloadsTests(AuthenticatedUserTestCase):
             mail.outbox[0].subject, "TestUser, your data download is ready"
         )
 
-        absolute_url = "".join(
-            [settings.MISAGO_ADDRESS.rstrip("/"), updated_data_download.file.url]
-        )
+        absolute_url = "".join(["http://test.com", updated_data_download.file.url])
         self.assertIn(absolute_url, mail.outbox[0].body)
 
     def test_skip_ready_data_download(self):

+ 11 - 8
misago/users/tests/test_user_changeemail_api.py

@@ -1,6 +1,7 @@
 from django.core import mail
 from django.urls import reverse
 
+from ...conf.test import override_dynamic_settings
 from ..test import AuthenticatedUserTestCase, create_test_user
 
 
@@ -76,10 +77,11 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
         """api allows users to change their e-mail addresses"""
         new_email = "new@email.com"
 
-        response = self.client.post(
-            self.link, data={"new_email": new_email, "password": self.USER_PASSWORD}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.link, data={"new_email": new_email, "password": self.USER_PASSWORD}
+            )
+            self.assertEqual(response.status_code, 200)
 
         self.assertIn("Confirm e-mail change", mail.outbox[0].subject)
         for line in [l.strip() for l in mail.outbox[0].body.splitlines()]:
@@ -108,10 +110,11 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
 
         self.login_user(self.user)
 
-        response = self.client.post(
-            self.link, data={"new_email": new_email, "password": user_password}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.link, data={"new_email": new_email, "password": user_password}
+            )
+            self.assertEqual(response.status_code, 200)
 
         self.assertIn("Confirm e-mail change", mail.outbox[0].subject)
         for line in [l.strip() for l in mail.outbox[0].body.splitlines()]:

+ 12 - 9
misago/users/tests/test_user_changepassword_api.py

@@ -1,6 +1,7 @@
 from django.core import mail
 from django.urls import reverse
 
+from ...conf.test import override_dynamic_settings
 from ..test import AuthenticatedUserTestCase
 
 
@@ -71,11 +72,12 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
         """api allows users to change their passwords"""
         new_password = "N3wP@55w0rd"
 
-        response = self.client.post(
-            self.link,
-            data={"new_password": new_password, "password": self.USER_PASSWORD},
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.link,
+                data={"new_password": new_password, "password": self.USER_PASSWORD},
+            )
+            self.assertEqual(response.status_code, 200)
 
         self.assertIn("Confirm password change", mail.outbox[0].subject)
         for line in [l.strip() for l in mail.outbox[0].body.splitlines()]:
@@ -104,10 +106,11 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
 
         self.login_user(self.user)
 
-        response = self.client.post(
-            self.link, data={"new_password": new_password, "password": old_password}
-        )
-        self.assertEqual(response.status_code, 200)
+        with override_dynamic_settings(forum_address="http://test.com/"):
+            response = self.client.post(
+                self.link, data={"new_password": new_password, "password": old_password}
+            )
+            self.assertEqual(response.status_code, 200)
 
         self.assertIn("Confirm password change", mail.outbox[0].subject)
         for line in [l.strip() for l in mail.outbox[0].body.splitlines()]: