Selaa lähdekoodia

Refactor internal default settings (#497)

* Refactored internal settings to behave more like Django settings

* Use this opportunity to rename stuff to crx_* for consistency.

* Lazy load any settings being used as choices in concrete model fields. This will totally eliminate the issue of sites generating migrations back in coderedcms. It's a bit of a hack (that we were already employing elsewhere) but will be a good quality of life improvement until we make built-in models abstract and have no more concrete models (which may never even happen)
Vince Salvino 2 vuotta sitten
vanhempi
commit
5d11616b7f

+ 11 - 9
coderedcms/blocks/base_blocks.py

@@ -12,7 +12,7 @@ from wagtail.core.models import Collection
 from wagtail.core.utils import resolve_model_string
 from wagtail.documents.blocks import DocumentChooserBlock
 
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 
 
 class ClassifierTermChooserBlock(blocks.FieldBlock):
@@ -109,14 +109,14 @@ class ButtonMixin(blocks.StructBlock):
         label=_('Button Title'),
     )
     button_style = blocks.ChoiceBlock(
-        choices=cr_settings['FRONTEND_BTN_STYLE_CHOICES'],
-        default=cr_settings['FRONTEND_BTN_STYLE_DEFAULT'],
+        choices=crx_settings.CRX_FRONTEND_BTN_STYLE_CHOICES,
+        default=crx_settings.CRX_FRONTEND_BTN_STYLE_DEFAULT,
         required=False,
         label=_('Button Style'),
     )
     button_size = blocks.ChoiceBlock(
-        choices=cr_settings['FRONTEND_BTN_SIZE_CHOICES'],
-        default=cr_settings['FRONTEND_BTN_SIZE_DEFAULT'],
+        choices=crx_settings.CRX_FRONTEND_BTN_SIZE_CHOICES,
+        default=crx_settings.CRX_FRONTEND_BTN_SIZE_DEFAULT,
         required=False,
         label=_('Button Size'),
     )
@@ -184,8 +184,8 @@ class CoderedAdvColumnSettings(CoderedAdvSettings):
     BaseBlockSettings plus additional column fields.
     """
     column_breakpoint = blocks.ChoiceBlock(
-        choices=cr_settings['FRONTEND_COL_BREAK_CHOICES'],
-        default=cr_settings['FRONTEND_COL_BREAK_DEFAULT'],
+        choices=crx_settings.CRX_FRONTEND_COL_BREAK_CHOICES,
+        default=crx_settings.CRX_FRONTEND_COL_BREAK_DEFAULT,
         required=False,
         verbose_name=_('Column Breakpoint'),
         help_text=_('Screen size at which the column will expand horizontally or stack vertically.'),  # noqa
@@ -207,8 +207,10 @@ class BaseBlock(blocks.StructBlock):
         Construct and inject settings block, then initialize normally.
         """
         klassname = self.__class__.__name__.lower()
-        choices = cr_settings['FRONTEND_TEMPLATES_BLOCKS'].get('*', ()) + \
-            cr_settings['FRONTEND_TEMPLATES_BLOCKS'].get(klassname, ())
+        choices = (
+            crx_settings.CRX_FRONTEND_TEMPLATES_BLOCKS.get('*', []) +
+            crx_settings.CRX_FRONTEND_TEMPLATES_BLOCKS.get(klassname, [])
+        )
 
         if not local_blocks:
             local_blocks = ()

+ 3 - 3
coderedcms/blocks/layout_blocks.py

@@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
 from wagtail.core import blocks
 from wagtail.images.blocks import ImageChooserBlock
 
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 
 from .base_blocks import BaseLayoutBlock, CoderedAdvColumnSettings
 
@@ -20,8 +20,8 @@ class ColumnBlock(BaseLayoutBlock):
     Renders content in a column.
     """
     column_size = blocks.ChoiceBlock(
-        choices=cr_settings['FRONTEND_COL_SIZE_CHOICES'],
-        default=cr_settings['FRONTEND_COL_SIZE_DEFAULT'],
+        choices=crx_settings.CRX_FRONTEND_COL_SIZE_CHOICES,
+        default=crx_settings.CRX_FRONTEND_COL_SIZE_DEFAULT,
         required=False,
         label=_('Column size'),
     )

+ 5 - 5
coderedcms/forms.py

@@ -13,7 +13,7 @@ from wagtail.contrib.forms.views import SubmissionsListView as WagtailSubmission
 from wagtail.contrib.forms.forms import FormBuilder
 from wagtail.contrib.forms.models import AbstractFormField
 
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 from coderedcms.utils import attempt_protected_media_value_conversion
 
 FORM_FIELD_CHOICES = (
@@ -64,13 +64,13 @@ class SecureFileField(forms.FileField):
             self._check_blacklist(value)
 
     def _check_whitelist(self, value):
-        if cr_settings['PROTECTED_MEDIA_UPLOAD_WHITELIST']:
-            if os.path.splitext(value.name)[1].lower() not in cr_settings['PROTECTED_MEDIA_UPLOAD_WHITELIST']:  # noqa
+        if crx_settings.CRX_PROTECTED_MEDIA_UPLOAD_WHITELIST:
+            if os.path.splitext(value.name)[1].lower() not in crx_settings.CRX_PROTECTED_MEDIA_UPLOAD_WHITELIST:  # noqa
                 raise ValidationError(self.error_messages['whitelist_file'])
 
     def _check_blacklist(self, value):
-        if cr_settings['PROTECTED_MEDIA_UPLOAD_BLACKLIST']:
-            if os.path.splitext(value.name)[1].lower() in cr_settings['PROTECTED_MEDIA_UPLOAD_BLACKLIST']:  # noqa
+        if crx_settings.CRX_PROTECTED_MEDIA_UPLOAD_BLACKLIST:
+            if os.path.splitext(value.name)[1].lower() in crx_settings.CRX_PROTECTED_MEDIA_UPLOAD_BLACKLIST:  # noqa
                 raise ValidationError(self.error_messages['blacklist_file'])
 
 

+ 43 - 0
coderedcms/migrations/0028_auto_20220609_1532.py

@@ -0,0 +1,43 @@
+# Generated by Django 3.2.13 on 2022-06-09 19:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('coderedcms', '0027_auto_20220603_1142'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='carousel',
+            name='animation',
+            field=models.CharField(blank=True, default='', help_text='The animation when transitioning between slides.', max_length=20, verbose_name='Animation'),
+        ),
+        migrations.AlterField(
+            model_name='layoutsettings',
+            name='frontend_theme',
+            field=models.CharField(blank=True, default='', max_length=50, verbose_name='Theme variant'),
+        ),
+        migrations.AlterField(
+            model_name='layoutsettings',
+            name='navbar_class',
+            field=models.CharField(blank=True, default='', help_text='Custom classes applied to navbar e.g. "bg-light", "bg-dark", "bg-primary".', max_length=255, verbose_name='Navbar CSS class'),
+        ),
+        migrations.AlterField(
+            model_name='layoutsettings',
+            name='navbar_collapse_mode',
+            field=models.CharField(blank=True, default='', help_text='Control on what screen sizes to show and collapse the navbar menu links.', max_length=50, verbose_name='Collapse navbar menu'),
+        ),
+        migrations.AlterField(
+            model_name='layoutsettings',
+            name='navbar_color_scheme',
+            field=models.CharField(blank=True, default='', help_text='Optimizes text and other navbar elements for use with light or dark backgrounds.', max_length=50, verbose_name='Navbar color scheme'),
+        ),
+        migrations.AlterField(
+            model_name='layoutsettings',
+            name='navbar_format',
+            field=models.CharField(blank=True, default='', max_length=50, verbose_name='Navbar format'),
+        ),
+    ]

+ 17 - 15
coderedcms/models/page_models.py

@@ -86,7 +86,7 @@ from coderedcms.wagtail_flexible_forms.models import (
     SessionFormSubmission,
     SubmissionRevision,
 )
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 from coderedcms.widgets import ClassifierSelectWidget
 
 
@@ -331,8 +331,10 @@ class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
         """
         super().__init__(*args, **kwargs)
         klassname = self.__class__.__name__.lower()
-        template_choices = cr_settings['FRONTEND_TEMPLATES_PAGES'].get('*', ()) + \
-            cr_settings['FRONTEND_TEMPLATES_PAGES'].get(klassname, ())
+        template_choices = (
+            crx_settings.CRX_FRONTEND_TEMPLATES_PAGES.get('*', []) +
+            crx_settings.CRX_FRONTEND_TEMPLATES_PAGES.get(klassname, [])
+        )
 
         self._meta.get_field('index_order_by').choices = self.index_order_by_choices
         self._meta.get_field('custom_template').choices = template_choices
@@ -1082,15 +1084,15 @@ class CoderedFormMixin(models.Model):
     )
     button_style = models.CharField(
         blank=True,
-        choices=cr_settings['FRONTEND_BTN_STYLE_CHOICES'],
-        default=cr_settings["FRONTEND_BTN_STYLE_DEFAULT"],
+        choices=crx_settings.CRX_FRONTEND_BTN_STYLE_CHOICES,
+        default=crx_settings.CRX_FRONTEND_BTN_STYLE_DEFAULT,
         max_length=255,
         verbose_name=_('Button style'),
     )
     button_size = models.CharField(
         blank=True,
-        choices=cr_settings['FRONTEND_BTN_SIZE_CHOICES'],
-        default=cr_settings["FRONTEND_BTN_SIZE_DEFAULT"],
+        choices=crx_settings.CRX_FRONTEND_BTN_SIZE_CHOICES,
+        default=crx_settings.CRX_FRONTEND_BTN_SIZE_DEFAULT,
         max_length=255,
         verbose_name=_('Button Size'),
     )
@@ -1203,7 +1205,7 @@ class CoderedFormMixin(models.Model):
                     for chunk in val.chunks():
                         destination.write(chunk)
 
-                processed_data[key] = "{0}{1}".format(cr_settings['PROTECTED_MEDIA_URL'], path)
+                processed_data[key] = "{0}{1}".format(crx_settings.CRX_PROTECTED_MEDIA_URL, path)
             else:
                 processed_data[key] = val
 
@@ -1211,8 +1213,8 @@ class CoderedFormMixin(models.Model):
 
     def get_storage(self):
         return FileSystemStorage(
-            location=cr_settings['PROTECTED_MEDIA_ROOT'],
-            base_url=cr_settings['PROTECTED_MEDIA_URL']
+            location=crx_settings.CRX_PROTECTED_MEDIA_ROOT,
+            base_url=crx_settings.CRX_PROTECTED_MEDIA_URL
         )
 
     def process_form_submission(self, request, form, form_submission, processed_data):
@@ -1561,13 +1563,13 @@ class CoderedSessionFormSubmission(SessionFormSubmission):
         return value
 
     def render_link(self, value):
-        return "{0}{1}".format(cr_settings['PROTECTED_MEDIA_URL'], value)
+        return "{0}{1}".format(crx_settings.CRX_PROTECTED_MEDIA_URL, value)
 
     def render_image(self, value):
-        return "{0}{1}".format(cr_settings['PROTECTED_MEDIA_URL'], value)
+        return "{0}{1}".format(crx_settings.CRX_PROTECTED_MEDIA_URL, value)
 
     def render_file(self, value):
-        return "{0}{1}".format(cr_settings['PROTECTED_MEDIA_URL'], value)
+        return "{0}{1}".format(crx_settings.CRX_PROTECTED_MEDIA_URL, value)
 
 
 @receiver(post_save)
@@ -1702,8 +1704,8 @@ class CoderedStreamFormPage(CoderedFormMixin, CoderedStreamFormMixin, CoderedWeb
 
     def get_storage(self):
         return FileSystemStorage(
-            location=cr_settings['PROTECTED_MEDIA_ROOT'],
-            base_url=cr_settings['PROTECTED_MEDIA_URL']
+            location=crx_settings.CRX_PROTECTED_MEDIA_ROOT,
+            base_url=crx_settings.CRX_PROTECTED_MEDIA_URL
         )
 
 

+ 17 - 3
coderedcms/models/snippet_models.py

@@ -19,7 +19,7 @@ from wagtail.images import get_image_model_string
 
 from coderedcms.blocks import HTML_STREAMBLOCKS, LAYOUT_STREAMBLOCKS, NAVIGATION_STREAMBLOCKS
 from coderedcms.fields import CoderedStreamField
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 
 
 @register_snippet
@@ -49,8 +49,8 @@ class Carousel(ClusterableModel):
     animation = models.CharField(
         blank=True,
         max_length=20,
-        choices=cr_settings['FRONTEND_CAROUSEL_FX_CHOICES'],
-        default=cr_settings['FRONTEND_CAROUSEL_FX_DEFAULT'],
+        choices=None,
+        default='',
         verbose_name=_('Animation'),
         help_text=_('The animation when transitioning between slides.'),
     )
@@ -73,6 +73,20 @@ class Carousel(ClusterableModel):
     def __str__(self):
         return self.name
 
+    def __init__(self, *args, **kwargs):
+        """
+        Inject custom choices and defaults into the form fields
+        to enable customization of settings without causing migration issues.
+        """
+        super().__init__(*args, **kwargs)
+        # Set choices dynamically.
+        self._meta.get_field('animation').choices = (
+            crx_settings.CRX_FRONTEND_CAROUSEL_FX_CHOICES
+        )
+        # Set default dynamically.
+        if not self.id:
+            self.animation = crx_settings.CRX_FRONTEND_CAROUSEL_FX_DEFAULT
+
 
 class CarouselSlide(Orderable, models.Model):
     """

+ 37 - 11
coderedcms/models/wagtailsettings_models.py

@@ -12,7 +12,7 @@ from wagtail.contrib.settings.models import BaseSetting, register_setting
 from wagtail.images import get_image_model_string
 
 from coderedcms.fields import MonospaceField
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 
 
 @register_setting(icon='cr-desktop')
@@ -43,15 +43,15 @@ class LayoutSettings(BaseSetting):
     navbar_color_scheme = models.CharField(
         blank=True,
         max_length=50,
-        choices=cr_settings['FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES'],
-        default=cr_settings['FRONTEND_NAVBAR_COLOR_SCHEME_DEFAULT'],
+        choices=None,
+        default='',
         verbose_name=_('Navbar color scheme'),
         help_text=_('Optimizes text and other navbar elements for use with light or dark backgrounds.'),  # noqa
     )
     navbar_class = models.CharField(
         blank=True,
         max_length=255,
-        default=cr_settings['FRONTEND_NAVBAR_CLASS_DEFAULT'],
+        default='',
         verbose_name=_('Navbar CSS class'),
         help_text=_('Custom classes applied to navbar e.g. "bg-light", "bg-dark", "bg-primary".'),
     )
@@ -73,16 +73,16 @@ class LayoutSettings(BaseSetting):
     navbar_collapse_mode = models.CharField(
         blank=True,
         max_length=50,
-        choices=cr_settings['FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES'],
-        default=cr_settings['FRONTEND_NAVBAR_COLLAPSE_MODE_DEFAULT'],
+        choices=None,
+        default='',
         verbose_name=_('Collapse navbar menu'),
         help_text=_('Control on what screen sizes to show and collapse the navbar menu links.'),
     )
     navbar_format = models.CharField(
         blank=True,
         max_length=50,
-        choices=cr_settings['FRONTEND_NAVBAR_FORMAT_CHOICES'],
-        default=cr_settings['FRONTEND_NAVBAR_FORMAT_DEFAULT'],
+        choices=None,
+        default='',
         verbose_name=_('Navbar format'),
     )
     navbar_search = models.BooleanField(
@@ -93,10 +93,9 @@ class LayoutSettings(BaseSetting):
     frontend_theme = models.CharField(
         blank=True,
         max_length=50,
-        choices=cr_settings['FRONTEND_THEME_CHOICES'],
-        default=cr_settings['FRONTEND_THEME_DEFAULT'],
+        choices=None,
+        default='',
         verbose_name=_('Theme variant'),
-        help_text=cr_settings['FRONTEND_THEME_HELP'],
     )
 
     panels = [
@@ -128,6 +127,33 @@ class LayoutSettings(BaseSetting):
         ),
     ]
 
+    def __init__(self, *args, **kwargs):
+        """
+        Inject custom choices and defaults into the form fields
+        to enable customization of settings without causing migration issues.
+        """
+        super().__init__(*args, **kwargs)
+        # Set choices dynamically.
+        self._meta.get_field('frontend_theme').choices = (
+            crx_settings.CRX_FRONTEND_THEME_CHOICES
+        )
+        self._meta.get_field('navbar_collapse_mode').choices = (
+            crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES
+        )
+        self._meta.get_field('navbar_color_scheme').choices = (
+            crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES
+        )
+        self._meta.get_field('navbar_format').choices = (
+            crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_CHOICES
+        )
+        # Set default dynamically.
+        if not self.id:
+            self.frontend_theme = crx_settings.CRX_FRONTEND_THEME_DEFAULT
+            self.navbar_class = crx_settings.CRX_FRONTEND_NAVBAR_CLASS_DEFAULT
+            self.navbar_collapse_mode = crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_DEFAULT
+            self.navbar_color_scheme = crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_DEFAULT
+            self.navbar_format = crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_DEFAULT
+
 
 @register_setting(icon='cr-google')
 class AnalyticsSettings(BaseSetting):

+ 73 - 71
coderedcms/settings.py

@@ -1,29 +1,35 @@
 import os
-from functools import lru_cache
 from django.conf import settings
 import bootstrap4.bootstrap as bootstrap
 
 
-PROJECT_DIR = settings.PROJECT_DIR if getattr(settings, 'PROJECT_DIR') else os.path.dirname(
-    os.path.dirname(os.path.abspath(__file__))
-)
-BASE_DIR = settings.BASE_DIR if getattr(settings, 'BASE_DIR') else os.path.dirname(PROJECT_DIR)
-
-DEFAULTS = {
-    'PROTECTED_MEDIA_URL': '/protected/',
-    'PROTECTED_MEDIA_ROOT': os.path.join(BASE_DIR, 'protected'),
-    'PROTECTED_MEDIA_UPLOAD_WHITELIST': [],
-    'PROTECTED_MEDIA_UPLOAD_BLACKLIST': ['.sh', '.exe', '.bat', '.ps1', '.app', '.jar', '.py', '.php', '.pl', '.rb'],  # noqa
-
-    'FRONTEND_BTN_SIZE_DEFAULT': '',
-    'FRONTEND_BTN_SIZE_CHOICES': (
+class _DefaultSettings:
+
+    CRX_PROTECTED_MEDIA_URL = '/protected/'
+    CRX_PROTECTED_MEDIA_ROOT = os.path.join(settings.BASE_DIR, 'protected')
+    CRX_PROTECTED_MEDIA_UPLOAD_WHITELIST = []
+    CRX_PROTECTED_MEDIA_UPLOAD_BLACKLIST = [
+        '.app',
+        '.bat',
+        '.exe',
+        '.jar',
+        '.php',
+        '.pl',
+        '.ps1',
+        '.py',
+        '.rb',
+        '.sh',
+    ]
+
+    CRX_FRONTEND_BTN_SIZE_DEFAULT = ''
+    CRX_FRONTEND_BTN_SIZE_CHOICES = [
         ('btn-sm', 'Small'),
         ('', 'Default'),
         ('btn-lg', 'Large'),
-    ),
+    ]
 
-    'FRONTEND_BTN_STYLE_DEFAULT': 'btn-primary',
-    'FRONTEND_BTN_STYLE_CHOICES': (
+    CRX_FRONTEND_BTN_STYLE_DEFAULT = 'btn-primary'
+    CRX_FRONTEND_BTN_STYLE_CHOICES = [
         ('btn-primary', 'Primary'),
         ('btn-secondary', 'Secondary'),
         ('btn-success', 'Success'),
@@ -41,16 +47,16 @@ DEFAULTS = {
         ('btn-outline-info', 'Outline Info'),
         ('btn-outline-light', 'Outline Light'),
         ('btn-outline-dark', 'Outline Dark'),
-    ),
+    ]
 
-    'FRONTEND_CAROUSEL_FX_DEFAULT': '',
-    'FRONTEND_CAROUSEL_FX_CHOICES': (
+    CRX_FRONTEND_CAROUSEL_FX_DEFAULT = ''
+    CRX_FRONTEND_CAROUSEL_FX_CHOICES = [
         ('', 'Slide'),
         ('carousel-fade', 'Fade'),
-    ),
+    ]
 
-    'FRONTEND_COL_SIZE_DEFAULT': '',
-    'FRONTEND_COL_SIZE_CHOICES': (
+    CRX_FRONTEND_COL_SIZE_DEFAULT = ''
+    CRX_FRONTEND_COL_SIZE_CHOICES = [
         ('', 'Automatically size'),
         ('12', 'Full row'),
         ('6', 'Half - 1/2 column'),
@@ -64,43 +70,42 @@ DEFAULTS = {
         ('5', 'Twelfths - 5/12 column'),
         ('7', 'Twelfths - 7/12 column'),
         ('11', 'Twelfths - 11/12 column'),
-    ),
+    ]
 
-    'FRONTEND_COL_BREAK_DEFAULT': 'md',
-    'FRONTEND_COL_BREAK_CHOICES': (
+    CRX_FRONTEND_COL_BREAK_DEFAULT = 'md'
+    CRX_FRONTEND_COL_BREAK_CHOICES = [
         ('', 'Always expanded'),
         ('sm', 'sm - Expand on small screens (phone, 576px) and larger'),
         ('md', 'md - Expand on medium screens (tablet, 768px) and larger'),
         ('lg', 'lg - Expand on large screens (laptop, 992px) and larger'),
         ('xl', 'xl - Expand on extra large screens (wide monitor, 1200px)'),
-    ),
+    ]
 
-    'FRONTEND_NAVBAR_FORMAT_DEFAULT': '',
-    'FRONTEND_NAVBAR_FORMAT_CHOICES': (
+    CRX_FRONTEND_NAVBAR_FORMAT_DEFAULT = ''
+    CRX_FRONTEND_NAVBAR_FORMAT_CHOICES = [
         ('', 'Default Bootstrap Navbar'),
         ('codered-navbar-center', 'Centered logo at top'),
-    ),
+    ]
 
-    'FRONTEND_NAVBAR_COLOR_SCHEME_DEFAULT': 'navbar-light',
-    'FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES': (
+    CRX_FRONTEND_NAVBAR_COLOR_SCHEME_DEFAULT = 'navbar-light'
+    CRX_FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES = [
         ('navbar-light', 'Light - for use with a light-colored navbar'),
         ('navbar-dark', 'Dark - for use with a dark-colored navbar'),
-    ),
+    ]
 
-    'FRONTEND_NAVBAR_CLASS_DEFAULT': 'bg-light',
+    CRX_FRONTEND_NAVBAR_CLASS_DEFAULT = 'bg-light'
 
-    'FRONTEND_NAVBAR_COLLAPSE_MODE_DEFAULT': 'navbar-expand-lg',
-    'FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES': (
+    CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_DEFAULT = 'navbar-expand-lg'
+    CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES = [
         ('', 'Never show menu - Always collapse menu behind a button'),
         ('navbar-expand-sm', 'sm - Show on small screens (phone size) and larger'),
         ('navbar-expand-md', 'md - Show on medium screens (tablet size) and larger'),
         ('navbar-expand-lg', 'lg - Show on large screens (laptop size) and larger'),
         ('navbar-expand-xl', 'xl - Show on extra large screens (desktop, wide monitor)'),
-    ),
+    ]
 
-    'FRONTEND_THEME_HELP': "Change the color palette of your site with a Bootstrap theme. Powered by Bootswatch https://bootswatch.com/.",  # noqa
-    'FRONTEND_THEME_DEFAULT': '',
-    'FRONTEND_THEME_CHOICES': (
+    CRX_FRONTEND_THEME_DEFAULT = ''
+    CRX_FRONTEND_THEME_CHOICES = [
         ('', 'Default - Classic Bootstrap'),
         ('cerulean', 'Cerulean - A calm blue sky'),
         ('cosmo', 'Cosmo - An ode to Metro'),
@@ -123,23 +128,23 @@ DEFAULTS = {
         ('superhero', 'Superhero - The brave and the blue'),
         ('united', 'United - Ubuntu orange and unique font'),
         ('yeti', 'Yeti - A friendly foundation'),
-    ),
+    ]
 
-    'FRONTEND_TEMPLATES_BLOCKS': {
-        'cardblock': (
+    CRX_FRONTEND_TEMPLATES_BLOCKS = {
+        'cardblock': [
             ('coderedcms/blocks/card_block.html', 'Card'),
             ('coderedcms/blocks/card_head.html', 'Card with header'),
             ('coderedcms/blocks/card_foot.html', 'Card with footer'),
             ('coderedcms/blocks/card_head_foot.html', 'Card with header and footer'),
             ('coderedcms/blocks/card_blurb.html', 'Blurb - rounded image and no border'),
             ('coderedcms/blocks/card_img.html', 'Cover image - use image as background'),
-        ),
-        'cardgridblock': (
+        ],
+        'cardgridblock': [
             ('coderedcms/blocks/cardgrid_group.html', 'Card group - attached cards of equal size'),
             ('coderedcms/blocks/cardgrid_deck.html', 'Card deck - separate cards of equal size'),
             ('coderedcms/blocks/cardgrid_columns.html', 'Card masonry - fluid brick pattern'),
-        ),
-        'pagelistblock': (
+        ],
+        'pagelistblock': [
             ('coderedcms/blocks/pagelist_block.html', 'General, simple list'),
             ('coderedcms/blocks/pagelist_list_group.html', 'General, list group navigation panel'),
             ('coderedcms/blocks/pagelist_article_media.html', 'Article, media format'),
@@ -149,45 +154,42 @@ DEFAULTS = {
              'Article, card deck - separate cards of equal size'),
             ('coderedcms/blocks/pagelist_article_card_columns.html',
              'Article, card masonry - fluid brick pattern'),
-        ),
-        'pagepreviewblock': (
+        ],
+        'pagepreviewblock': [
             ('coderedcms/blocks/pagepreview_card.html', 'Card'),
             ('coderedcms/blocks/pagepreview_form.html', 'Form inputs'),
-        ),
+        ],
         # templates that are available for all block types
-        '*': (
+        '*': [
             ('', 'Default'),
-        ),
-    },
+        ],
+    }
 
-    'FRONTEND_TEMPLATES_PAGES': {
+    CRX_FRONTEND_TEMPLATES_PAGES = {
         # templates that are available for all page types
-        '*': (
+        '*': [
             ('', 'Default'),
             ('coderedcms/pages/web_page.html', 'Web page showing title and cover image'),
             ('coderedcms/pages/web_page_notitle.html', 'Web page without title and cover image'),
             ('coderedcms/pages/home_page.html', 'Home page without title and cover image'),
             ('coderedcms/pages/base.html', 'Blank page - no navbar or footer'),
-        ),
-    },
-
-    'BANNER': None,
-    'BANNER_BACKGROUND': '#f00',
-    'BANNER_TEXT_COLOR': '#fff',
-}
+        ],
+    }
 
+    CRX_BANNER = None
+    CRX_BANNER_BACKGROUND = '#f00'
+    CRX_BANNER_TEXT_COLOR = '#fff'
 
-@lru_cache()
-def get_config():
-    config = DEFAULTS.copy()
-    for var in config:
-        cr_var = 'CODERED_%s' % var
-        if hasattr(settings, cr_var):
-            config[var] = getattr(settings, cr_var)
-    return config
+    def __getattribute__(self, attr: str):
+        # First load from Django settings.
+        # If it does not exist, load from _DefaultSettings.
+        try:
+            return getattr(settings, attr)
+        except AttributeError:
+            return super().__getattribute__(attr)
 
 
-cr_settings = get_config()
+crx_settings = _DefaultSettings()
 
 
 get_bootstrap_setting = bootstrap.get_bootstrap_setting

+ 3 - 3
coderedcms/templates/coderedcms/includes/codered_banner.html

@@ -1,6 +1,6 @@
 {% load coderedcms_tags %}
-{% if "BANNER"|codered_settings %}
-<div class="codered-banner" style="background-color:{{ 'BANNER_BACKGROUND'|codered_settings }}; color:{{ 'BANNER_TEXT_COLOR'|codered_settings }}; width:100%; padding:4px;">
-    {{ "BANNER"|codered_settings|safe }}
+{% if "CRX_BANNER"|crx_settings %}
+<div class="codered-banner" style="background-color:{{ 'CRX_BANNER_BACKGROUND'|crx_settings }}; color:{{ 'CRX_BANNER_TEXT_COLOR'|crx_settings }}; width:100%; padding:4px;">
+    {{ "CRX_BANNER"|crx_settings|safe }}
 </div>
 {% endif %}

+ 6 - 6
coderedcms/templatetags/coderedcms_tags.py

@@ -3,7 +3,6 @@ import random
 
 from bs4 import BeautifulSoup
 from django import template
-from django.conf import settings
 from django.forms import ClearableFileInput
 from django.utils.html import mark_safe
 from wagtail.core.models import Collection
@@ -13,7 +12,8 @@ from coderedcms import utils, __version__
 from coderedcms.blocks import CoderedAdvSettings
 from coderedcms.forms import SearchForm
 from coderedcms.models import Footer, Navbar
-from coderedcms.settings import cr_settings, get_bootstrap_setting
+from coderedcms.settings import crx_settings as crx_settings_obj
+from coderedcms.settings import get_bootstrap_setting
 from coderedcms.models.wagtailsettings_models import LayoutSettings
 
 register = template.Library()
@@ -102,7 +102,7 @@ def get_pageform(page, request):
 
 @register.simple_tag
 def process_form_cell(request, cell):
-    if isinstance(cell, str) and cell.startswith(cr_settings['PROTECTED_MEDIA_URL']):
+    if isinstance(cell, str) and cell.startswith(crx_settings_obj.CRX_PROTECTED_MEDIA_URL):
         return utils.get_protected_media_link(request, cell, render_link=True)
     if utils.uri_validator(str(cell)):
         return mark_safe("<a href='{0}'>{1}</a>".format(cell, cell))
@@ -110,8 +110,8 @@ def process_form_cell(request, cell):
 
 
 @register.filter
-def codered_settings(value):
-    return cr_settings.get(value, None)
+def crx_settings(value):
+    return getattr(crx_settings_obj, value)
 
 
 @register.filter
@@ -121,7 +121,7 @@ def bootstrap_settings(value):
 
 @register.filter
 def django_settings(value):
-    return getattr(settings, value)
+    return getattr(crx_settings_obj, value)
 
 
 @register.simple_tag

+ 3 - 6
coderedcms/tests/test_templates.py

@@ -1,10 +1,7 @@
-from unittest.mock import patch
-
 import pytest
 from django.contrib.auth import get_user_model
-from django.test import TestCase
+from django.test import override_settings, TestCase
 
-from coderedcms.templatetags.coderedcms_tags import cr_settings
 
 EXPECTED_BANNER_HTML = """
 <div class="codered-banner" style="background-color:#f00; color:#fff; width:100%; padding:4px;">
@@ -16,7 +13,7 @@ EXPECTED_BANNER_HTML = """
 @pytest.mark.django_db
 class TestSiteBanner(TestCase):
 
-    @patch.dict(cr_settings, {"BANNER": "Test"})
+    @override_settings(CRX_BANNER="Test")
     def test_with_banner(self):
         response = self.client.get("/")
         self.assertEqual(response.status_code, 200)
@@ -41,7 +38,7 @@ class TestWagtailAdminBanner(TestCase):
     def tearDown(self):
         self.client.logout()
 
-    @patch.dict(cr_settings, {"BANNER": "Test"})
+    @override_settings(CRX_BANNER="Test")
     def test_with_banner(self):
         response = self.client.get("/admin/")
         self.assertEqual(response.status_code, 200)

+ 2 - 2
coderedcms/urls.py

@@ -1,7 +1,7 @@
 from django.urls import include, path, re_path
 from wagtail.contrib.sitemaps.views import sitemap
 from wagtail.core import urls as wagtailcore_urls
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 from coderedcms.views import (
     event_generate_ical_for_calendar,
     event_generate_recurring_ical_for_event,
@@ -19,7 +19,7 @@ urlpatterns = [
     re_path(r'^robots\.txt$', robots, name='codered_robots'),
     re_path(r'^sitemap\.xml$', sitemap, name='codered_sitemap'),
     re_path(r'^{0}(?P<path>.*)$'.format(
-        cr_settings['PROTECTED_MEDIA_URL'].lstrip('/')),
+        crx_settings.CRX_PROTECTED_MEDIA_URL.lstrip('/')),
         serve_protected_file,
         name="serve_protected_file"
     ),

+ 2 - 2
coderedcms/utils.py

@@ -2,7 +2,7 @@ from django.core.validators import URLValidator
 from django.core.exceptions import ValidationError
 from django.utils.html import mark_safe
 
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 
 
 def get_protected_media_link(request, path, render_link=False):
@@ -27,7 +27,7 @@ def uri_validator(possible_uri):
 
 def attempt_protected_media_value_conversion(request, value):
     try:
-        if value.startswith(cr_settings['PROTECTED_MEDIA_URL']):
+        if value.startswith(crx_settings.CRX_PROTECTED_MEDIA_URL):
             new_value = get_protected_media_link(request, value)
             return new_value
     except AttributeError:

+ 2 - 2
coderedcms/views.py

@@ -23,7 +23,7 @@ from coderedcms.models import (
     LayoutSettings
 )
 from coderedcms.importexport import convert_csv_to_json, import_pages, ImportPagesFromCSVFileForm
-from coderedcms.settings import cr_settings
+from coderedcms.settings import crx_settings
 
 
 def search(request):
@@ -112,7 +112,7 @@ def serve_protected_file(request, path):
     Function that serves protected files uploaded from forms.
     """
     # Fully resolve all provided paths.
-    mediapath = os.path.abspath(cr_settings['PROTECTED_MEDIA_ROOT'])
+    mediapath = os.path.abspath(crx_settings.CRX_PROTECTED_MEDIA_ROOT)
     fullpath = os.path.abspath(os.path.join(mediapath, path))
 
     # Path must be a sub-path of the PROTECTED_MEDIA_ROOT, and exist.