wagtailstreamforms 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. wagtailstreamforms/__init__.py +1 -0
  2. wagtailstreamforms/blocks.py +93 -0
  3. wagtailstreamforms/conf.py +35 -0
  4. wagtailstreamforms/fields.py +218 -0
  5. wagtailstreamforms/forms.py +61 -0
  6. wagtailstreamforms/hooks.py +62 -0
  7. wagtailstreamforms/locale/es/LC_MESSAGES/django.mo +0 -0
  8. wagtailstreamforms/locale/es/LC_MESSAGES/django.po +328 -0
  9. wagtailstreamforms/locale/nl/LC_MESSAGES/django.mo +0 -0
  10. wagtailstreamforms/locale/nl/LC_MESSAGES/django.po +464 -0
  11. wagtailstreamforms/locale/tr/LC_MESSAGES/django.mo +0 -0
  12. wagtailstreamforms/locale/tr/LC_MESSAGES/django.po +317 -0
  13. wagtailstreamforms/management/__init__.py +0 -0
  14. wagtailstreamforms/management/commands/__init__.py +0 -0
  15. wagtailstreamforms/management/commands/prunesubmissions.py +29 -0
  16. wagtailstreamforms/migrations/0001_initial.py +555 -0
  17. wagtailstreamforms/migrations/0002_form_site.py +21 -0
  18. wagtailstreamforms/migrations/0003_alter_form_fields.py +20 -0
  19. wagtailstreamforms/migrations/__init__.py +0 -0
  20. wagtailstreamforms/models/__init__.py +39 -0
  21. wagtailstreamforms/models/abstract.py +15 -0
  22. wagtailstreamforms/models/file.py +35 -0
  23. wagtailstreamforms/models/form.py +188 -0
  24. wagtailstreamforms/models/submission.py +27 -0
  25. wagtailstreamforms/serializers.py +11 -0
  26. wagtailstreamforms/streamfield.py +48 -0
  27. wagtailstreamforms/templates/streamforms/advanced_settings.html +25 -0
  28. wagtailstreamforms/templates/streamforms/confirm_copy.html +26 -0
  29. wagtailstreamforms/templates/streamforms/confirm_delete.html +23 -0
  30. wagtailstreamforms/templates/streamforms/form_block.html +10 -0
  31. wagtailstreamforms/templates/streamforms/index_submissions.html +104 -0
  32. wagtailstreamforms/templates/streamforms/list_submissions.html +56 -0
  33. wagtailstreamforms/templates/streamforms/non_existent_form.html +2 -0
  34. wagtailstreamforms/templates/streamforms/partials/form_field.html +6 -0
  35. wagtailstreamforms/templates/streamforms/partials/pagination_nav.html +17 -0
  36. wagtailstreamforms/templates/streamforms/wagtailadmin/shared/datetimepicker_translations.html +39 -0
  37. wagtailstreamforms/templatetags/__init__.py +0 -0
  38. wagtailstreamforms/templatetags/streamforms_tags.py +43 -0
  39. wagtailstreamforms/urls.py +31 -0
  40. wagtailstreamforms/utils/__init__.py +0 -0
  41. wagtailstreamforms/utils/apps.py +25 -0
  42. wagtailstreamforms/utils/general.py +6 -0
  43. wagtailstreamforms/utils/loading.py +30 -0
  44. wagtailstreamforms/utils/requests.py +13 -0
  45. wagtailstreamforms/utils/version.py +0 -0
  46. wagtailstreamforms/views/__init__.py +4 -0
  47. wagtailstreamforms/views/advanced_settings.py +51 -0
  48. wagtailstreamforms/views/copy.py +83 -0
  49. wagtailstreamforms/views/submission_delete.py +73 -0
  50. wagtailstreamforms/views/submission_list.py +112 -0
  51. wagtailstreamforms/wagtail_hooks.py +255 -0
  52. wagtailstreamforms/wagtailstreamforms_fields.py +274 -0
  53. wagtailstreamforms/wagtailstreamforms_hooks.py +31 -0
  54. wagtailstreamforms-0.0.0.dist-info/METADATA +24 -0
  55. wagtailstreamforms-0.0.0.dist-info/RECORD +58 -0
  56. wagtailstreamforms-0.0.0.dist-info/WHEEL +5 -0
  57. wagtailstreamforms-0.0.0.dist-info/licenses/LICENSE +19 -0
  58. wagtailstreamforms-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ __version__ = "4.1.0"
@@ -0,0 +1,93 @@
1
+ import uuid
2
+
3
+ from django.utils.functional import cached_property
4
+ from django.utils.safestring import SafeText, mark_safe
5
+ from django.utils.translation import gettext_lazy as _
6
+ from wagtail import blocks
7
+
8
+
9
+ class InfoBlock(blocks.CharBlock):
10
+ def render_form(self, value, prefix="", errors=None) -> SafeText:
11
+ field = self.field
12
+ shown_value = value if value else field.help_text
13
+ return mark_safe('<div style="margin-top:5px;padding:0.9em 1.2em;">%s</div>' % shown_value)
14
+
15
+
16
+ class FormChooserBlock(blocks.ChooserBlock):
17
+ @cached_property
18
+ def target_model(self):
19
+ from .models import Form
20
+
21
+ return Form
22
+
23
+ @cached_property
24
+ def widget(self):
25
+ from .wagtail_hooks import WagtailStreamFormsChooser
26
+
27
+ return WagtailStreamFormsChooser()
28
+
29
+ def get_form_state(self, value: dict):
30
+ return self.widget.get_value_data(value)
31
+
32
+
33
+ class WagtailFormBlock(blocks.StructBlock):
34
+ form = FormChooserBlock()
35
+ form_action = blocks.CharBlock(
36
+ required=False,
37
+ help_text=_('The form post action. "" or "." for the current page or a url'),
38
+ )
39
+ form_reference = InfoBlock(
40
+ required=False,
41
+ help_text=_("This form will be given a unique reference once saved"),
42
+ )
43
+
44
+ class Meta:
45
+ icon = "form"
46
+ template = None
47
+
48
+ def render(self, value: dict, context: dict | None = None) -> str | SafeText:
49
+ form = value.get("form")
50
+
51
+ # check if we have a form, as they can be deleted, and we dont want to break the site with
52
+ # a none template value
53
+ if form:
54
+ self.meta.template = form.template_name
55
+ else:
56
+ self.meta.template = "streamforms/non_existent_form.html"
57
+
58
+ return super().render(value, context)
59
+
60
+ def get_context(self, value: dict, parent_context: dict | None = None) -> dict:
61
+ context = super().get_context(value, parent_context)
62
+
63
+ form = value.get("form")
64
+ form_reference = value.get("form_reference")
65
+
66
+ if form:
67
+ # check the context for an invalid form submitted to the page.
68
+ # Use that instead if it has the same unique form_reference number
69
+ invalid_form_reference = context.get("invalid_stream_form_reference")
70
+ invalid_form = context.get("invalid_stream_form")
71
+
72
+ if (
73
+ invalid_form_reference
74
+ and invalid_form
75
+ and invalid_form_reference == form_reference
76
+ ):
77
+ context["form"] = invalid_form
78
+ else:
79
+ context["form"] = form.get_form(
80
+ initial={"form_id": form.id, "form_reference": form_reference}
81
+ )
82
+
83
+ return context
84
+
85
+ def clean(self, value: dict) -> dict:
86
+ result = super().clean(value)
87
+
88
+ # set to a new uuid so we can ensure we can identify this form
89
+ # against other forms of the same type in the page
90
+ if not result.get("form_reference"):
91
+ result["form_reference"] = uuid.uuid4()
92
+
93
+ return result
@@ -0,0 +1,35 @@
1
+ from django.conf import settings
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ SETTINGS_PREFIX = "WAGTAILSTREAMFORMS"
5
+ SETTINGS_DEFAULTS = {
6
+ "ADMIN_MENU_LABEL": _("Streamforms"),
7
+ "ADMIN_MENU_ORDER": None,
8
+ "ADVANCED_SETTINGS_MODEL": None,
9
+ "ENABLE_FORM_PROCESSING": True,
10
+ "ENABLE_BUILTIN_HOOKS": True,
11
+ "ENABLED_FIELDS": (
12
+ "singleline",
13
+ "multiline",
14
+ "date",
15
+ "datetime",
16
+ "email",
17
+ "url",
18
+ "number",
19
+ "dropdown",
20
+ "radio",
21
+ "checkboxes",
22
+ "checkbox",
23
+ "hidden",
24
+ "singlefile",
25
+ "multifile",
26
+ ),
27
+ "FORM_TEMPLATES": [
28
+ ("streamforms/form_block.html", "Default Form Template"),
29
+ ],
30
+ }
31
+
32
+
33
+ def get_setting(name):
34
+ setting_key = "{}_{}".format(SETTINGS_PREFIX, name)
35
+ return getattr(settings, setting_key, SETTINGS_DEFAULTS[name])
@@ -0,0 +1,218 @@
1
+ from django import forms
2
+ from django.core import exceptions
3
+ from django.db import models
4
+ from django.utils.text import capfirst
5
+ from wagtail import blocks
6
+
7
+ from wagtailstreamforms import hooks
8
+ from wagtailstreamforms.utils.apps import get_app_submodules
9
+ from wagtailstreamforms.utils.general import get_slug_from_string
10
+
11
+ _fields = {}
12
+ _searched_for_fields = False
13
+
14
+
15
+ def register(field_name, cls=None):
16
+ """
17
+ Register field for ``field_name``. Can be used as a decorator::
18
+ @register('singleline')
19
+ class SingleLineTextField(BaseField):
20
+ field_class = django.forms.CharField
21
+ or as a function call::
22
+ class SingleLineTextField(BaseField):
23
+ field_class = django.forms.CharField
24
+ register('singleline', SingleLineTextField)
25
+ """
26
+
27
+ if cls is None:
28
+
29
+ def decorator(cls):
30
+ register(field_name, cls)
31
+ return cls
32
+
33
+ return decorator
34
+
35
+ _fields[field_name] = cls
36
+
37
+
38
+ def search_for_fields():
39
+ global _searched_for_fields
40
+ if not _searched_for_fields:
41
+ list(get_app_submodules("wagtailstreamforms_fields"))
42
+ _searched_for_fields = True
43
+
44
+
45
+ def get_fields():
46
+ """Return the registered field classes."""
47
+
48
+ search_for_fields()
49
+ return _fields
50
+
51
+
52
+ class BaseField:
53
+ """A base form field class, all form fields must inherit this class.
54
+
55
+ Usage::
56
+
57
+ @register('multiline')
58
+ class MultiLineTextField(BaseField):
59
+ field_class = forms.CharField
60
+ widget = forms.widgets.Textarea
61
+ icon = 'placeholder'
62
+ label = 'Text (multi line)'
63
+
64
+ """
65
+
66
+ field_class = None
67
+ widget = None
68
+ icon = "placeholder"
69
+ label = None
70
+
71
+ @classmethod
72
+ def get_formfield_name(cls, block_value):
73
+ """
74
+ Return a value to use as the 'name' for the Django form field.
75
+ """
76
+ return block_value.get("name") or get_slug_from_string(
77
+ cls.get_formfield_label(block_value)
78
+ )
79
+
80
+ @classmethod
81
+ def get_formfield_label(cls, block_value):
82
+ """
83
+ Return a value to use as the 'label' for the Django form field.
84
+ """
85
+ try:
86
+ return block_value["label"]
87
+ except KeyError:
88
+ raise AttributeError(
89
+ f"No label value can be determined for an instance of {cls.__name__}. "
90
+ "Add a 'label' CharBlock() in your field's get_form_block() method to "
91
+ "allow this to be specified by form editors. Or, override "
92
+ "get_formfield_label() to return a different value."
93
+ )
94
+
95
+ @classmethod
96
+ def get_formfield_help_text(cls, block_value):
97
+ """
98
+ Return a value to use as the 'help_text' for the Django form field.
99
+ """
100
+ return block_value.get("help_text")
101
+
102
+ @classmethod
103
+ def get_formfield_initial(cls, block_value):
104
+ """
105
+ Return a 'initial' value to use for the Django form field.
106
+ """
107
+ return block_value.get("default_value")
108
+
109
+ @classmethod
110
+ def get_formfield_required(cls, block_value):
111
+ """
112
+ Return a boolean indicating whether the Django form field should be required.
113
+ """
114
+ return block_value.get("required", True)
115
+
116
+ def get_formfield(self, block_value):
117
+ """
118
+ Get the form field. Its unlikely you will need to override this.
119
+
120
+ :param block_value: The StreamValue for this field from the StreamField
121
+ :return: An instance of a form field class, ie ``django.forms.CharField(**options)``
122
+ """
123
+
124
+ if not self.field_class:
125
+ raise NotImplementedError("must provide a cls.field_class")
126
+
127
+ options = self.get_options(block_value)
128
+
129
+ if self.widget:
130
+ return self.field_class(widget=self.widget, **options)
131
+
132
+ return self.field_class(**options)
133
+
134
+ def get_options(self, block_value):
135
+ """The field options.
136
+
137
+ Override this to provide additional options such as ``choices`` for a dropdown.
138
+
139
+ :param block_value: The StreamValue for this field from the StreamField
140
+ :return: The options to be passed into the field, ie ``django.forms.CharField(**options)``
141
+ """
142
+
143
+ return {
144
+ "label": self.get_formfield_label(block_value),
145
+ "help_text": self.get_formfield_help_text(block_value),
146
+ "required": self.get_formfield_required(block_value),
147
+ "initial": self.get_formfield_initial(block_value),
148
+ }
149
+
150
+ def get_form_block(self):
151
+ """The StreamField StructBlock.
152
+
153
+ Override this to provide additional fields in the StreamField.
154
+
155
+ :return: The ``wagtail.core.blocks.StructBlock`` to be used in the StreamField
156
+ """
157
+ return blocks.StructBlock(
158
+ [
159
+ ("label", blocks.CharBlock()),
160
+ ("help_text", blocks.CharBlock(required=False)),
161
+ ("required", blocks.BooleanBlock(required=False)),
162
+ ("default_value", blocks.CharBlock(required=False)),
163
+ ],
164
+ icon=self.icon,
165
+ label=self.label,
166
+ )
167
+
168
+
169
+ class HookMultiSelectFormField(forms.MultipleChoiceField):
170
+ widget = forms.CheckboxSelectMultiple
171
+
172
+
173
+ class HookSelectField(models.Field):
174
+ def get_choices_default(self):
175
+ return [
176
+ (fn.__name__, capfirst(fn.__name__.replace("_", " ")))
177
+ for fn in hooks.get_hooks("process_form_submission")
178
+ ]
179
+
180
+ def get_db_prep_value(self, value, connection=None, prepared=False):
181
+ if isinstance(value, str):
182
+ return value
183
+ elif isinstance(value, list):
184
+ return ",".join(value)
185
+
186
+ def get_internal_type(self):
187
+ return "TextField"
188
+
189
+ def formfield(self, **kwargs):
190
+ defaults = {
191
+ "form_class": HookMultiSelectFormField,
192
+ "choices": self.get_choices_default(),
193
+ }
194
+ defaults.update(kwargs)
195
+ return super().formfield(**defaults)
196
+
197
+ def from_db_value(self, value, expression, connection, context=None, *args, **kwargs):
198
+ if value is None or value == "":
199
+ return []
200
+ return value.split(",")
201
+
202
+ def to_python(self, value):
203
+ if not value or value == "":
204
+ return []
205
+ if isinstance(value, list):
206
+ return value
207
+ return value.split(",")
208
+
209
+ def value_to_string(self, obj):
210
+ value = self.value_from_object(obj)
211
+ return ",".join(value)
212
+
213
+ def validate(self, value, model_instance):
214
+ arr_choices = [v for v, s in self.get_choices_default()]
215
+ for opt in value:
216
+ if opt not in arr_choices:
217
+ raise exceptions.ValidationError("%s is not a valid choice" % opt)
218
+ return
@@ -0,0 +1,61 @@
1
+ from collections import OrderedDict
2
+
3
+ from django import forms
4
+ from django.utils.translation import gettext_lazy as _
5
+
6
+ from wagtailstreamforms.fields import get_fields
7
+
8
+
9
+ class BaseForm(forms.Form):
10
+ def __init__(self, *args, **kwargs):
11
+ kwargs.setdefault("label_suffix", "")
12
+
13
+ self.user = kwargs.pop("user", None)
14
+ self.page = kwargs.pop("page", None)
15
+
16
+ super().__init__(*args, **kwargs)
17
+
18
+
19
+ class FormBuilder:
20
+ def __init__(self, fields):
21
+ self.fields = fields
22
+
23
+ @property
24
+ def formfields(self):
25
+ """Return a list of form fields from the registered fields."""
26
+
27
+ formfields = OrderedDict()
28
+
29
+ registered_fields = get_fields()
30
+
31
+ for field in self.fields:
32
+ field_type = field.get("type")
33
+ field_value = field.get("value")
34
+
35
+ # check we have the field
36
+ if field_type not in registered_fields:
37
+ raise AttributeError("Could not find a registered field of type %s" % field_type)
38
+
39
+ # get the field
40
+ registered_cls = registered_fields[field_type]()
41
+ field_name = registered_cls.get_formfield_name(field_value)
42
+ field_cls = registered_cls.get_formfield(field_value)
43
+ formfields[field_name] = field_cls
44
+
45
+ # add fields to uniquely identify the form
46
+ formfields["form_id"] = forms.CharField(widget=forms.HiddenInput)
47
+ formfields["form_reference"] = forms.CharField(widget=forms.HiddenInput)
48
+
49
+ return formfields
50
+
51
+ def get_form_class(self):
52
+ return type(str("StreamformsForm"), (BaseForm,), self.formfields)
53
+
54
+
55
+ class SelectDateForm(forms.Form):
56
+ date_from = forms.DateTimeField(
57
+ required=False, widget=forms.DateInput(attrs={"placeholder": _("Date from")})
58
+ )
59
+ date_to = forms.DateTimeField(
60
+ required=False, widget=forms.DateInput(attrs={"placeholder": _("Date to")})
61
+ )
@@ -0,0 +1,62 @@
1
+ from operator import itemgetter
2
+
3
+ from wagtailstreamforms.conf import get_setting
4
+ from wagtailstreamforms.utils.apps import get_app_submodules
5
+
6
+ _hooks = {}
7
+
8
+
9
+ def register(hook_name, fn=None, order=0):
10
+ """
11
+ Register hook for ``hook_name``. Can be used as a decorator::
12
+ @register('hook_name')
13
+ def my_hook(...):
14
+ pass
15
+ or as a function call::
16
+ def my_hook(...):
17
+ pass
18
+ register('hook_name', my_hook)
19
+ """
20
+
21
+ # Pretend to be a decorator if fn is not supplied
22
+ if fn is None:
23
+
24
+ def decorator(fn):
25
+ register(hook_name, fn, order=order)
26
+ return fn
27
+
28
+ return decorator
29
+
30
+ if hook_name not in _hooks:
31
+ _hooks[hook_name] = []
32
+ _hooks[hook_name].append((fn, order))
33
+
34
+
35
+ _searched_for_hooks = False
36
+
37
+
38
+ def search_for_hooks():
39
+ global _searched_for_hooks
40
+ if not _searched_for_hooks:
41
+ list(get_app_submodules("wagtailstreamforms_hooks"))
42
+ _searched_for_hooks = True
43
+
44
+
45
+ def get_hooks(hook_name):
46
+ """Return the hooks function sorted by their order."""
47
+
48
+ search_for_hooks()
49
+ hooks = _hooks.get(hook_name, [])
50
+ hooks = sorted(hooks, key=itemgetter(1))
51
+ fncs = []
52
+ builtin_hook_modules = ["wagtailstreamforms.wagtailstreamforms_hooks"]
53
+ builtin_enabled = get_setting("ENABLE_BUILTIN_HOOKS")
54
+
55
+ for fn, _ in hooks:
56
+ # dont add the hooks if they have been disabled via the setting
57
+ # this is so that they can be overridden
58
+ if fn.__module__ in builtin_hook_modules and not builtin_enabled:
59
+ continue
60
+ fncs.append(fn)
61
+
62
+ return fncs