Просмотр исходного кода

Include active theme styles on page

rafalp 6 лет назад
Родитель
Сommit
9a5228f425

+ 1 - 3
misago/conf/tests/test_getting_dynamic_settings_values.py

@@ -7,16 +7,14 @@ from ..dynamicsettings import DynamicSettings
 def test_settings_are_loaded_from_database_if_cache_is_not_available(
     db, mocker, cache_versions, django_assert_num_queries
 ):
-    mocker.patch("django.core.cache.cache.set")
     mocker.patch("django.core.cache.cache.get", return_value=None)
     with django_assert_num_queries(1):
         DynamicSettings(cache_versions)
 
 
-def test_settings_are_loaded_from_cache_if_it_is_not_none(
+def test_settings_are_loaded_from_cache_if_it_is_set(
     db, mocker, cache_versions, django_assert_num_queries
 ):
-    mocker.patch("django.core.cache.cache.set")
     cache_get = mocker.patch("django.core.cache.cache.get", return_value={})
     with django_assert_num_queries(0):
         DynamicSettings(cache_versions)

+ 7 - 1
misago/conftest.py

@@ -5,13 +5,19 @@ from .admin.auth import authorize_admin
 from .conf import SETTINGS_CACHE
 from .conf.dynamicsettings import DynamicSettings
 from .conf.staticsettings import StaticSettings
+from .themes import THEME_CACHE
 from .users import BANS_CACHE
 from .users.models import AnonymousUser
 from .users.test import create_test_superuser, create_test_user
 
 
 def get_cache_versions():
-    return {ACL_CACHE: "abcdefgh", BANS_CACHE: "abcdefgh", SETTINGS_CACHE: "abcdefgh"}
+    return {
+        ACL_CACHE: "abcdefgh",
+        BANS_CACHE: "abcdefgh",
+        SETTINGS_CACHE: "abcdefgh",
+        THEME_CACHE: "abcdefgh",
+    }
 
 
 @pytest.fixture

+ 24 - 0
misago/themes/activetheme.py

@@ -0,0 +1,24 @@
+from .models import Theme
+
+
+def get_active_theme():
+    active_theme = Theme.objects.get(is_active=True)
+    themes = active_theme.get_ancestors(include_self=True)
+    themes = themes.prefetch_related("css")
+
+    include_defaults = False
+    styles = []
+
+    for theme in themes:
+        if theme.is_default:
+            include_defaults = True
+        for css in theme.css.all():
+            if css.url:
+                styles.append(css.url)
+            elif css.source_needs_building:
+                if css.build_file:
+                    styles.append(css.build_file.url)
+            else:
+                styles.append(css.source_file.url)
+
+    return {"include_defaults": include_defaults, "styles": styles}

+ 16 - 0
misago/themes/cache.py

@@ -1,6 +1,22 @@
+from django.core.cache import cache
+
 from . import THEME_CACHE
 from ..cache.versions import invalidate_cache
 
 
+def get_theme_cache(cache_versions):
+    key = get_cache_key(cache_versions)
+    return cache.get(key)
+
+
+def set_theme_cache(cache_versions, theme):
+    key = get_cache_key(cache_versions)
+    cache.set(key, theme)
+
+
+def get_cache_key(cache_versions):
+    return "%s_%s" % (THEME_CACHE, cache_versions[THEME_CACHE])
+
+
 def clear_theme_cache():
     invalidate_cache(THEME_CACHE)

+ 7 - 20
misago/themes/context_processors.py

@@ -1,24 +1,11 @@
-from .models import Theme
+from .activetheme import get_active_theme
+from .cache import get_theme_cache, set_theme_cache
 
 
 def theme(request):
-    active_theme = Theme.objects.get(is_active=True)
-    themes = active_theme.get_ancestors(include_self=True)
-    themes = themes.prefetch_related("css")
+    active_theme = get_theme_cache(request.cache_versions)
+    if active_theme is None:
+        active_theme = get_active_theme()
+        set_theme_cache(request.cache_versions, active_theme)
 
-    include_defaults = False
-    styles = []
-
-    for theme in themes:
-        if theme.is_default:
-            include_defaults = True
-        for css in theme.css.all():
-            if css.url:
-                styles.append(css.url)
-            if css.source_needs_building:
-                if css.build_file:
-                    styles.append(css.build_file.url)
-            else:
-                styles.append(css.source_file.url)
-
-    return {"theme": {"include_defaults": include_defaults, "styles": styles}}
+    return {"theme": active_theme}

+ 26 - 0
misago/themes/tests/conftest.py

@@ -0,0 +1,26 @@
+import pytest
+
+from ..models import Theme
+
+
+@pytest.fixture
+def default_theme(db):
+    return Theme.objects.get(is_default=True)
+
+
+@pytest.fixture
+def theme(db):
+    return Theme.objects.create(name="Custom theme")
+
+
+@pytest.fixture
+def other_theme(db):
+    return Theme.objects.create(name="Other theme")
+
+
+@pytest.fixture
+def active_theme(theme):
+    Theme.objects.update(is_active=False)
+    theme.is_active = True
+    theme.save()
+    return theme

+ 61 - 0
misago/themes/tests/test_context_processors.py

@@ -0,0 +1,61 @@
+from unittest.mock import Mock
+
+import pytest
+
+from .. import THEME_CACHE
+from ..context_processors import theme as context_processor
+
+
+@pytest.fixture
+def mock_request(cache_versions):
+    return Mock(cache_versions=cache_versions)
+
+
+def test_theme_data_is_included_in_template_context(db, mock_request):
+    assert context_processor(mock_request)["theme"]
+
+
+def test_theme_is_loaded_from_database_if_cache_is_not_available(
+    db, mocker, mock_request, django_assert_num_queries
+):
+    mocker.patch("django.core.cache.cache.get", return_value=None)
+    with django_assert_num_queries(3):
+        context_processor(mock_request)
+
+
+def test_theme_is_loaded_from_cache_if_it_is_set(
+    db, mocker, mock_request, django_assert_num_queries
+):
+    cache_get = mocker.patch("django.core.cache.cache.get", return_value={})
+    with django_assert_num_queries(0):
+        context_processor(mock_request)
+    cache_get.assert_called_once()
+
+
+def test_theme_cache_is_set_if_none_exists(db, mocker, mock_request):
+    cache_set = mocker.patch("django.core.cache.cache.set")
+    mocker.patch("django.core.cache.cache.get", return_value=None)
+
+    context_processor(mock_request)
+    cache_set.assert_called_once()
+
+
+def test_theme_cache_is_not_set_if_it_already_exists(
+    db, mocker, mock_request, django_assert_num_queries
+):
+    cache_set = mocker.patch("django.core.cache.cache.set")
+    mocker.patch("django.core.cache.cache.get", return_value={})
+    with django_assert_num_queries(0):
+        context_processor(mock_request)
+    cache_set.assert_not_called()
+
+
+def test_theme_cache_key_includes_cache_name_and_version(
+    db, mocker, mock_request, cache_versions
+):
+    cache_set = mocker.patch("django.core.cache.cache.set")
+    mocker.patch("django.core.cache.cache.get", return_value=None)
+    context_processor(mock_request)
+    cache_key = cache_set.call_args[0][0]
+    assert THEME_CACHE in cache_key
+    assert cache_versions[THEME_CACHE] in cache_key

+ 94 - 0
misago/themes/tests/test_getting_active_theme.py

@@ -0,0 +1,94 @@
+from django.core.files.base import ContentFile
+
+from ..activetheme import get_active_theme
+from ..models import Theme
+
+
+def test_active_theme_data_can_be_obtained(db):
+    assert get_active_theme()
+
+
+def test_if_active_theme_is_default_theme_include_defaults_flag_is_set(db):
+    assert get_active_theme()["include_defaults"]
+
+
+def test_if_active_theme_is_default_themes_child_include_defaults_flag_is_set(
+    default_theme, active_theme
+):
+    active_theme.parent = default_theme
+    active_theme.save()
+    assert get_active_theme()["include_defaults"]
+
+
+def test_if_active_theme_is_not_default_themes_child_include_defaults_flag_is_not_set(
+    active_theme
+):
+    assert not get_active_theme()["include_defaults"]
+
+
+def test_active_theme_styles_are_included(active_theme):
+    active_theme.css.create(name="test", url="https://example.com")
+    assert get_active_theme()["styles"]
+
+
+def test_active_theme_parents_styles_are_included(active_theme):
+    parent_theme = Theme.objects.create(name="Parent theme")
+    parent_theme.css.create(name="test", url="https://example.com")
+
+    active_theme.move_to(parent_theme)
+    active_theme.save()
+
+    assert get_active_theme()["styles"]
+
+
+def test_active_theme_child_themes_styles_are_not_included(active_theme):
+    child_theme = Theme.objects.create(parent=active_theme, name="Child theme")
+    child_theme.css.create(name="test", url="https://example.com")
+    assert not get_active_theme()["styles"]
+
+
+def test_active_theme_styles_are_ordered(active_theme):
+    last_css = active_theme.css.create(
+        name="test", url="https://last-example.com", order=1
+    )
+    first_css = active_theme.css.create(
+        name="test", url="https://first-example.com", order=0
+    )
+
+    assert get_active_theme()["styles"] == [first_css.url, last_css.url]
+
+
+def test_active_theme_styles_list_includes_url_to_remote_css(active_theme):
+    css = active_theme.css.create(name="test", url="https://last-example.com")
+    assert get_active_theme()["styles"] == [css.url]
+
+
+def test_active_theme_styles_list_contains_url_to_local_css(active_theme):
+    css = active_theme.css.create(
+        name="test",
+        source_file=ContentFile("body {}", name="test.css"),
+        source_hash="abcdefgh",
+    )
+    assert get_active_theme()["styles"] == [css.source_file.url]
+
+
+def test_active_theme_styles_list_contains_url_to_local_built_css(active_theme):
+    css = active_theme.css.create(
+        name="test",
+        source_needs_building=True,
+        build_file=ContentFile("body {}", name="test.css"),
+        build_hash="abcdefgh",
+    )
+    assert get_active_theme()["styles"] == [css.build_file.url]
+
+
+def test_active_theme_styles_list_exclude_url_to_css_that_has_not_been_built(
+    active_theme
+):
+    active_theme.css.create(
+        name="test",
+        source_file=ContentFile("body {}", name="test.css"),
+        source_hash="abcdefgh",
+        source_needs_building=True,
+    )
+    assert not get_active_theme()["styles"]

+ 7 - 0
misago/themes/tests/test_styles_are_included_on_page.py

@@ -0,0 +1,7 @@
+from ...test import assert_contains
+
+
+def test_active_theme_styles_are_included_in_page_html(client, active_theme):
+    css = active_theme.css.create(name="test", url="https://cdn.example.com/style.css")
+    response = client.get("/")
+    assert_contains(response, 'link href="%s" rel="stylesheet"' % css.url)