wagtailsettings_models.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. """
  2. Custom wagtail settings used by Wagtail CRX.
  3. Settings are user-configurable on a per-site basis (multisite).
  4. Global project or developer settings should be defined in coderedcms.settings.py .
  5. """
  6. from django.db import models
  7. from django.utils.translation import gettext_lazy as _
  8. from modelcluster.fields import ParentalKey
  9. from modelcluster.models import ClusterableModel
  10. from wagtail.admin.panels import FieldPanel
  11. from wagtail.admin.panels import HelpPanel
  12. from wagtail.admin.panels import InlinePanel
  13. from wagtail.admin.panels import MultiFieldPanel
  14. from wagtail.contrib.settings.models import BaseSiteSetting
  15. from wagtail.contrib.settings.models import register_setting
  16. from wagtail.images import get_image_model_string
  17. from wagtail.models import Orderable
  18. from coderedcms.fields import MonospaceField
  19. from coderedcms.models.snippet_models import Footer
  20. from coderedcms.models.snippet_models import Navbar
  21. from coderedcms.settings import crx_settings
  22. def maybe_register_setting(disable: bool, **kwargs):
  23. """Decorator that conditionally registers a settings class."""
  24. def check_if_disabled(model):
  25. if not disable:
  26. register_setting(model, **kwargs)
  27. return model
  28. return check_if_disabled
  29. @maybe_register_setting(crx_settings.CRX_DISABLE_LAYOUT, icon="cr-desktop")
  30. class LayoutSettings(ClusterableModel, BaseSiteSetting):
  31. """
  32. Branding, navbar, and theme settings.
  33. """
  34. class Meta:
  35. verbose_name = _("CRX Settings")
  36. logo = models.ForeignKey(
  37. get_image_model_string(),
  38. null=True,
  39. blank=True,
  40. on_delete=models.SET_NULL,
  41. related_name="+",
  42. verbose_name=_("Logo"),
  43. help_text=_("Brand logo used in the navbar and throughout the site"),
  44. )
  45. favicon = models.ForeignKey(
  46. get_image_model_string(),
  47. null=True,
  48. blank=True,
  49. on_delete=models.SET_NULL,
  50. related_name="favicon",
  51. verbose_name=_("Favicon"),
  52. )
  53. navbar_color_scheme = models.CharField(
  54. blank=True,
  55. max_length=50,
  56. choices=None,
  57. default="",
  58. verbose_name=_("Navbar color scheme"),
  59. help_text=_(
  60. "Optimizes text and other navbar elements for use with light or "
  61. "dark backgrounds."
  62. ),
  63. )
  64. navbar_class = models.CharField(
  65. blank=True,
  66. max_length=255,
  67. default="",
  68. verbose_name=_("Navbar CSS class"),
  69. help_text=_(
  70. 'Custom classes applied to navbar e.g. "bg-light", "bg-dark", "bg-primary".'
  71. ),
  72. )
  73. navbar_fixed = models.BooleanField(
  74. default=False,
  75. verbose_name=_("Fixed navbar"),
  76. help_text=_(
  77. "Fixed navbar will remain at the top of the page when scrolling."
  78. ),
  79. )
  80. navbar_content_fluid = models.BooleanField(
  81. default=False,
  82. verbose_name=_("Full width navbar contents"),
  83. help_text=_("Content within the navbar will fill edge to edge."),
  84. )
  85. navbar_collapse_mode = models.CharField(
  86. blank=True,
  87. max_length=50,
  88. choices=None,
  89. default="",
  90. verbose_name=_("Collapse navbar menu"),
  91. help_text=_(
  92. "Control on what screen sizes to show and collapse the navbar menu links."
  93. ),
  94. )
  95. navbar_format = models.CharField(
  96. blank=True,
  97. max_length=50,
  98. choices=None,
  99. default="",
  100. verbose_name=_("Navbar format"),
  101. )
  102. navbar_search = models.BooleanField(
  103. default=True,
  104. verbose_name=_("Search box"),
  105. help_text=_("Show search box in navbar"),
  106. )
  107. from_email_address = models.CharField(
  108. blank=True,
  109. max_length=255,
  110. verbose_name=_("From email address"),
  111. help_text=_(
  112. "The default email address this site appears to send from. "
  113. 'For example: "sender@example.com" or '
  114. '"Sender Name <sender@example.com>" (without quotes)'
  115. ),
  116. )
  117. search_num_results = models.PositiveIntegerField(
  118. default=10,
  119. verbose_name=_("Number of results per page"),
  120. )
  121. external_new_tab = models.BooleanField(
  122. default=False, verbose_name=_("Open all external links in new tab")
  123. )
  124. google_maps_api_key = models.CharField(
  125. blank=True,
  126. max_length=255,
  127. verbose_name=_("Google Maps API Key"),
  128. help_text=_("The API Key used for Google Maps."),
  129. )
  130. mailchimp_api_key = models.CharField(
  131. blank=True,
  132. max_length=255,
  133. verbose_name=_("Mailchimp API Key"),
  134. help_text=_("The API Key used for Mailchimp."),
  135. )
  136. navbar_panels = [
  137. MultiFieldPanel(
  138. [
  139. "navbar_color_scheme",
  140. "navbar_class",
  141. "navbar_fixed",
  142. "navbar_content_fluid",
  143. "navbar_collapse_mode",
  144. "navbar_format",
  145. "navbar_search",
  146. ],
  147. heading=_("Site Navbar Layout"),
  148. ),
  149. InlinePanel(
  150. "site_navbar",
  151. help_text=_("Choose one or more navbars for your site."),
  152. heading=_("Site Navbars"),
  153. ),
  154. ]
  155. footer_panels = [
  156. InlinePanel(
  157. "site_footer",
  158. help_text=_("Choose one or more footers for your site."),
  159. heading=_("Site Footers"),
  160. ),
  161. ]
  162. panels = [
  163. MultiFieldPanel(
  164. [
  165. "logo",
  166. "favicon",
  167. ],
  168. heading=_("Branding"),
  169. ),
  170. MultiFieldPanel(
  171. [
  172. "from_email_address",
  173. "search_num_results",
  174. "external_new_tab",
  175. ],
  176. heading=_("General"),
  177. ),
  178. MultiFieldPanel(
  179. [
  180. "google_maps_api_key",
  181. "mailchimp_api_key",
  182. ],
  183. heading=_("API Keys"),
  184. ),
  185. ]
  186. if not crx_settings.CRX_DISABLE_NAVBAR:
  187. panels += navbar_panels
  188. if not crx_settings.CRX_DISABLE_FOOTER:
  189. panels += footer_panels
  190. def __init__(self, *args, **kwargs):
  191. """
  192. Inject custom choices and defaults into the form fields
  193. to enable customization of settings without causing migration issues.
  194. """
  195. super().__init__(*args, **kwargs)
  196. # Set choices dynamically.
  197. self._meta.get_field(
  198. "navbar_collapse_mode"
  199. ).choices = crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES
  200. self._meta.get_field(
  201. "navbar_color_scheme"
  202. ).choices = crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES
  203. self._meta.get_field(
  204. "navbar_format"
  205. ).choices = crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_CHOICES
  206. # Set default dynamically.
  207. if not self.id:
  208. self.navbar_class = crx_settings.CRX_FRONTEND_NAVBAR_CLASS_DEFAULT
  209. self.navbar_collapse_mode = (
  210. crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_DEFAULT
  211. )
  212. self.navbar_color_scheme = (
  213. crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_DEFAULT
  214. )
  215. self.navbar_format = crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_DEFAULT
  216. class NavbarOrderable(Orderable, models.Model):
  217. navbar_chooser = ParentalKey(
  218. LayoutSettings,
  219. related_name="site_navbar",
  220. verbose_name=_("Site Navbars"),
  221. )
  222. navbar = models.ForeignKey(
  223. Navbar,
  224. blank=True,
  225. null=True,
  226. on_delete=models.CASCADE,
  227. )
  228. panels = ["navbar"]
  229. class FooterOrderable(Orderable, models.Model):
  230. footer_chooser = ParentalKey(
  231. LayoutSettings,
  232. related_name="site_footer",
  233. verbose_name=_("Site Footers"),
  234. )
  235. footer = models.ForeignKey(
  236. Footer,
  237. blank=True,
  238. null=True,
  239. on_delete=models.CASCADE,
  240. )
  241. panels = ["footer"]
  242. @maybe_register_setting(crx_settings.CRX_DISABLE_ANALYTICS, icon="cr-google")
  243. class AnalyticsSettings(BaseSiteSetting):
  244. """
  245. Tracking and Google Analytics.
  246. """
  247. class Meta:
  248. verbose_name = _("Tracking")
  249. ga_g_tracking_id = models.CharField(
  250. blank=True,
  251. max_length=255,
  252. verbose_name=_("G Tracking ID"),
  253. help_text=_('Your Google Analytics 4 tracking ID (begins with "G-")'),
  254. )
  255. ga_track_button_clicks = models.BooleanField(
  256. default=False,
  257. verbose_name=_("Track button clicks"),
  258. help_text=_(
  259. "Track all button clicks using Google Analytics event tracking. "
  260. "Event tracking details can be specified in each button’s advanced "
  261. "settings options."
  262. ),
  263. )
  264. gtm_id = models.CharField(
  265. blank=True,
  266. max_length=255,
  267. verbose_name=_("Google Tag Manager ID"),
  268. help_text=_('Begins with "GTM-"'),
  269. )
  270. head_scripts = MonospaceField(
  271. blank=True,
  272. null=True,
  273. verbose_name=_("<head> tracking scripts"),
  274. help_text=_("Add tracking scripts between the <head> tags."),
  275. )
  276. body_scripts = MonospaceField(
  277. blank=True,
  278. null=True,
  279. verbose_name=_("<body> tracking scripts"),
  280. help_text=_("Add tracking scripts toward closing <body> tag."),
  281. )
  282. panels = [
  283. HelpPanel(
  284. heading=_("Know your tracking"),
  285. content=_(
  286. "<h2>Which tracking IDs do I need?</h2>"
  287. "<p>Before adding tracking to your site, "
  288. '<a href="https://docs.coderedcorp.com/wagtail-crx/how_to/add_tracking_scripts.html" ' # noqa
  289. 'target="_blank">read about the difference between G, UA, GTM, '
  290. "and other tracking IDs</a>.</p>"
  291. ),
  292. ),
  293. MultiFieldPanel(
  294. [
  295. "ga_g_tracking_id",
  296. "ga_track_button_clicks",
  297. ],
  298. heading=_("Google Analytics"),
  299. ),
  300. MultiFieldPanel(
  301. [
  302. "gtm_id",
  303. ],
  304. heading=_("Google Tag Manager"),
  305. ),
  306. MultiFieldPanel(
  307. [
  308. "head_scripts",
  309. "body_scripts",
  310. ],
  311. heading=_("Other Tracking Scripts"),
  312. ),
  313. ]