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.
- wagtailstreamforms/__init__.py +1 -0
- wagtailstreamforms/blocks.py +93 -0
- wagtailstreamforms/conf.py +35 -0
- wagtailstreamforms/fields.py +218 -0
- wagtailstreamforms/forms.py +61 -0
- wagtailstreamforms/hooks.py +62 -0
- wagtailstreamforms/locale/es/LC_MESSAGES/django.mo +0 -0
- wagtailstreamforms/locale/es/LC_MESSAGES/django.po +328 -0
- wagtailstreamforms/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtailstreamforms/locale/nl/LC_MESSAGES/django.po +464 -0
- wagtailstreamforms/locale/tr/LC_MESSAGES/django.mo +0 -0
- wagtailstreamforms/locale/tr/LC_MESSAGES/django.po +317 -0
- wagtailstreamforms/management/__init__.py +0 -0
- wagtailstreamforms/management/commands/__init__.py +0 -0
- wagtailstreamforms/management/commands/prunesubmissions.py +29 -0
- wagtailstreamforms/migrations/0001_initial.py +555 -0
- wagtailstreamforms/migrations/0002_form_site.py +21 -0
- wagtailstreamforms/migrations/0003_alter_form_fields.py +20 -0
- wagtailstreamforms/migrations/__init__.py +0 -0
- wagtailstreamforms/models/__init__.py +39 -0
- wagtailstreamforms/models/abstract.py +15 -0
- wagtailstreamforms/models/file.py +35 -0
- wagtailstreamforms/models/form.py +188 -0
- wagtailstreamforms/models/submission.py +27 -0
- wagtailstreamforms/serializers.py +11 -0
- wagtailstreamforms/streamfield.py +48 -0
- wagtailstreamforms/templates/streamforms/advanced_settings.html +25 -0
- wagtailstreamforms/templates/streamforms/confirm_copy.html +26 -0
- wagtailstreamforms/templates/streamforms/confirm_delete.html +23 -0
- wagtailstreamforms/templates/streamforms/form_block.html +10 -0
- wagtailstreamforms/templates/streamforms/index_submissions.html +104 -0
- wagtailstreamforms/templates/streamforms/list_submissions.html +56 -0
- wagtailstreamforms/templates/streamforms/non_existent_form.html +2 -0
- wagtailstreamforms/templates/streamforms/partials/form_field.html +6 -0
- wagtailstreamforms/templates/streamforms/partials/pagination_nav.html +17 -0
- wagtailstreamforms/templates/streamforms/wagtailadmin/shared/datetimepicker_translations.html +39 -0
- wagtailstreamforms/templatetags/__init__.py +0 -0
- wagtailstreamforms/templatetags/streamforms_tags.py +43 -0
- wagtailstreamforms/urls.py +31 -0
- wagtailstreamforms/utils/__init__.py +0 -0
- wagtailstreamforms/utils/apps.py +25 -0
- wagtailstreamforms/utils/general.py +6 -0
- wagtailstreamforms/utils/loading.py +30 -0
- wagtailstreamforms/utils/requests.py +13 -0
- wagtailstreamforms/utils/version.py +0 -0
- wagtailstreamforms/views/__init__.py +4 -0
- wagtailstreamforms/views/advanced_settings.py +51 -0
- wagtailstreamforms/views/copy.py +83 -0
- wagtailstreamforms/views/submission_delete.py +73 -0
- wagtailstreamforms/views/submission_list.py +112 -0
- wagtailstreamforms/wagtail_hooks.py +255 -0
- wagtailstreamforms/wagtailstreamforms_fields.py +274 -0
- wagtailstreamforms/wagtailstreamforms_hooks.py +31 -0
- wagtailstreamforms-0.0.0.dist-info/METADATA +24 -0
- wagtailstreamforms-0.0.0.dist-info/RECORD +58 -0
- wagtailstreamforms-0.0.0.dist-info/WHEEL +5 -0
- wagtailstreamforms-0.0.0.dist-info/licenses/LICENSE +19 -0
- wagtailstreamforms-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from django.contrib import messages
|
|
2
|
+
from django.contrib.admin.utils import quote
|
|
3
|
+
from django.shortcuts import redirect
|
|
4
|
+
from django.template.response import TemplateResponse
|
|
5
|
+
from django.urls import include, path, reverse
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
from generic_chooser.views import ModelChooserViewSet
|
|
8
|
+
from wagtail import hooks
|
|
9
|
+
from wagtail.admin import messages as wagtail_messages
|
|
10
|
+
from wagtail.admin.widgets import AdminPageChooser
|
|
11
|
+
from wagtail_modeladmin.helpers import AdminURLHelper, ButtonHelper
|
|
12
|
+
from wagtail_modeladmin.options import ModelAdmin, modeladmin_register
|
|
13
|
+
from wagtail_modeladmin.views import (
|
|
14
|
+
CreateView,
|
|
15
|
+
DeleteView,
|
|
16
|
+
EditView,
|
|
17
|
+
InspectView,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from wagtailstreamforms import hooks as form_hooks
|
|
21
|
+
from wagtailstreamforms.conf import get_setting
|
|
22
|
+
from wagtailstreamforms.models import Form
|
|
23
|
+
from wagtailstreamforms.utils.loading import get_advanced_settings_model
|
|
24
|
+
from wagtailstreamforms.utils.requests import get_form_instance_from_request
|
|
25
|
+
|
|
26
|
+
SettingsModel = get_advanced_settings_model()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FormURLHelper(AdminURLHelper):
|
|
30
|
+
def get_action_url(self, action, *args, **kwargs):
|
|
31
|
+
if action in ["advanced", "copy", "submissions"]:
|
|
32
|
+
return reverse("wagtailstreamforms:streamforms_%s" % action, args=args, kwargs=kwargs)
|
|
33
|
+
|
|
34
|
+
return super().get_action_url(action, *args, **kwargs)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FormButtonHelper(ButtonHelper):
|
|
38
|
+
def button(self, pk, action, label, title, classnames_add, classnames_exclude):
|
|
39
|
+
cn = self.finalise_classname(classnames_add, classnames_exclude)
|
|
40
|
+
button = {
|
|
41
|
+
"url": self.url_helper.get_action_url(action, quote(pk)),
|
|
42
|
+
"label": label,
|
|
43
|
+
"classname": cn,
|
|
44
|
+
"title": title,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return button
|
|
48
|
+
|
|
49
|
+
def get_buttons_for_obj(self, obj, exclude=None, classnames_add=None, classnames_exclude=None):
|
|
50
|
+
buttons = super().get_buttons_for_obj(obj, exclude, classnames_add, classnames_exclude)
|
|
51
|
+
pk = getattr(obj, self.opts.pk.attname)
|
|
52
|
+
ph = self.permission_helper
|
|
53
|
+
usr = self.request.user
|
|
54
|
+
|
|
55
|
+
# if there is a form settings model defined
|
|
56
|
+
# users that either create or edit forms should be able edit advanced settings
|
|
57
|
+
if SettingsModel and (ph.user_can_create(usr) or ph.user_can_edit_obj(usr, obj)):
|
|
58
|
+
buttons.append(
|
|
59
|
+
self.button(
|
|
60
|
+
pk,
|
|
61
|
+
"advanced",
|
|
62
|
+
_("Advanced"),
|
|
63
|
+
_("Advanced settings"),
|
|
64
|
+
classnames_add,
|
|
65
|
+
classnames_exclude,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# users that can create forms can copy them
|
|
70
|
+
if ph.user_can_create(usr):
|
|
71
|
+
buttons.append(
|
|
72
|
+
self.button(
|
|
73
|
+
pk,
|
|
74
|
+
"copy",
|
|
75
|
+
_("Copy"),
|
|
76
|
+
_("Copy this form"),
|
|
77
|
+
classnames_add,
|
|
78
|
+
classnames_exclude,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# users that can do any form actions can view submissions
|
|
83
|
+
buttons.append(
|
|
84
|
+
self.button(
|
|
85
|
+
pk,
|
|
86
|
+
"submissions",
|
|
87
|
+
_("Submissions"),
|
|
88
|
+
_("Submissions of this form"),
|
|
89
|
+
classnames_add,
|
|
90
|
+
classnames_exclude,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return buttons
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class InstanceSpecificViewHookMixin:
|
|
98
|
+
"""Mixin class responsible to apply wagtailstreamforms specific hooks."""
|
|
99
|
+
|
|
100
|
+
def dispatch(self, request, *args, **kwargs):
|
|
101
|
+
for fn in form_hooks.get_hooks("before_inspect_form_instance_dispatch"):
|
|
102
|
+
self.instance = fn(self.instance, request)
|
|
103
|
+
return super().dispatch(request, *args, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class InspectFormView(InstanceSpecificViewHookMixin, InspectView):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class CreateFormView(CreateView):
|
|
111
|
+
def form_valid(self, form):
|
|
112
|
+
instance = form.save(commit=False)
|
|
113
|
+
for fn in form_hooks.get_hooks("before_form_save"):
|
|
114
|
+
instance = fn(instance, self.request)
|
|
115
|
+
instance.save()
|
|
116
|
+
wagtail_messages.success(
|
|
117
|
+
self.request,
|
|
118
|
+
self.get_success_message(instance),
|
|
119
|
+
buttons=self.get_success_message_buttons(instance),
|
|
120
|
+
)
|
|
121
|
+
return redirect(self.get_success_url())
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class EditFormView(InstanceSpecificViewHookMixin, EditView):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class DeleteFormView(InstanceSpecificViewHookMixin, DeleteView):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@modeladmin_register
|
|
133
|
+
class FormModelAdmin(ModelAdmin):
|
|
134
|
+
model = Form
|
|
135
|
+
list_display = ("title", "slug", "latest_submission", "saved_submissions")
|
|
136
|
+
list_filter = None
|
|
137
|
+
menu_label = _(get_setting("ADMIN_MENU_LABEL"))
|
|
138
|
+
menu_order = get_setting("ADMIN_MENU_ORDER")
|
|
139
|
+
menu_icon = "form"
|
|
140
|
+
search_fields = ("title", "slug")
|
|
141
|
+
button_helper_class = FormButtonHelper
|
|
142
|
+
inspect_view_class = InspectFormView
|
|
143
|
+
create_view_class = CreateFormView
|
|
144
|
+
edit_view_class = EditFormView
|
|
145
|
+
delete_view_class = DeleteFormView
|
|
146
|
+
url_helper_class = FormURLHelper
|
|
147
|
+
index_template_name = "modeladmin/index.html"
|
|
148
|
+
|
|
149
|
+
def get_queryset(self, request):
|
|
150
|
+
qs = super().get_queryset(request)
|
|
151
|
+
for fn in form_hooks.get_hooks("construct_form_queryset"):
|
|
152
|
+
qs = fn(qs, request)
|
|
153
|
+
return qs
|
|
154
|
+
|
|
155
|
+
def get_list_display(self, request):
|
|
156
|
+
list_display = self.list_display
|
|
157
|
+
for fn in form_hooks.get_hooks("construct_form_list_display"):
|
|
158
|
+
list_display = fn(list_display, request)
|
|
159
|
+
return list_display
|
|
160
|
+
|
|
161
|
+
def get_list_filter(self, request):
|
|
162
|
+
list_filter = self.list_filter
|
|
163
|
+
for fn in form_hooks.get_hooks("construct_form_list_filter"):
|
|
164
|
+
list_filter = fn(list_filter, request)
|
|
165
|
+
return list_filter
|
|
166
|
+
|
|
167
|
+
def latest_submission(self, obj):
|
|
168
|
+
submission_class = obj.get_submission_class()
|
|
169
|
+
return submission_class._default_manager.filter(form=obj).latest("submit_time").submit_time
|
|
170
|
+
|
|
171
|
+
latest_submission.short_description = _("Latest submission")
|
|
172
|
+
|
|
173
|
+
def saved_submissions(self, obj):
|
|
174
|
+
submission_class = obj.get_submission_class()
|
|
175
|
+
return submission_class._default_manager.filter(form=obj).count()
|
|
176
|
+
|
|
177
|
+
saved_submissions.short_description = _("Saved submissions")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@hooks.register("register_admin_urls")
|
|
181
|
+
def register_admin_urls():
|
|
182
|
+
from wagtailstreamforms import urls
|
|
183
|
+
|
|
184
|
+
return [path("wagtailstreamforms/", include((urls, "wagtailstreamforms")))]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@hooks.register("before_serve_page")
|
|
188
|
+
def process_form(page, request, *args, **kwargs):
|
|
189
|
+
"""Process the form if there is one, if not just continue."""
|
|
190
|
+
|
|
191
|
+
# only process if settings.WAGTAILSTREAMFORMS_ENABLE_FORM_PROCESSING is True
|
|
192
|
+
if not get_setting("ENABLE_FORM_PROCESSING"):
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if request.method == "POST":
|
|
196
|
+
form_def = get_form_instance_from_request(request)
|
|
197
|
+
|
|
198
|
+
if form_def:
|
|
199
|
+
form = form_def.get_form(request.POST, request.FILES, page=page, user=request.user)
|
|
200
|
+
context = page.get_context(request, *args, **kwargs)
|
|
201
|
+
|
|
202
|
+
if form.is_valid():
|
|
203
|
+
# process the form submission
|
|
204
|
+
form_def.process_form_submission(form)
|
|
205
|
+
|
|
206
|
+
# create success message
|
|
207
|
+
if form_def.success_message:
|
|
208
|
+
messages.success(request, form_def.success_message, fail_silently=True)
|
|
209
|
+
|
|
210
|
+
# redirect to the page defined in the form
|
|
211
|
+
# or the current page as a fallback - this will avoid refreshing
|
|
212
|
+
# and submitting again
|
|
213
|
+
redirect_page = form_def.post_redirect_page or page
|
|
214
|
+
|
|
215
|
+
return redirect(redirect_page.get_url(request), context=context)
|
|
216
|
+
|
|
217
|
+
else:
|
|
218
|
+
# update the context with the invalid form and serve the page
|
|
219
|
+
context.update(
|
|
220
|
+
{
|
|
221
|
+
"invalid_stream_form_reference": form.data.get("form_reference"),
|
|
222
|
+
"invalid_stream_form": form,
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# create error message
|
|
227
|
+
if form_def.error_message:
|
|
228
|
+
messages.error(request, form_def.error_message, fail_silently=True)
|
|
229
|
+
|
|
230
|
+
return TemplateResponse(
|
|
231
|
+
request, page.get_template(request, *args, **kwargs), context
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class WagtailStreamFormsChooserViewSet(ModelChooserViewSet):
|
|
236
|
+
icon = "form"
|
|
237
|
+
model = Form
|
|
238
|
+
page_title = _("Choose a form")
|
|
239
|
+
per_page = 10
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class WagtailStreamFormsChooser(AdminPageChooser):
|
|
243
|
+
choose_one_text = _("Choose a form")
|
|
244
|
+
choose_another_text = _("Choose another form")
|
|
245
|
+
link_to_chosen_text = _("Edit this form")
|
|
246
|
+
model = Form
|
|
247
|
+
choose_modal_url_name = "wagtailstreamforms_chooser:choose"
|
|
248
|
+
icon = "form"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@hooks.register("register_admin_viewset")
|
|
252
|
+
def register_wagtailstreamforms_chooser_viewset():
|
|
253
|
+
return WagtailStreamFormsChooserViewSet(
|
|
254
|
+
"wagtailstreamforms_chooser", url_prefix="wagtailstreamforms-chooser"
|
|
255
|
+
)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
from django import forms
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
from wagtail import blocks
|
|
6
|
+
|
|
7
|
+
from wagtailstreamforms.conf import get_setting
|
|
8
|
+
from wagtailstreamforms.fields import BaseField, register
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SingleLineTextField(BaseField):
|
|
12
|
+
field_class = forms.CharField
|
|
13
|
+
label = _("Text field (single line)")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MultiLineTextField(BaseField):
|
|
17
|
+
field_class = forms.CharField
|
|
18
|
+
widget = forms.widgets.Textarea
|
|
19
|
+
label = _("Text field (multi line)")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DateField(BaseField):
|
|
23
|
+
field_class = forms.DateField
|
|
24
|
+
icon = "date"
|
|
25
|
+
label = _("Date field")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DateTimeField(BaseField):
|
|
29
|
+
field_class = forms.DateTimeField
|
|
30
|
+
icon = "time"
|
|
31
|
+
label = _("Time field")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EmailField(BaseField):
|
|
35
|
+
field_class = forms.EmailField
|
|
36
|
+
icon = "mail"
|
|
37
|
+
label = _("Email field")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class URLField(BaseField):
|
|
41
|
+
field_class = forms.URLField
|
|
42
|
+
icon = "link"
|
|
43
|
+
label = _("URL field")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class NumberField(BaseField):
|
|
47
|
+
field_class = forms.DecimalField
|
|
48
|
+
label = _("Number field")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DropdownField(BaseField):
|
|
52
|
+
field_class = forms.ChoiceField
|
|
53
|
+
icon = "arrow-down-big"
|
|
54
|
+
label = _("Dropdown field")
|
|
55
|
+
|
|
56
|
+
def get_options(self, block_value):
|
|
57
|
+
options = super().get_options(block_value)
|
|
58
|
+
choices: List[Tuple[str, str]] = []
|
|
59
|
+
for c in block_value.get("choices"):
|
|
60
|
+
if isinstance(c, dict) and c.get("value"):
|
|
61
|
+
choices.append((c["value"].strip(), c["value"].strip()))
|
|
62
|
+
else:
|
|
63
|
+
choices.append((c.strip(), c.strip()))
|
|
64
|
+
|
|
65
|
+
if block_value.get("empty_label"):
|
|
66
|
+
choices.insert(0, ("", block_value.get("empty_label")))
|
|
67
|
+
options.update({"choices": choices})
|
|
68
|
+
return options
|
|
69
|
+
|
|
70
|
+
def get_form_block(self):
|
|
71
|
+
return blocks.StructBlock(
|
|
72
|
+
[
|
|
73
|
+
("label", blocks.CharBlock()),
|
|
74
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
75
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
76
|
+
("empty_label", blocks.CharBlock(required=False)),
|
|
77
|
+
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
|
|
78
|
+
],
|
|
79
|
+
icon=self.icon,
|
|
80
|
+
label=self.label,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MultiSelectField(BaseField):
|
|
85
|
+
field_class = forms.MultipleChoiceField
|
|
86
|
+
icon = "list-ul"
|
|
87
|
+
label = _("Multiselect field")
|
|
88
|
+
|
|
89
|
+
def get_options(self, block_value):
|
|
90
|
+
options = super().get_options(block_value)
|
|
91
|
+
choices: List[Tuple[str, str]] = []
|
|
92
|
+
for c in block_value.get("choices"):
|
|
93
|
+
if isinstance(c, dict) and c.get("value"):
|
|
94
|
+
choices.append((c["value"].strip(), c["value"].strip()))
|
|
95
|
+
else:
|
|
96
|
+
choices.append((c.strip(), c.strip()))
|
|
97
|
+
options.update({"choices": choices})
|
|
98
|
+
return options
|
|
99
|
+
|
|
100
|
+
def get_form_block(self):
|
|
101
|
+
return blocks.StructBlock(
|
|
102
|
+
[
|
|
103
|
+
("label", blocks.CharBlock()),
|
|
104
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
105
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
106
|
+
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
|
|
107
|
+
],
|
|
108
|
+
icon=self.icon,
|
|
109
|
+
label=self.label,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RadioField(BaseField):
|
|
114
|
+
field_class = forms.ChoiceField
|
|
115
|
+
widget = forms.widgets.RadioSelect
|
|
116
|
+
icon = "radio-empty"
|
|
117
|
+
label = _("Radio buttons")
|
|
118
|
+
|
|
119
|
+
def get_options(self, block_value):
|
|
120
|
+
options = super().get_options(block_value)
|
|
121
|
+
choices: List[Tuple[str, str]] = []
|
|
122
|
+
for c in block_value.get("choices"):
|
|
123
|
+
if isinstance(c, dict) and c.get("value"):
|
|
124
|
+
choices.append((c["value"].strip(), c["value"].strip()))
|
|
125
|
+
else:
|
|
126
|
+
choices.append((c.strip(), c.strip()))
|
|
127
|
+
options.update({"choices": choices})
|
|
128
|
+
return options
|
|
129
|
+
|
|
130
|
+
def get_form_block(self):
|
|
131
|
+
return blocks.StructBlock(
|
|
132
|
+
[
|
|
133
|
+
("label", blocks.CharBlock()),
|
|
134
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
135
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
136
|
+
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
|
|
137
|
+
],
|
|
138
|
+
icon=self.icon,
|
|
139
|
+
label=self.label,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class CheckboxesField(BaseField):
|
|
144
|
+
field_class = forms.MultipleChoiceField
|
|
145
|
+
widget = forms.widgets.CheckboxSelectMultiple
|
|
146
|
+
icon = "tick-inverse"
|
|
147
|
+
label = _("Checkboxes")
|
|
148
|
+
|
|
149
|
+
def get_options(self, block_value):
|
|
150
|
+
options = super().get_options(block_value)
|
|
151
|
+
choices: List[Tuple[str, str]] = []
|
|
152
|
+
for c in block_value.get("choices"):
|
|
153
|
+
if isinstance(c, dict) and c.get("value"):
|
|
154
|
+
choices.append((c["value"].strip(), c["value"].strip()))
|
|
155
|
+
else:
|
|
156
|
+
choices.append((c.strip(), c.strip()))
|
|
157
|
+
options.update({"choices": choices})
|
|
158
|
+
return options
|
|
159
|
+
|
|
160
|
+
def get_form_block(self):
|
|
161
|
+
return blocks.StructBlock(
|
|
162
|
+
[
|
|
163
|
+
("label", blocks.CharBlock()),
|
|
164
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
165
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
166
|
+
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
|
|
167
|
+
],
|
|
168
|
+
icon=self.icon,
|
|
169
|
+
label=self.label,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class CheckboxField(BaseField):
|
|
174
|
+
field_class = forms.BooleanField
|
|
175
|
+
icon = "tick-inverse"
|
|
176
|
+
label = _("Checkbox field")
|
|
177
|
+
|
|
178
|
+
def get_form_block(self):
|
|
179
|
+
return blocks.StructBlock(
|
|
180
|
+
[
|
|
181
|
+
("label", blocks.CharBlock()),
|
|
182
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
183
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
184
|
+
],
|
|
185
|
+
icon=self.icon,
|
|
186
|
+
label=self.label,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class HiddenField(BaseField):
|
|
191
|
+
field_class = forms.CharField
|
|
192
|
+
widget = forms.widgets.HiddenInput
|
|
193
|
+
icon = "no-view"
|
|
194
|
+
label = _("Hidden field")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class SingleFileField(BaseField):
|
|
198
|
+
field_class = forms.FileField
|
|
199
|
+
widget = forms.widgets.FileInput
|
|
200
|
+
icon = "doc-full-inverse"
|
|
201
|
+
label = _("File field")
|
|
202
|
+
|
|
203
|
+
def get_form_block(self):
|
|
204
|
+
return blocks.StructBlock(
|
|
205
|
+
[
|
|
206
|
+
("label", blocks.CharBlock()),
|
|
207
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
208
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
209
|
+
],
|
|
210
|
+
icon=self.icon,
|
|
211
|
+
label=self.label,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class MultipleFileInput(forms.ClearableFileInput):
|
|
216
|
+
allow_multiple_selected = True
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class MultipleFileField(forms.FileField):
|
|
220
|
+
def __init__(self, *args, **kwargs):
|
|
221
|
+
kwargs.setdefault("widget", MultipleFileInput())
|
|
222
|
+
super().__init__(*args, **kwargs)
|
|
223
|
+
|
|
224
|
+
def clean(self, data, initial=None):
|
|
225
|
+
single_file_clean = super().clean
|
|
226
|
+
if data and isinstance(data, (list, tuple)):
|
|
227
|
+
result = [single_file_clean(d, initial) for d in data]
|
|
228
|
+
else:
|
|
229
|
+
result = single_file_clean(data, initial)
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class MultiFileField(BaseField):
|
|
234
|
+
field_class = MultipleFileField
|
|
235
|
+
widget = MultipleFileInput(attrs={"multiple": True})
|
|
236
|
+
icon = "doc-full-inverse"
|
|
237
|
+
label = _("Files field")
|
|
238
|
+
|
|
239
|
+
def get_form_block(self):
|
|
240
|
+
return blocks.StructBlock(
|
|
241
|
+
[
|
|
242
|
+
("label", blocks.CharBlock()),
|
|
243
|
+
("help_text", blocks.CharBlock(required=False)),
|
|
244
|
+
("required", blocks.BooleanBlock(required=False)),
|
|
245
|
+
],
|
|
246
|
+
icon=self.icon,
|
|
247
|
+
label=self.label,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
FIELD_MAPPING = {
|
|
252
|
+
"singleline": SingleLineTextField,
|
|
253
|
+
"multiline": MultiLineTextField,
|
|
254
|
+
"date": DateField,
|
|
255
|
+
"datetime": DateTimeField,
|
|
256
|
+
"email": EmailField,
|
|
257
|
+
"url": URLField,
|
|
258
|
+
"number": NumberField,
|
|
259
|
+
"dropdown": DropdownField,
|
|
260
|
+
"radio": RadioField,
|
|
261
|
+
"checkboxes": CheckboxesField,
|
|
262
|
+
"checkbox": CheckboxField,
|
|
263
|
+
"hidden": HiddenField,
|
|
264
|
+
"singlefile": SingleFileField,
|
|
265
|
+
"multifile": MultiFileField,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
enabled_fields = get_setting("ENABLED_FIELDS")
|
|
269
|
+
|
|
270
|
+
for field_name in enabled_fields:
|
|
271
|
+
cls = FIELD_MAPPING.get(field_name, None)
|
|
272
|
+
if not cls:
|
|
273
|
+
raise KeyError("Field with name '%s' does not exist" % field_name)
|
|
274
|
+
register(field_name, cls)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from django.template.defaultfilters import pluralize
|
|
4
|
+
|
|
5
|
+
from wagtailstreamforms.hooks import register
|
|
6
|
+
from wagtailstreamforms.models import FormSubmissionFile
|
|
7
|
+
from wagtailstreamforms.serializers import FormSubmissionSerializer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@register("process_form_submission")
|
|
11
|
+
def save_form_submission_data(instance, form):
|
|
12
|
+
"""saves the form submission data"""
|
|
13
|
+
|
|
14
|
+
# copy the cleaned_data so we dont mess with the original
|
|
15
|
+
submission_data = form.cleaned_data.copy()
|
|
16
|
+
|
|
17
|
+
# change the submission data to a count of the files
|
|
18
|
+
for field in form.files.keys():
|
|
19
|
+
count = len(form.files.getlist(field))
|
|
20
|
+
submission_data[field] = "{} file{}".format(count, pluralize(count))
|
|
21
|
+
|
|
22
|
+
# save the submission data
|
|
23
|
+
submission = instance.get_submission_class().objects.create(
|
|
24
|
+
form_data=json.dumps(submission_data, cls=FormSubmissionSerializer),
|
|
25
|
+
form=instance,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# save the form files
|
|
29
|
+
for field in form.files:
|
|
30
|
+
for file in form.files.getlist(field):
|
|
31
|
+
FormSubmissionFile.objects.create(submission=submission, field=field, file=file)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wagtailstreamforms
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Author-email: Lab Digital BV <info@labdigital.nl>
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Classifier: License :: Other/Proprietary License
|
|
7
|
+
Classifier: Programming Language :: Python
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: wagtail<7.0,>=4.1
|
|
18
|
+
Requires-Dist: Unidecode<2.0,>=0.04.14
|
|
19
|
+
Requires-Dist: wagtail-generic-chooser<0.7,>=0.5.0
|
|
20
|
+
Requires-Dist: wagtail-modeladmin>=2.2.0
|
|
21
|
+
Requires-Dist: django-recaptcha>=4.0.0
|
|
22
|
+
Requires-Dist: psycopg>=3.2.9
|
|
23
|
+
Requires-Dist: tox>=4.26.0
|
|
24
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
wagtailstreamforms/__init__.py,sha256=jegJMYuSW90VKiMotVdNAakBvYVPirm-A5oeyJVuhH8,22
|
|
2
|
+
wagtailstreamforms/blocks.py,sha256=IgLGZt8vribjHR_2_Wz7PqmxCs_J5XB5xtMPS6ufI0c,3052
|
|
3
|
+
wagtailstreamforms/conf.py,sha256=-9RxLbU2WifnOItQWsmlCoi1DfbRpPdZMau-cS1oQ20,876
|
|
4
|
+
wagtailstreamforms/fields.py,sha256=Bnsf98OAiKlDw8LVx9u9NNeZ9TG13TigaXU87zjhMF4,6703
|
|
5
|
+
wagtailstreamforms/forms.py,sha256=wwi81uY1Rij-95SsU_NymWuusMUGwsNsOsYg_KCAMuk,1906
|
|
6
|
+
wagtailstreamforms/hooks.py,sha256=mxrYkCooKf5liNFBVraOpTpIzP2hNPdLxdzOQyvF-Ug,1609
|
|
7
|
+
wagtailstreamforms/serializers.py,sha256=GVWe8A1QpKojkMZF8swGQeUcjR-lSf60_zJ9l5mRNmg,305
|
|
8
|
+
wagtailstreamforms/streamfield.py,sha256=al6tM2635E0EUMMylL2gTGB7sIM99tgMhg3PMVPdI2A,1766
|
|
9
|
+
wagtailstreamforms/urls.py,sha256=3cS2XhBjfvrl8iBkGHQF1Db8kmQABdJ3QnR6j4y4f5E,790
|
|
10
|
+
wagtailstreamforms/wagtail_hooks.py,sha256=9xkCvwQHEIAfVy1gv47ZVhk164IMoXWY8eLiQJOLoUM,8910
|
|
11
|
+
wagtailstreamforms/wagtailstreamforms_fields.py,sha256=iHyiy75mAkxu-6uBqFzj7BOTLzmnZwGCqyC22y58a7w,8264
|
|
12
|
+
wagtailstreamforms/wagtailstreamforms_hooks.py,sha256=06FyWnNspNhDkCzV8Sikjm-cb9UrLm5N43lQ-uN-PIQ,1092
|
|
13
|
+
wagtailstreamforms/locale/es/LC_MESSAGES/django.mo,sha256=5QbzAOL5FsDtu2uT0j4hHh3gSm4tMfFydLvhQXp66KE,5217
|
|
14
|
+
wagtailstreamforms/locale/es/LC_MESSAGES/django.po,sha256=GAfbXi4qmeN76M_g_cWV5H76fl9oG7mYLhXDjEko1G4,7797
|
|
15
|
+
wagtailstreamforms/locale/nl/LC_MESSAGES/django.mo,sha256=Lq6G1E8z784nbXJZzeCX_a28rnbmC2k0PwactxP5Kcw,5391
|
|
16
|
+
wagtailstreamforms/locale/nl/LC_MESSAGES/django.po,sha256=N-R8N6TLvtelylyW4u9lGfHDN4_xglZupAuqiBBEli4,11227
|
|
17
|
+
wagtailstreamforms/locale/tr/LC_MESSAGES/django.mo,sha256=Pz4EEZkaA2w2zpHd9W-9w-CKO8S7J4UXe_qA6HLqeIw,379
|
|
18
|
+
wagtailstreamforms/locale/tr/LC_MESSAGES/django.po,sha256=CB2ilvnbsh6fJTOxm_Cvs8VhQASNGDhtkP3nKBc94hg,5948
|
|
19
|
+
wagtailstreamforms/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
wagtailstreamforms/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
wagtailstreamforms/management/commands/prunesubmissions.py,sha256=3T9Oa1x2peYj4RRsSB788vnsl-bNG_RBkfUIz0zB7mA,877
|
|
22
|
+
wagtailstreamforms/migrations/0001_initial.py,sha256=um6loNvjplnsKM4geqFlXWBfXqb-gUNylxQBqNU13Vs,25291
|
|
23
|
+
wagtailstreamforms/migrations/0002_form_site.py,sha256=Cb8ylWAKcAG0GlvSf3rg2jczJd-bPn5IKPMYBhwb_JA,553
|
|
24
|
+
wagtailstreamforms/migrations/0003_alter_form_fields.py,sha256=lbm6BXwpxZ2jXe_qGvKC1t4K_xbnldws13MMO7Rtppc,4656
|
|
25
|
+
wagtailstreamforms/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
wagtailstreamforms/models/__init__.py,sha256=GoTADDaGEIKmuLeBOLDi9899xlhiXvQCC9GQdAUK8AQ,1393
|
|
27
|
+
wagtailstreamforms/models/abstract.py,sha256=iPTVroEQI56g7GLBmu_s12UQPVFx_Ls2Bb3Vq8Psco4,318
|
|
28
|
+
wagtailstreamforms/models/file.py,sha256=TCzJoH3qW1neoCl-iBFyDGg8vbrHzeksg-Cp67fva1I,1002
|
|
29
|
+
wagtailstreamforms/models/form.py,sha256=qmGPNbfEaSNF__OhVVec-U08OVmD7wjJ7sVZ1X3FVGA,6015
|
|
30
|
+
wagtailstreamforms/models/submission.py,sha256=SjmgocMfCyEJZWnkjTm7XXftKBNNMUpRbTclZX9CaYE,739
|
|
31
|
+
wagtailstreamforms/templates/streamforms/advanced_settings.html,sha256=wsEqMxG0rBrQO3vN0r4eGa4nlEivTbwXNMqluly2anw,999
|
|
32
|
+
wagtailstreamforms/templates/streamforms/confirm_copy.html,sha256=Tlq27K7FH3ZwBdmJE_e7lwiUFtkv1045apTm2RbOqbM,1051
|
|
33
|
+
wagtailstreamforms/templates/streamforms/confirm_delete.html,sha256=99Z3ujkiJQh46Dejg8Vqd7NciJgoSL9wzhZNX4aUR50,1040
|
|
34
|
+
wagtailstreamforms/templates/streamforms/form_block.html,sha256=G0-vYbEjvDCrgMEJw8zLrB60dtYeKjjleDlsq-Qfncw,471
|
|
35
|
+
wagtailstreamforms/templates/streamforms/index_submissions.html,sha256=h8VLgyY-6dedXurrx7XPRAZxcoM99Z_q3QspKMB6ydU,4996
|
|
36
|
+
wagtailstreamforms/templates/streamforms/list_submissions.html,sha256=kYtmQYEWMvjFW_KT2ftxE__i_V3jlcYIV7a-x7dd284,1801
|
|
37
|
+
wagtailstreamforms/templates/streamforms/non_existent_form.html,sha256=tolwhcWcupsok-bKaTqGd3Tv0LRbg2j4XIt0SL401Lw,71
|
|
38
|
+
wagtailstreamforms/templates/streamforms/partials/form_field.html,sha256=vRhNs8mHercJOTq5T8pJh0Eq6I-YkOCciPKL4PWVmMs,181
|
|
39
|
+
wagtailstreamforms/templates/streamforms/partials/pagination_nav.html,sha256=wK7bY4vSP3a9sP9RFr--urH1cUNdITmWJRwHsl_tH0w,692
|
|
40
|
+
wagtailstreamforms/templates/streamforms/wagtailadmin/shared/datetimepicker_translations.html,sha256=1sC0EsFoVAhGZo34CS1OIgzc25BDX5rvlC6ON6yPuCQ,1183
|
|
41
|
+
wagtailstreamforms/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
wagtailstreamforms/templatetags/streamforms_tags.py,sha256=hBnqUvnZ1AwM2FsuCuZM02nriUZPP7g2d2jm_TkeFW4,1219
|
|
43
|
+
wagtailstreamforms/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
wagtailstreamforms/utils/apps.py,sha256=4AIcASy9nIgHDDnyHxKvWZkx8E-bDwBK50YwJtO7O84,685
|
|
45
|
+
wagtailstreamforms/utils/general.py,sha256=LijQjiWQiF4uzgRfnny1hoFjg1TMgiUgjOH1TPvCkWw,152
|
|
46
|
+
wagtailstreamforms/utils/loading.py,sha256=wSAdrXDcXGyXSnNWD1fC2Ir1fD4qnzKQHkSgGD0pu2A,990
|
|
47
|
+
wagtailstreamforms/utils/requests.py,sha256=xSE4wo8-z_ZhpVm53RqAcUllVgSyIgw3MqYROf6Cixk,351
|
|
48
|
+
wagtailstreamforms/utils/version.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
wagtailstreamforms/views/__init__.py,sha256=nwvG8U51a0D-j4gnpuoM1KTCAljN0ijSl43E6PlrqbE,215
|
|
50
|
+
wagtailstreamforms/views/advanced_settings.py,sha256=6o8cDw-RIejnWFwTtG44vKarJlz03Pb7GtxCM8BUuns,1527
|
|
51
|
+
wagtailstreamforms/views/copy.py,sha256=m-pt86NoTT8AKTiNrzmpd82eZEqDK65LGUIBGFWx--U,2698
|
|
52
|
+
wagtailstreamforms/views/submission_delete.py,sha256=FXvFmgoguliZxL3vf44BIYF73ay9mFnpUT-ovehPoWs,2578
|
|
53
|
+
wagtailstreamforms/views/submission_list.py,sha256=K18UtXmc4uwA3gxfWtgJifNvVnXCtW8nnvVzCNuiUJM,4019
|
|
54
|
+
wagtailstreamforms-0.0.0.dist-info/licenses/LICENSE,sha256=Oy8Ub6BG7n1hon-euIrnTvJ1wvgm9ZANjslBA3FY4hc,1066
|
|
55
|
+
wagtailstreamforms-0.0.0.dist-info/METADATA,sha256=3I1LUIz4ZeC2Lhp6vhTAnTaCAdH74lQAqq5DiHVwiuI,894
|
|
56
|
+
wagtailstreamforms-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
57
|
+
wagtailstreamforms-0.0.0.dist-info/top_level.txt,sha256=rdaWLHEw0fFv5h1bdErzwQW8Bd1JSDnR8s322dj6iE8,19
|
|
58
|
+
wagtailstreamforms-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2018 Accent Design Group LTD
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wagtailstreamforms
|