2
0

blocks.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. from django import forms
  2. from django.db.models import BLANK_CHOICE_DASH
  3. from django.utils.dateparse import parse_datetime
  4. from django.utils.text import slugify
  5. from django.utils.translation import gettext_lazy as _
  6. from anyascii import anyascii
  7. from wagtail.blocks import (
  8. StructBlock,
  9. TextBlock,
  10. CharBlock,
  11. BooleanBlock,
  12. ListBlock,
  13. StreamBlock,
  14. DateBlock,
  15. TimeBlock,
  16. DateTimeBlock,
  17. ChoiceBlock,
  18. RichTextBlock,
  19. )
  20. class FormFieldBlock(StructBlock):
  21. field_label = CharBlock(label=_("Label"))
  22. help_text = TextBlock(required=False, label=_("Help text"))
  23. field_class = forms.CharField
  24. widget = None
  25. def get_slug(self, struct_value):
  26. return slugify(anyascii(struct_value["field_label"]))
  27. def get_field_class(self, struct_value):
  28. return self.field_class
  29. def get_widget(self, struct_value):
  30. return self.widget
  31. def get_field_kwargs(self, struct_value):
  32. kwargs = {
  33. "label": struct_value["field_label"],
  34. "help_text": struct_value["help_text"],
  35. "required": struct_value.get("required", False),
  36. }
  37. if "default_value" in struct_value:
  38. kwargs["initial"] = struct_value["default_value"]
  39. form_widget = self.get_widget(struct_value)
  40. if form_widget is not None:
  41. kwargs["widget"] = form_widget
  42. return kwargs
  43. def get_field(self, struct_value):
  44. return self.get_field_class(struct_value)(
  45. **self.get_field_kwargs(struct_value)
  46. )
  47. class OptionalFormFieldBlock(FormFieldBlock):
  48. required = BooleanBlock(label=_("Required"), required=False)
  49. CHARFIELD_FORMATS = [
  50. ("email", _("Email")),
  51. ("url", _("URL")),
  52. ]
  53. try:
  54. from phonenumber_field.formfields import PhoneNumberField
  55. except ImportError:
  56. pass
  57. else:
  58. CHARFIELD_FORMATS.append(("phone", _("Phone")))
  59. class CharFieldBlock(OptionalFormFieldBlock):
  60. format = ChoiceBlock(
  61. choices=CHARFIELD_FORMATS, required=False, label=_("Format")
  62. )
  63. default_value = CharBlock(required=False, label=_("Default value"))
  64. class Meta:
  65. label = _("Text field (single line)")
  66. def get_field_class(self, struct_value):
  67. text_format = struct_value["format"]
  68. if text_format == "url":
  69. return forms.URLField
  70. if text_format == "email":
  71. return forms.EmailField
  72. if text_format == "phone":
  73. return PhoneNumberField
  74. return super().get_field_class(struct_value)
  75. class TextFieldBlock(OptionalFormFieldBlock):
  76. default_value = TextBlock(required=False, label=_("Default value"))
  77. widget = forms.Textarea(attrs={"rows": 5})
  78. class Meta:
  79. label = _("Text field (multi line)")
  80. class NumberFieldBlock(OptionalFormFieldBlock):
  81. default_value = CharBlock(required=False, label=_("Default value"))
  82. widget = forms.NumberInput
  83. class Meta:
  84. label = _("Number field")
  85. class CheckboxFieldBlock(FormFieldBlock):
  86. default_value = BooleanBlock(required=False)
  87. field_class = forms.BooleanField
  88. class Meta:
  89. label = _("Checkbox field")
  90. icon = "tick-inverse"
  91. class RadioButtonsFieldBlock(OptionalFormFieldBlock):
  92. choices = ListBlock(CharBlock(label=_("Choice")))
  93. field_class = forms.ChoiceField
  94. widget = forms.RadioSelect
  95. class Meta:
  96. label = _("Radio buttons")
  97. icon = "radio-empty"
  98. def get_field_kwargs(self, struct_value):
  99. kwargs = super().get_field_kwargs(struct_value)
  100. kwargs["choices"] = [
  101. (choice, choice) for choice in struct_value["choices"]
  102. ]
  103. return kwargs
  104. class DropdownFieldBlock(RadioButtonsFieldBlock):
  105. widget = forms.Select
  106. class Meta:
  107. label = _("Dropdown field")
  108. icon = "arrow-down-big"
  109. def get_field_kwargs(self, struct_value):
  110. kwargs = super(DropdownFieldBlock, self).get_field_kwargs(struct_value)
  111. kwargs["choices"].insert(0, BLANK_CHOICE_DASH[0])
  112. return kwargs
  113. class CheckboxesFieldBlock(OptionalFormFieldBlock):
  114. checkboxes = ListBlock(CharBlock(label=_("Checkbox")))
  115. field_class = forms.MultipleChoiceField
  116. widget = forms.CheckboxSelectMultiple
  117. class Meta:
  118. label = _("Multiple checkboxes field")
  119. icon = "list-ul"
  120. def get_field_kwargs(self, struct_value):
  121. kwargs = super(CheckboxesFieldBlock, self).get_field_kwargs(
  122. struct_value
  123. )
  124. kwargs["choices"] = [
  125. (choice, choice) for choice in struct_value["checkboxes"]
  126. ]
  127. return kwargs
  128. class DatePickerInput(forms.DateInput):
  129. def __init__(self, *args, **kwargs):
  130. attrs = kwargs.get("attrs")
  131. if attrs is None:
  132. attrs = {}
  133. attrs.update(
  134. {
  135. "data-provide": "datepicker",
  136. "data-date-format": "yyyy-mm-dd",
  137. }
  138. )
  139. kwargs["attrs"] = attrs
  140. super().__init__(*args, **kwargs)
  141. class DateFieldBlock(OptionalFormFieldBlock):
  142. default_value = DateBlock(required=False)
  143. field_class = forms.DateField
  144. widget = DatePickerInput
  145. class Meta:
  146. label = _("Date field")
  147. icon = "date"
  148. class HTML5TimeInput(forms.TimeInput):
  149. input_type = "time"
  150. class TimeFieldBlock(OptionalFormFieldBlock):
  151. default_value = TimeBlock(required=False)
  152. field_class = forms.TimeField
  153. widget = HTML5TimeInput
  154. class Meta:
  155. label = _("Time field")
  156. icon = "time"
  157. class DateTimePickerInput(forms.SplitDateTimeWidget):
  158. def __init__(self, attrs=None, date_format=None, time_format=None):
  159. super().__init__(
  160. attrs=attrs, date_format=date_format, time_format=time_format
  161. )
  162. self.widgets = (
  163. DatePickerInput(attrs=attrs, format=date_format),
  164. HTML5TimeInput(attrs=attrs, format=time_format),
  165. )
  166. def decompress(self, value):
  167. if isinstance(value, str):
  168. value = parse_datetime(value)
  169. return super().decompress(value)
  170. class DateTimeFieldBlock(OptionalFormFieldBlock):
  171. default_value = DateTimeBlock(required=False)
  172. field_class = forms.SplitDateTimeField
  173. widget = DateTimePickerInput
  174. class Meta:
  175. label = _("Date+time field")
  176. icon = "date"
  177. class ImageFieldBlock(OptionalFormFieldBlock):
  178. field_class = forms.ImageField
  179. class Meta:
  180. label = _("Image field")
  181. icon = "image"
  182. class FileFieldBlock(OptionalFormFieldBlock):
  183. field_class = forms.FileField
  184. class Meta:
  185. label = _("File field")
  186. icon = "download"
  187. class FormFieldsBlock(StreamBlock):
  188. char = CharFieldBlock(group=_("Fields"))
  189. text = TextFieldBlock(group=_("Fields"))
  190. number = NumberFieldBlock(group=_("Fields"))
  191. checkbox = CheckboxFieldBlock(group=_("Fields"))
  192. radios = RadioButtonsFieldBlock(group=_("Fields"))
  193. dropdown = DropdownFieldBlock(group=_("Fields"))
  194. checkboxes = CheckboxesFieldBlock(group=_("Fields"))
  195. date = DateFieldBlock(group=_("Fields"))
  196. time = TimeFieldBlock(group=_("Fields"))
  197. datetime = DateTimeFieldBlock(group=_("Fields"))
  198. image = ImageFieldBlock(group=_("Fields"))
  199. file = FileFieldBlock(group=_("Fields"))
  200. text_markup = RichTextBlock(group=_("Other"))
  201. class Meta:
  202. label = _("Form fields")
  203. class FormStepBlock(StructBlock):
  204. name = CharBlock(label=_("Name"), required=False)
  205. form_fields = FormFieldsBlock()
  206. class Meta:
  207. label = _("Form step")
  208. class FormStepsBlock(StreamBlock):
  209. step = FormStepBlock()
  210. class Meta:
  211. label = _("Form steps")