|
@@ -4,6 +4,7 @@ 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
|
|
@@ -44,6 +45,18 @@ class LayoutSettings(ClusterableModel, BaseSiteSetting):
|
|
|
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,
|
|
@@ -132,6 +145,43 @@ class LayoutSettings(ClusterableModel, BaseSiteSetting):
|
|
|
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,
|
|
@@ -189,6 +239,15 @@ class LayoutSettings(ClusterableModel, BaseSiteSetting):
|
|
|
],
|
|
|
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"),
|
|
@@ -229,6 +288,17 @@ class LayoutSettings(ClusterableModel, BaseSiteSetting):
|
|
|
)
|
|
|
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(
|