wagtailsettings_models.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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, InlinePanel, HelpPanel, MultiFieldPanel
  11. from wagtail.models import Orderable
  12. from wagtail.contrib.settings.models import BaseSetting, register_setting
  13. from wagtail.images import get_image_model_string
  14. from coderedcms.fields import MonospaceField
  15. from coderedcms.settings import crx_settings
  16. from coderedcms.models.snippet_models import Navbar, Footer
  17. @register_setting(icon='cr-desktop')
  18. class LayoutSettings(ClusterableModel, BaseSetting):
  19. """
  20. Branding, navbar, and theme settings.
  21. """
  22. class Meta:
  23. verbose_name = _('Layout')
  24. logo = models.ForeignKey(
  25. get_image_model_string(),
  26. null=True,
  27. blank=True,
  28. on_delete=models.SET_NULL,
  29. related_name='+',
  30. verbose_name=_('Logo'),
  31. help_text=_('Brand logo used in the navbar and throughout the site')
  32. )
  33. favicon = models.ForeignKey(
  34. get_image_model_string(),
  35. null=True,
  36. blank=True,
  37. on_delete=models.SET_NULL,
  38. related_name='favicon',
  39. verbose_name=_('Favicon'),
  40. )
  41. navbar_color_scheme = models.CharField(
  42. blank=True,
  43. max_length=50,
  44. choices=None,
  45. default='',
  46. verbose_name=_('Navbar color scheme'),
  47. help_text=_('Optimizes text and other navbar elements for use with light or dark backgrounds.'), # noqa
  48. )
  49. navbar_class = models.CharField(
  50. blank=True,
  51. max_length=255,
  52. default='',
  53. verbose_name=_('Navbar CSS class'),
  54. help_text=_('Custom classes applied to navbar e.g. "bg-light", "bg-dark", "bg-primary".'),
  55. )
  56. navbar_fixed = models.BooleanField(
  57. default=False,
  58. verbose_name=_('Fixed navbar'),
  59. help_text=_('Fixed navbar will remain at the top of the page when scrolling.'),
  60. )
  61. navbar_wrapper_fluid = models.BooleanField(
  62. default=True,
  63. verbose_name=_('Full width navbar'),
  64. help_text=_('The navbar will fill edge to edge.'),
  65. )
  66. navbar_content_fluid = models.BooleanField(
  67. default=False,
  68. verbose_name=_('Full width navbar contents'),
  69. help_text=_('Content within the navbar will fill edge to edge.'),
  70. )
  71. navbar_collapse_mode = models.CharField(
  72. blank=True,
  73. max_length=50,
  74. choices=None,
  75. default='',
  76. verbose_name=_('Collapse navbar menu'),
  77. help_text=_('Control on what screen sizes to show and collapse the navbar menu links.'),
  78. )
  79. navbar_format = models.CharField(
  80. blank=True,
  81. max_length=50,
  82. choices=None,
  83. default='',
  84. verbose_name=_('Navbar format'),
  85. )
  86. navbar_search = models.BooleanField(
  87. default=True,
  88. verbose_name=_('Search box'),
  89. help_text=_('Show search box in navbar')
  90. )
  91. frontend_theme = models.CharField(
  92. blank=True,
  93. max_length=50,
  94. choices=None,
  95. default='',
  96. verbose_name=_('Theme variant'),
  97. )
  98. panels = [
  99. MultiFieldPanel(
  100. [
  101. FieldPanel('logo'),
  102. FieldPanel('favicon'),
  103. ],
  104. heading=_('Branding')
  105. ),
  106. InlinePanel(
  107. 'site_navbar',
  108. help_text=_('Choose one or more navbars for your site.'),
  109. heading=_('Site Navbars')
  110. ),
  111. MultiFieldPanel(
  112. [
  113. FieldPanel('navbar_color_scheme'),
  114. FieldPanel('navbar_class'),
  115. FieldPanel('navbar_fixed'),
  116. FieldPanel('navbar_wrapper_fluid'),
  117. FieldPanel('navbar_content_fluid'),
  118. FieldPanel('navbar_collapse_mode'),
  119. FieldPanel('navbar_format'),
  120. FieldPanel('navbar_search'),
  121. ],
  122. heading=_('Site Navbar Layout')
  123. ),
  124. InlinePanel(
  125. 'site_footer',
  126. help_text=_('Choose one or more footers for your site.'),
  127. heading=_('Site Footers')
  128. ),
  129. MultiFieldPanel(
  130. [
  131. FieldPanel('frontend_theme'),
  132. ],
  133. heading=_('Theming')
  134. ),
  135. ]
  136. def __init__(self, *args, **kwargs):
  137. """
  138. Inject custom choices and defaults into the form fields
  139. to enable customization of settings without causing migration issues.
  140. """
  141. super().__init__(*args, **kwargs)
  142. # Set choices dynamically.
  143. self._meta.get_field('frontend_theme').choices = (
  144. crx_settings.CRX_FRONTEND_THEME_CHOICES
  145. )
  146. self._meta.get_field('navbar_collapse_mode').choices = (
  147. crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES
  148. )
  149. self._meta.get_field('navbar_color_scheme').choices = (
  150. crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES
  151. )
  152. self._meta.get_field('navbar_format').choices = (
  153. crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_CHOICES
  154. )
  155. # Set default dynamically.
  156. if not self.id:
  157. self.frontend_theme = crx_settings.CRX_FRONTEND_THEME_DEFAULT
  158. self.navbar_class = crx_settings.CRX_FRONTEND_NAVBAR_CLASS_DEFAULT
  159. self.navbar_collapse_mode = crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_DEFAULT
  160. self.navbar_color_scheme = crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_DEFAULT
  161. self.navbar_format = crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_DEFAULT
  162. class NavbarOrderable(Orderable, models.Model):
  163. navbar_chooser = ParentalKey(
  164. LayoutSettings,
  165. related_name="site_navbar",
  166. verbose_name=_('Site Navbars')
  167. )
  168. navbar = models.ForeignKey(
  169. Navbar,
  170. blank=True,
  171. null=True,
  172. on_delete=models.CASCADE,
  173. )
  174. panels = [
  175. FieldPanel("navbar")
  176. ]
  177. class FooterOrderable(Orderable, models.Model):
  178. footer_chooser = ParentalKey(
  179. LayoutSettings,
  180. related_name="site_footer",
  181. verbose_name=_('Site Footers')
  182. )
  183. footer = models.ForeignKey(
  184. Footer,
  185. blank=True,
  186. null=True,
  187. on_delete=models.CASCADE,
  188. )
  189. panels = [
  190. FieldPanel("footer")
  191. ]
  192. @register_setting(icon='cr-google')
  193. class AnalyticsSettings(BaseSetting):
  194. """
  195. Tracking and Google Analytics.
  196. """
  197. class Meta:
  198. verbose_name = _('Tracking')
  199. ga_tracking_id = models.CharField(
  200. blank=True,
  201. max_length=255,
  202. verbose_name=_('UA Tracking ID'),
  203. help_text=_('Your Google "Universal Analytics" tracking ID (begins with "UA-")'),
  204. )
  205. ga_g_tracking_id = models.CharField(
  206. blank=True,
  207. max_length=255,
  208. verbose_name=_('G Tracking ID'),
  209. help_text=_('Your Google Analytics 4 tracking ID (begins with "G-")'),
  210. )
  211. ga_track_button_clicks = models.BooleanField(
  212. default=False,
  213. verbose_name=_('Track button clicks'),
  214. help_text=_('Track all button clicks using Google Analytics event tracking. Event tracking details can be specified in each button’s advanced settings options.'), # noqa
  215. )
  216. gtm_id = models.CharField(
  217. blank=True,
  218. max_length=255,
  219. verbose_name=_('Google Tag Manager ID'),
  220. help_text=_('Begins with "GTM-"'),
  221. )
  222. head_scripts = MonospaceField(
  223. blank=True,
  224. null=True,
  225. verbose_name=_('<head> tracking scripts'),
  226. help_text=_('Add tracking scripts between the <head> tags.'),
  227. )
  228. body_scripts = MonospaceField(
  229. blank=True,
  230. null=True,
  231. verbose_name=_('<body> tracking scripts'),
  232. help_text=_('Add tracking scripts toward closing <body> tag.'),
  233. )
  234. panels = [
  235. HelpPanel(
  236. heading=_('Know your tracking'),
  237. content=_(
  238. '<h3><b>Which tracking IDs do I need?</b></h3>'
  239. '<p>Before adding tracking to your site, '
  240. '<a href="https://docs.coderedcorp.com/wagtail-crx/how_to/add_tracking_scripts.html" ' # noqa
  241. 'target="_blank">read about the difference between UA, G, GTM, '
  242. 'and other tracking IDs</a>.</p>'
  243. ),
  244. ),
  245. MultiFieldPanel(
  246. [
  247. FieldPanel('ga_tracking_id'),
  248. FieldPanel('ga_g_tracking_id'),
  249. FieldPanel('ga_track_button_clicks'),
  250. ],
  251. heading=_('Google Analytics'),
  252. ),
  253. MultiFieldPanel(
  254. [
  255. FieldPanel('gtm_id'),
  256. ],
  257. heading=_('Google Tag Manager'),
  258. ),
  259. MultiFieldPanel(
  260. [
  261. FieldPanel('head_scripts'),
  262. FieldPanel('body_scripts'),
  263. ],
  264. heading=_('Other Tracking Scripts')
  265. )
  266. ]
  267. @register_setting(icon='cr-universal-access')
  268. class ADASettings(BaseSetting):
  269. """
  270. Accessibility related options.
  271. """
  272. class Meta:
  273. verbose_name = 'Accessibility'
  274. skip_navigation = models.BooleanField(
  275. default=False,
  276. verbose_name=_('Show skip navigation link'),
  277. help_text=_('Shows a "Skip Navigation" link above the navbar that takes you directly to the main content.'), # noqa
  278. )
  279. panels = [
  280. MultiFieldPanel(
  281. [
  282. FieldPanel('skip_navigation'),
  283. ],
  284. heading=_('Accessibility')
  285. )
  286. ]
  287. @register_setting(icon='cog')
  288. class GeneralSettings(BaseSetting):
  289. """
  290. Various site-wide settings. A good place to put
  291. one-off settings that don't belong anywhere else.
  292. """
  293. from_email_address = models.CharField(
  294. blank=True,
  295. max_length=255,
  296. verbose_name=_('From email address'),
  297. 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)'), # noqa
  298. )
  299. search_num_results = models.PositiveIntegerField(
  300. default=10,
  301. verbose_name=_('Number of results per page'),
  302. )
  303. external_new_tab = models.BooleanField(
  304. default=False,
  305. verbose_name=_('Open all external links in new tab')
  306. )
  307. panels = [
  308. MultiFieldPanel(
  309. [
  310. FieldPanel('from_email_address'),
  311. ],
  312. _('Email')
  313. ),
  314. MultiFieldPanel(
  315. [
  316. FieldPanel('search_num_results'),
  317. ],
  318. _('Search Settings')
  319. ),
  320. MultiFieldPanel(
  321. [
  322. FieldPanel('external_new_tab'),
  323. ],
  324. _('Links')
  325. ),
  326. ]
  327. class Meta:
  328. verbose_name = _('General')
  329. @register_setting(icon='cr-puzzle-piece')
  330. class GoogleApiSettings(BaseSetting):
  331. """
  332. Settings for Google API services.
  333. """
  334. class Meta:
  335. verbose_name = _('Google API')
  336. google_maps_api_key = models.CharField(
  337. blank=True,
  338. max_length=255,
  339. verbose_name=_('Google Maps API Key'),
  340. help_text=_('The API Key used for Google Maps.')
  341. )
  342. @register_setting(icon='cr-puzzle-piece')
  343. class MailchimpApiSettings(BaseSetting):
  344. """
  345. Settings for Mailchimp API services.
  346. """
  347. class Meta:
  348. verbose_name = _('Mailchimp API')
  349. mailchimp_api_key = models.CharField(
  350. blank=True,
  351. max_length=255,
  352. verbose_name=_('Mailchimp API Key'),
  353. help_text=_('The API Key used for Mailchimp.')
  354. )