base_blocks.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. """
  2. Bases, mixins, and utilities for blocks.
  3. """
  4. from django import forms
  5. from django.template.loader import render_to_string
  6. from django.utils.functional import cached_property
  7. from django.utils.safestring import mark_safe
  8. from django.utils.translation import gettext_lazy as _
  9. from wagtail import blocks
  10. from wagtail.models import Collection
  11. from wagtail.coreutils import resolve_model_string
  12. from wagtail.documents.blocks import DocumentChooserBlock
  13. from wagtailcrx.settings import crx_settings
  14. class ClassifierTermChooserBlock(blocks.FieldBlock):
  15. """
  16. Enables choosing a ClassifierTerm in the streamfield.
  17. Lazy loads the target_model from the string to avoid recursive imports.
  18. """
  19. widget = forms.Select
  20. def __init__(self, required=False, label=None, help_text=None, *args, **kwargs):
  21. self._required = required
  22. self._help_text = help_text
  23. self._label = label
  24. super().__init__(*args, **kwargs)
  25. @cached_property
  26. def target_model(self):
  27. return resolve_model_string('wagtailcrx.ClassifierTerm')
  28. @cached_property
  29. def field(self):
  30. return forms.ModelChoiceField(
  31. queryset=self.target_model.objects.all().order_by('classifier__name', 'name'),
  32. widget=self.widget,
  33. required=self._required,
  34. label=self._label,
  35. help_text=self._help_text,
  36. )
  37. def to_python(self, value):
  38. """
  39. Convert the serialized value back into a python object.
  40. """
  41. if isinstance(value, int):
  42. return self.target_model.objects.get(pk=value)
  43. return value
  44. def get_prep_value(self, value):
  45. """
  46. Serialize the model in a form suitable for wagtail's JSON-ish streamfield
  47. """
  48. if isinstance(value, self.target_model):
  49. return value.pk
  50. return value
  51. class CollectionChooserBlock(blocks.FieldBlock):
  52. """
  53. Enables choosing a wagtail Collection in the streamfield.
  54. """
  55. target_model = Collection
  56. widget = forms.Select
  57. def __init__(self, required=False, label=None, help_text=None, *args, **kwargs):
  58. self._required = required
  59. self._help_text = help_text
  60. self._label = label
  61. super().__init__(*args, **kwargs)
  62. @cached_property
  63. def field(self):
  64. return forms.ModelChoiceField(
  65. queryset=self.target_model.objects.all().order_by('name'),
  66. widget=self.widget,
  67. required=self._required,
  68. label=self._label,
  69. help_text=self._help_text,
  70. )
  71. def to_python(self, value):
  72. """
  73. Convert the serialized value back into a python object.
  74. """
  75. if isinstance(value, int):
  76. return self.target_model.objects.get(pk=value)
  77. return value
  78. def get_prep_value(self, value):
  79. """
  80. Serialize the model in a form suitable for wagtail's JSON-ish streamfield
  81. """
  82. if isinstance(value, self.target_model):
  83. return value.pk
  84. return value
  85. class ButtonMixin(blocks.StructBlock):
  86. """
  87. Standard style and size options for buttons.
  88. """
  89. button_title = blocks.CharBlock(
  90. max_length=255,
  91. required=True,
  92. label=_('Button Title'),
  93. )
  94. button_style = blocks.ChoiceBlock(
  95. choices=crx_settings.CRX_FRONTEND_BTN_STYLE_CHOICES,
  96. default=crx_settings.CRX_FRONTEND_BTN_STYLE_DEFAULT,
  97. required=False,
  98. label=_('Button Style'),
  99. )
  100. button_size = blocks.ChoiceBlock(
  101. choices=crx_settings.CRX_FRONTEND_BTN_SIZE_CHOICES,
  102. default=crx_settings.CRX_FRONTEND_BTN_SIZE_DEFAULT,
  103. required=False,
  104. label=_('Button Size'),
  105. )
  106. class CoderedAdvSettings(blocks.StructBlock):
  107. """
  108. Common fields each block should have,
  109. which are hidden under the block's "Advanced Settings" dropdown.
  110. """
  111. # placeholder, real value get set in __init__()
  112. custom_template = blocks.Block()
  113. custom_css_class = blocks.CharBlock(
  114. required=False,
  115. max_length=255,
  116. label=_('Custom CSS Class'),
  117. )
  118. custom_id = blocks.CharBlock(
  119. required=False,
  120. max_length=255,
  121. label=_('Custom ID'),
  122. )
  123. class Meta:
  124. form_template = 'wagtailadmin/block_forms/base_block_settings_struct.html'
  125. label = _('Advanced Settings')
  126. def __init__(self, local_blocks=None, template_choices=None, **kwargs):
  127. if not local_blocks:
  128. local_blocks = ()
  129. local_blocks += (
  130. (
  131. 'custom_template',
  132. blocks.ChoiceBlock(
  133. choices=template_choices,
  134. default=None,
  135. required=False,
  136. label=_('Template'))
  137. ),
  138. )
  139. super().__init__(local_blocks, **kwargs)
  140. class CoderedAdvTrackingSettings(CoderedAdvSettings):
  141. """
  142. CoderedAdvSettings plus additional tracking fields.
  143. """
  144. ga_tracking_event_category = blocks.CharBlock(
  145. required=False,
  146. max_length=255,
  147. label=_('Tracking Event Category'),
  148. )
  149. ga_tracking_event_label = blocks.CharBlock(
  150. required=False,
  151. max_length=255,
  152. label=_('Tracking Event Label'),
  153. )
  154. class CoderedAdvColumnSettings(CoderedAdvSettings):
  155. """
  156. BaseBlockSettings plus additional column fields.
  157. """
  158. column_breakpoint = blocks.ChoiceBlock(
  159. choices=crx_settings.CRX_FRONTEND_COL_BREAK_CHOICES,
  160. default=crx_settings.CRX_FRONTEND_COL_BREAK_DEFAULT,
  161. required=False,
  162. verbose_name=_('Column Breakpoint'),
  163. help_text=_('Screen size at which the column will expand horizontally or stack vertically.'), # noqa
  164. )
  165. class BaseBlock(blocks.StructBlock):
  166. """
  167. Common attributes for all blocks used in Wagtail CRX.
  168. """
  169. # subclasses can override this to determine the advanced settings class
  170. advsettings_class = CoderedAdvSettings
  171. # placeholder, real value get set in __init__() from advsettings_class
  172. settings = blocks.Block()
  173. def __init__(self, local_blocks=None, **kwargs):
  174. """
  175. Construct and inject settings block, then initialize normally.
  176. """
  177. klassname = self.__class__.__name__.lower()
  178. choices = (
  179. crx_settings.CRX_FRONTEND_TEMPLATES_BLOCKS.get('*', []) +
  180. crx_settings.CRX_FRONTEND_TEMPLATES_BLOCKS.get(klassname, [])
  181. )
  182. if not local_blocks:
  183. local_blocks = ()
  184. local_blocks += (('settings', self.advsettings_class(template_choices=choices)),)
  185. super().__init__(local_blocks, **kwargs)
  186. def render(self, value, context=None):
  187. template = value['settings']['custom_template']
  188. if not template:
  189. template = self.get_template(context=context)
  190. if not template:
  191. return self.render_basic(value, context=context)
  192. if context is None:
  193. new_context = self.get_context(value)
  194. else:
  195. new_context = self.get_context(value, parent_context=dict(context))
  196. return mark_safe(render_to_string(template, new_context))
  197. class BaseLayoutBlock(BaseBlock):
  198. """
  199. Common attributes for all blocks used in Wagtail CRX.
  200. """
  201. # Subclasses can override this to provide a default list of blocks for the content.
  202. content_streamblocks = []
  203. def __init__(self, local_blocks=None, **kwargs):
  204. if not local_blocks and self.content_streamblocks:
  205. local_blocks = self.content_streamblocks
  206. if local_blocks:
  207. local_blocks = (('content', blocks.StreamBlock(local_blocks, label=_('Content'))),)
  208. super().__init__(local_blocks, **kwargs)
  209. class LinkStructValue(blocks.StructValue):
  210. """
  211. Generates a URL for blocks with multiple link choices.
  212. """
  213. @property
  214. def url(self):
  215. page = self.get('page_link')
  216. doc = self.get('doc_link')
  217. ext = self.get('other_link')
  218. if page and ext:
  219. return "{0}{1}".format(page.url, ext)
  220. elif page:
  221. return page.url
  222. elif doc:
  223. return doc.url
  224. else:
  225. return ext
  226. class BaseLinkBlock(BaseBlock):
  227. """
  228. Common attributes for creating a link within the CMS.
  229. """
  230. page_link = blocks.PageChooserBlock(
  231. required=False,
  232. label=_('Page link'),
  233. )
  234. doc_link = DocumentChooserBlock(
  235. required=False,
  236. label=_('Document link'),
  237. )
  238. other_link = blocks.CharBlock(
  239. required=False,
  240. max_length=255,
  241. label=_('Other link'),
  242. )
  243. advsettings_class = CoderedAdvTrackingSettings
  244. class Meta:
  245. value_class = LinkStructValue