123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- """
- Custom wagtail settings used by Wagtail CRX.
- Settings are user-configurable on a per-site basis (multisite).
- Global project or developer settings should be defined in coderedcms.settings.py .
- """
- from django.core.exceptions import ValidationError
- from django.db import models
- from django.utils.translation import gettext_lazy as _
- from modelcluster.fields import ParentalKey
- from modelcluster.models import ClusterableModel
- from wagtail.admin.panels import FieldPanel
- from wagtail.admin.panels import HelpPanel
- from wagtail.admin.panels import InlinePanel
- from wagtail.admin.panels import MultiFieldPanel
- from wagtail.contrib.settings.models import BaseSiteSetting
- from wagtail.contrib.settings.models import register_setting
- from wagtail.images import get_image_model_string
- from wagtail.models import Orderable
- from coderedcms.fields import MonospaceField
- from coderedcms.models.snippet_models import Footer
- from coderedcms.models.snippet_models import Navbar
- from coderedcms.settings import crx_settings
- def maybe_register_setting(disable: bool, **kwargs):
- """Decorator that conditionally registers a settings class."""
- def check_if_disabled(model):
- if not disable:
- register_setting(model, **kwargs)
- return model
- return check_if_disabled
- @maybe_register_setting(crx_settings.CRX_DISABLE_LAYOUT, icon="cr-desktop")
- class LayoutSettings(ClusterableModel, BaseSiteSetting):
- """
- Branding, navbar, and theme settings.
- """
- class Meta:
- verbose_name = _("CRX Settings")
- class SpamService(models.TextChoices):
- NONE = ("", _("None"))
- HONEYPOT = ("honeypot", _("Basic - honeypot technique"))
- RECAPTCHA_V3 = (
- "recaptcha3",
- _("reCAPTCHA v3 - Invisible (requires API key)"),
- )
- RECAPTCHA_V2 = (
- "recaptcha2",
- _("reCAPTCHA v2 - I am not a robot (requires API key)"),
- )
- logo = models.ForeignKey(
- get_image_model_string(),
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- related_name="+",
- verbose_name=_("Logo"),
- help_text=_("Brand logo used in the navbar and throughout the site"),
- )
- favicon = models.ForeignKey(
- get_image_model_string(),
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- related_name="favicon",
- verbose_name=_("Favicon"),
- )
- navbar_color_scheme = models.CharField(
- blank=True,
- max_length=50,
- choices=None,
- default="",
- verbose_name=_("Navbar color scheme"),
- help_text=_(
- "Optimizes text and other navbar elements for use with light or "
- "dark backgrounds."
- ),
- )
- navbar_class = models.CharField(
- blank=True,
- max_length=255,
- default="",
- verbose_name=_("Navbar CSS class"),
- help_text=_(
- 'Custom classes applied to navbar e.g. "bg-light", "bg-dark", "bg-primary".'
- ),
- )
- navbar_fixed = models.BooleanField(
- default=False,
- verbose_name=_("Fixed navbar"),
- help_text=_(
- "Fixed navbar will remain at the top of the page when scrolling."
- ),
- )
- navbar_content_fluid = models.BooleanField(
- default=False,
- verbose_name=_("Full width navbar contents"),
- help_text=_("Content within the navbar will fill edge to edge."),
- )
- navbar_collapse_mode = models.CharField(
- blank=True,
- max_length=50,
- 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=None,
- default="",
- verbose_name=_("Navbar format"),
- )
- navbar_search = models.BooleanField(
- default=True,
- verbose_name=_("Search box"),
- help_text=_("Show search box in navbar"),
- )
- from_email_address = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("From email address"),
- help_text=_(
- "The default email address this site appears to send from. "
- 'For example: "sender@example.com" or '
- '"Sender Name <sender@example.com>" (without quotes)'
- ),
- )
- search_num_results = models.PositiveIntegerField(
- default=10,
- verbose_name=_("Number of results per page"),
- )
- external_new_tab = models.BooleanField(
- default=False, verbose_name=_("Open all external links in new tab")
- )
- spam_service = models.CharField(
- blank=True,
- max_length=10,
- choices=SpamService.choices,
- default=SpamService.HONEYPOT,
- verbose_name=_("Spam Protection"),
- help_text=_(
- "Choose a technique or 3rd party service to help block spam submissions."
- ),
- )
- recaptcha_threshold = models.DecimalField(
- default=0.5,
- max_digits=2,
- decimal_places=1,
- verbose_name=_("reCAPTCHA Threshold"),
- help_text=_(
- "reCAPTCHA v3 returns a score (0.0 is very likely a bot, "
- "1.0 is very likely a good interaction). "
- "Reject submissions below this score (recommended 0.5)."
- ),
- )
- recaptcha_public_key = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("reCAPTCHA Site Key (Public)"),
- help_text=_(
- "Create this key in the Google reCAPTCHA or Google Cloud dashboard."
- ),
- )
- recaptcha_secret_key = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("reCAPTCHA Secret Key (Private)"),
- help_text=_(
- "Create this key in the Google reCAPTCHA or Google Cloud dashboard."
- ),
- )
- google_maps_api_key = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("Google Maps API Key"),
- help_text=_("The API Key used for Google Maps."),
- )
- mailchimp_api_key = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("Mailchimp API Key"),
- help_text=_("The API Key used for Mailchimp."),
- )
- navbar_panels = [
- MultiFieldPanel(
- [
- FieldPanel("navbar_color_scheme"),
- FieldPanel("navbar_class"),
- FieldPanel("navbar_fixed"),
- FieldPanel("navbar_content_fluid"),
- FieldPanel("navbar_collapse_mode"),
- FieldPanel("navbar_format"),
- FieldPanel("navbar_search"),
- ],
- heading=_("Site Navbar Layout"),
- ),
- InlinePanel(
- "site_navbar",
- help_text=_("Choose one or more navbars for your site."),
- heading=_("Site Navbars"),
- ),
- ]
- footer_panels = [
- InlinePanel(
- "site_footer",
- help_text=_("Choose one or more footers for your site."),
- heading=_("Site Footers"),
- ),
- ]
- panels = [
- MultiFieldPanel(
- [
- FieldPanel("logo"),
- FieldPanel("favicon"),
- ],
- heading=_("Branding"),
- ),
- MultiFieldPanel(
- [
- FieldPanel("from_email_address"),
- FieldPanel("search_num_results"),
- FieldPanel("external_new_tab"),
- ],
- heading=_("General"),
- ),
- MultiFieldPanel(
- [
- FieldPanel("spam_service"),
- FieldPanel("recaptcha_threshold"),
- FieldPanel("recaptcha_public_key"),
- FieldPanel("recaptcha_secret_key"),
- ],
- heading=_("Form Settings"),
- ),
- MultiFieldPanel(
- [
- FieldPanel("google_maps_api_key"),
- FieldPanel("mailchimp_api_key"),
- ],
- heading=_("API Keys"),
- ),
- ]
- if not crx_settings.CRX_DISABLE_NAVBAR:
- panels += navbar_panels
- if not crx_settings.CRX_DISABLE_FOOTER:
- panels += footer_panels
- 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(
- "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.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
- def clean(self):
- """
- Make sure reCAPTCHA keys are set if selected.
- """
- if self.spam_service in [
- self.SpamService.RECAPTCHA_V3,
- self.SpamService.RECAPTCHA_V2,
- ] and not (self.recaptcha_public_key and self.recaptcha_secret_key):
- raise ValidationError(_("API keys are required to use reCAPTCHA."))
- return super().clean()
- class NavbarOrderable(Orderable, models.Model):
- navbar_chooser = ParentalKey(
- LayoutSettings,
- related_name="site_navbar",
- verbose_name=_("Site Navbars"),
- )
- navbar = models.ForeignKey(
- Navbar,
- blank=True,
- null=True,
- on_delete=models.CASCADE,
- )
- panels = [FieldPanel("navbar")]
- class FooterOrderable(Orderable, models.Model):
- footer_chooser = ParentalKey(
- LayoutSettings,
- related_name="site_footer",
- verbose_name=_("Site Footers"),
- )
- footer = models.ForeignKey(
- Footer,
- blank=True,
- null=True,
- on_delete=models.CASCADE,
- )
- panels = [FieldPanel("footer")]
- @maybe_register_setting(crx_settings.CRX_DISABLE_ANALYTICS, icon="cr-google")
- class AnalyticsSettings(BaseSiteSetting):
- """
- Tracking and Google Analytics.
- """
- class Meta:
- verbose_name = _("Tracking")
- ga_g_tracking_id = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("G Tracking ID"),
- help_text=_('Your Google Analytics 4 tracking ID (begins with "G-")'),
- )
- ga_track_button_clicks = models.BooleanField(
- default=False,
- verbose_name=_("Track button clicks"),
- help_text=_(
- "Track all button clicks using Google Analytics event tracking. "
- "Event tracking details can be specified in each button’s advanced "
- "settings options."
- ),
- )
- gtm_id = models.CharField(
- blank=True,
- max_length=255,
- verbose_name=_("Google Tag Manager ID"),
- help_text=_('Begins with "GTM-"'),
- )
- head_scripts = MonospaceField(
- blank=True,
- null=True,
- verbose_name=_("<head> tracking scripts"),
- help_text=_("Add tracking scripts between the <head> tags."),
- )
- body_scripts = MonospaceField(
- blank=True,
- null=True,
- verbose_name=_("<body> tracking scripts"),
- help_text=_("Add tracking scripts toward closing <body> tag."),
- )
- panels = [
- HelpPanel(
- heading=_("Know your tracking"),
- content=_(
- "<h2>Which tracking IDs do I need?</h2>"
- "<p>Before adding tracking to your site, "
- '<a href="https://docs.coderedcorp.com/wagtail-crx/how_to/add_tracking_scripts.html" ' # noqa
- 'target="_blank">read about the difference between G, UA, GTM, '
- "and other tracking IDs</a>.</p>"
- ),
- ),
- MultiFieldPanel(
- [
- FieldPanel("ga_g_tracking_id"),
- FieldPanel("ga_track_button_clicks"),
- ],
- heading=_("Google Analytics"),
- ),
- MultiFieldPanel(
- [
- FieldPanel("gtm_id"),
- ],
- heading=_("Google Tag Manager"),
- ),
- MultiFieldPanel(
- [
- FieldPanel("head_scripts"),
- FieldPanel("body_scripts"),
- ],
- heading=_("Other Tracking Scripts"),
- ),
- ]
|