mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-25 05:02:57 +01:00
Use class based views instead of function views
Changes `index`, `delete_submissions` and `list_submissions`. Adjusted view tests to properly test the new class based structure. remove get_field_ordering from FormPage model - now on view
This commit is contained in:
parent
161b47d436
commit
815cb6e405
@ -29,6 +29,7 @@ Changelog
|
||||
* Upgraded jQuery to version 3.2.1 (Janneke Janssen)
|
||||
* Updated documentation styling (LB (Ben Johnston))
|
||||
* Rich text fields now take feature lists into account when whitelisting HTML elements (Matt Westcott)
|
||||
* FormPage lists and Form submission lists in admin now use class based views for easy overriding (Johan Arensman)
|
||||
* Fix: Do not remove stopwords when generating slugs from non-ASCII titles, to avoid issues with incorrect word boundaries (Sævar Öfjörð Magnússon)
|
||||
* Fix: The PostgreSQL search backend now preserves ordering of the `QuerySet` when searching with `order_by_relevance=False` (Bertrand Bordage)
|
||||
* Fix: Using `modeladmin_register` as a decorator no longer replaces the decorated class with `None` (Tim Heap)
|
||||
|
@ -604,3 +604,46 @@ Finally, we add a URL param of `id` based on the ``form_submission`` if it exist
|
||||
FieldPanel('subject'),
|
||||
], 'Email'),
|
||||
]
|
||||
|
||||
|
||||
Customise form submissions listing in Wagtail Admin
|
||||
---------------------------------------------------
|
||||
|
||||
The Admin listing of form submissions can be customised by setting the attribute ``submissions_list_view_class`` on your FormPage model.
|
||||
|
||||
The list view class must be a subclass of ``SubmissionsListView`` from ``wagtail.contrib.forms.views``, which is a child class of `Django's class based ListView <https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-display/#listview>`_.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
|
||||
from wagtail.contrib.forms.views import SubmissionsListView
|
||||
|
||||
|
||||
class CustomSubmissionsListView(SubmissionsListView):
|
||||
paginate_by = 50 # show more submissions per page, default is 20
|
||||
ordering = ('submit_time',) # order submissions by oldest first, normally newest first
|
||||
ordering_csv = ('-submit_time',) # order csv export by newest first, normally oldest first
|
||||
|
||||
# override the method to generate csv filename
|
||||
def get_csv_filename(self):
|
||||
""" Returns the filename for CSV file with page title at start"""
|
||||
filename = super().get_csv_filename()
|
||||
return self.form_page.slug + '-' + filename
|
||||
|
||||
|
||||
class FormField(AbstractFormField):
|
||||
page = ParentalKey('FormPage', related_name='form_fields')
|
||||
|
||||
|
||||
class FormPage(AbstractEmailForm):
|
||||
"""Form Page with customised submissions listing view"""
|
||||
|
||||
# set custom view class as class attribute
|
||||
submissions_list_view_class = CustomSubmissionsListView
|
||||
|
||||
intro = RichTextField(blank=True)
|
||||
thank_you_text = RichTextField(blank=True)
|
||||
|
||||
# content_panels = ...
|
||||
|
@ -42,6 +42,7 @@ Other features
|
||||
* Upgraded jQuery to version 3.2.1 (Janneke Janssen)
|
||||
* Updated documentation styling (LB (Ben Johnston))
|
||||
* Rich text fields now take feature lists into account when whitelisting HTML elements (Matt Westcott)
|
||||
* FormPage lists and Form submission lists in admin now use class based views for easy overriding (Johan Arensman)
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
@ -1,7 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import models
|
||||
from django.shortcuts import render
|
||||
@ -11,10 +10,12 @@ from unidecode import unidecode
|
||||
|
||||
from wagtail.admin.edit_handlers import FieldPanel
|
||||
from wagtail.admin.utils import send_mail
|
||||
from wagtail.core import hooks
|
||||
from wagtail.core.models import Orderable, Page, UserPagePermissionsProxy, get_page_models
|
||||
from wagtail.core.models import Orderable, Page
|
||||
|
||||
from .forms import FormBuilder, WagtailAdminFormPageForm
|
||||
from .views import SubmissionsListView
|
||||
|
||||
|
||||
|
||||
FORM_FIELD_CHOICES = (
|
||||
('singleline', _('Single line text')),
|
||||
@ -117,45 +118,16 @@ class AbstractFormField(Orderable):
|
||||
ordering = ['sort_order']
|
||||
|
||||
|
||||
_FORM_CONTENT_TYPES = None
|
||||
|
||||
|
||||
def get_form_types():
|
||||
global _FORM_CONTENT_TYPES
|
||||
if _FORM_CONTENT_TYPES is None:
|
||||
form_models = [
|
||||
model for model in get_page_models()
|
||||
if issubclass(model, AbstractForm)
|
||||
]
|
||||
|
||||
_FORM_CONTENT_TYPES = list(
|
||||
ContentType.objects.get_for_models(*form_models).values()
|
||||
)
|
||||
return _FORM_CONTENT_TYPES
|
||||
|
||||
|
||||
def get_forms_for_user(user):
|
||||
"""
|
||||
Return a queryset of form pages that this user is allowed to access the submissions for
|
||||
"""
|
||||
editable_forms = UserPagePermissionsProxy(user).editable_pages()
|
||||
editable_forms = editable_forms.filter(content_type__in=get_form_types())
|
||||
|
||||
# Apply hooks
|
||||
for fn in hooks.get_hooks('filter_form_submissions_for_user'):
|
||||
editable_forms = fn(user, editable_forms)
|
||||
|
||||
return editable_forms
|
||||
|
||||
|
||||
class AbstractForm(Page):
|
||||
"""
|
||||
A Form Page. Pages implementing a form should inherit from it
|
||||
"""
|
||||
|
||||
base_form_class = WagtailAdminFormPageForm
|
||||
|
||||
form_builder = FormBuilder
|
||||
|
||||
base_form_class = WagtailAdminFormPageForm
|
||||
submissions_list_view_class = SubmissionsListView
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -207,33 +179,6 @@ class AbstractForm(Page):
|
||||
def get_landing_page_template(self, request, *args, **kwargs):
|
||||
return self.landing_page_template
|
||||
|
||||
def get_field_ordering(self, ordering_list, default=('submit_time', 'descending')):
|
||||
"""
|
||||
Accepts a list of strings ['-submit_time', 'id']
|
||||
Returns a list of tuples [(field_name, 'ascending'/'descending'), ...]
|
||||
Intented to be used to process ordering via request.GET.getlist('order_by')
|
||||
Checks if the field options are valid, only returns valid, de-duplicated options
|
||||
invalid options are simply ignored - no error created
|
||||
"""
|
||||
valid_fields = ['id', 'submit_time']
|
||||
field_ordering = []
|
||||
if len(ordering_list) == 0:
|
||||
return [default]
|
||||
for ordering in ordering_list:
|
||||
try:
|
||||
none, prefix, field_name = ordering.rpartition('-')
|
||||
if field_name not in valid_fields:
|
||||
continue # Invalid field_name, skip it
|
||||
# only add to ordering if the field is not already set
|
||||
if field_name not in [order[0] for order in field_ordering]:
|
||||
asc_desc = 'ascending'
|
||||
if prefix == '-':
|
||||
asc_desc = 'descending'
|
||||
field_ordering.append((field_name, asc_desc))
|
||||
except (IndexError, ValueError):
|
||||
continue # Invalid ordering specified, skip it
|
||||
return field_ordering
|
||||
|
||||
def get_submission_class(self):
|
||||
"""
|
||||
Returns submission class.
|
||||
@ -273,6 +218,16 @@ class AbstractForm(Page):
|
||||
context
|
||||
)
|
||||
|
||||
def serve_submissions_list_view(self, request, *args, **kwargs):
|
||||
"""
|
||||
Returns list submissions view for admin.
|
||||
|
||||
`list_submissions_view_class` can bse set to provide custom view class.
|
||||
Your class must be inherited from SubmissionsListView.
|
||||
"""
|
||||
view = self.submissions_list_view_class.as_view()
|
||||
return view(request, form_page=self, *args, **kwargs)
|
||||
|
||||
def serve(self, request, *args, **kwargs):
|
||||
if request.method == 'POST':
|
||||
form = self.get_form(request.POST, request.FILES, page=self, user=request.user)
|
||||
|
@ -114,8 +114,7 @@
|
||||
{% if submissions %}
|
||||
<form action="{% url 'wagtailforms:delete_submissions' form_page.id %}" method="get">
|
||||
{% include "wagtailforms/list_submissions.html" %}
|
||||
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=submissions is_searching=False %}
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=page_obj is_searching=False linkurl='-' %}
|
||||
{# Here we pass an invalid non-empty URL name as linkurl to generate pagination links with the URL path omitted #}
|
||||
</form>
|
||||
{% else %}
|
||||
|
@ -6,13 +6,13 @@
|
||||
<col />
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="{{ data_fields_with_ordering|length|add:1 }}">
|
||||
<th colspan="{{ data_headings|length|add:1 }}">
|
||||
<button class="button no" id="delete-submissions" style="visibility: hidden">{% trans "Delete selected submissions" %}</button>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="select-all" /></th>
|
||||
{% for heading in data_fields_with_ordering %}
|
||||
{% for heading in data_headings %}
|
||||
<th id="{{ heading.name }}" class="{% if heading.order %}ordered {{ heading.order }}{% endif %}">
|
||||
{% if heading.order %}<a href="?order_by={% if heading.order == 'ascending' %}-{% endif %}{{ heading.name }}">{{ heading.label }}</a>{% else %}{{ heading.label }}{% endif %}
|
||||
</th>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% if form_pages %}
|
||||
{% include "wagtailforms/list_forms.html" %}
|
||||
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=form_pages linkurl="wagtailforms:index" %}
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=page_obj linkurl="wagtailforms:index" %}
|
||||
{% else %}
|
||||
<p>{% trans "No form pages have been created." %}</p>
|
||||
{% endif %}
|
||||
|
@ -5,20 +5,22 @@ from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from wagtail.tests.testapp.models import (
|
||||
CustomFormPageSubmission, FormField, FormFieldWithCustomSubmission,
|
||||
FormPage, FormPageWithCustomSubmission)
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.admin.edit_handlers import get_form_for_model
|
||||
from wagtail.admin.forms import WagtailAdminPageForm
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.contrib.forms.edit_handlers import FormSubmissionsPanel
|
||||
from wagtail.contrib.forms.models import FormSubmission
|
||||
from wagtail.contrib.forms.tests.utils import make_form_page, make_form_page_with_custom_submission
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.tests.testapp.models import (
|
||||
CustomFormPageSubmission, FormField, FormFieldForCustomListViewPage,
|
||||
FormFieldWithCustomSubmission, FormPage, FormPageWithCustomSubmission,
|
||||
FormPageWithCustomSubmissionListView)
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class TestFormResponsesPanel(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
self.form_page = make_form_page()
|
||||
|
||||
self.FormPageForm = get_form_for_model(
|
||||
@ -133,7 +135,7 @@ class TestFormsIndex(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index.html')
|
||||
|
||||
# Check that we got the correct page
|
||||
self.assertEqual(response.context['form_pages'].number, 2)
|
||||
self.assertEqual(response.context['page_obj'].number, 2)
|
||||
|
||||
def test_forms_index_pagination_invalid(self):
|
||||
# Create some more form pages to make pagination kick in
|
||||
@ -147,7 +149,7 @@ class TestFormsIndex(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index.html')
|
||||
|
||||
# Check that it got page one
|
||||
self.assertEqual(response.context['form_pages'].number, 1)
|
||||
self.assertEqual(response.context['page_obj'].number, 1)
|
||||
|
||||
def test_forms_index_pagination_out_of_range(self):
|
||||
# Create some more form pages to make pagination kick in
|
||||
@ -161,7 +163,7 @@ class TestFormsIndex(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index.html')
|
||||
|
||||
# Check that it got the last page
|
||||
self.assertEqual(response.context['form_pages'].number, response.context['form_pages'].paginator.num_pages)
|
||||
self.assertEqual(response.context['page_obj'].number, response.context['paginator'].num_pages)
|
||||
|
||||
def test_cannot_see_forms_without_permission(self):
|
||||
# Login with as a user without permission to see forms
|
||||
@ -315,7 +317,7 @@ class TestFormsSubmissionsList(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# Check that we got the correct page
|
||||
self.assertEqual(response.context['submissions'].number, 2)
|
||||
self.assertEqual(response.context['page_obj'].number, 2)
|
||||
|
||||
def test_list_submissions_pagination_invalid(self):
|
||||
self.make_list_submissions()
|
||||
@ -329,7 +331,7 @@ class TestFormsSubmissionsList(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# Check that we got page one
|
||||
self.assertEqual(response.context['submissions'].number, 1)
|
||||
self.assertEqual(response.context['page_obj'].number, 1)
|
||||
|
||||
def test_list_submissions_pagination_out_of_range(self):
|
||||
self.make_list_submissions()
|
||||
@ -341,7 +343,7 @@ class TestFormsSubmissionsList(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# Check that we got the last page
|
||||
self.assertEqual(response.context['submissions'].number, response.context['submissions'].paginator.num_pages)
|
||||
self.assertEqual(response.context['page_obj'].number, response.context['paginator'].num_pages)
|
||||
|
||||
def test_list_submissions_default_order(self):
|
||||
response = self.client.get(reverse(
|
||||
@ -415,6 +417,28 @@ class TestFormsSubmissionsExport(TestCase, WagtailTestUtils):
|
||||
self.assertEqual(data_lines[1], '2013-01-01 12:00:00+00:00,old@example.com,this is a really old message,"foo, baz"\r')
|
||||
self.assertEqual(data_lines[2], '2014-01-01 12:00:00+00:00,new@example.com,this is a fairly new message,None\r')
|
||||
|
||||
def test_list_submissions_csv_large_export(self):
|
||||
for i in range(100):
|
||||
new_form_submission = FormSubmission.objects.create(
|
||||
page=self.form_page,
|
||||
form_data=json.dumps({
|
||||
'your-email': "new@example-%s.com" % i,
|
||||
'your-message': "I like things x %s" % i,
|
||||
}),
|
||||
)
|
||||
new_form_submission.submit_time = '2014-01-01T12:00:00.000Z'
|
||||
new_form_submission.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse('wagtailforms:list_submissions', args=(self.form_page.id,)),
|
||||
{'action': 'CSV'}
|
||||
)
|
||||
|
||||
# Check that csv export is not paginated
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data_lines = response.content.decode().split("\n")
|
||||
self.assertEqual(104, len(data_lines))
|
||||
|
||||
def test_list_submissions_csv_export_after_filter_form_submissions_for_user_hook(self):
|
||||
# Hook forbids to delete form submissions for everyone
|
||||
def construct_forms_for_user(user, queryset):
|
||||
@ -800,7 +824,7 @@ class TestCustomFormsSubmissionsList(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# Check that we got the correct page
|
||||
self.assertEqual(response.context['submissions'].number, 2)
|
||||
self.assertEqual(response.context['page_obj'].number, 2)
|
||||
|
||||
# CustomFormPageSubmission have custom field. This field should appear in the listing
|
||||
self.assertContains(response, '<th id="username" class="">Username</th>', html=True)
|
||||
@ -818,7 +842,7 @@ class TestCustomFormsSubmissionsList(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# Check that we got page one
|
||||
self.assertEqual(response.context['submissions'].number, 1)
|
||||
self.assertEqual(response.context['page_obj'].number, 1)
|
||||
|
||||
def test_list_submissions_pagination_out_of_range(self):
|
||||
self.make_list_submissions()
|
||||
@ -831,8 +855,7 @@ class TestCustomFormsSubmissionsList(TestCase, WagtailTestUtils):
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# Check that we got the last page
|
||||
self.assertEqual(response.context['submissions'].number,
|
||||
response.context['submissions'].paginator.num_pages)
|
||||
self.assertEqual(response.context['page_obj'].number, response.context['paginator'].num_pages)
|
||||
|
||||
|
||||
class TestDeleteFormSubmission(TestCase, WagtailTestUtils):
|
||||
@ -969,6 +992,146 @@ class TestDeleteCustomFormSubmission(TestCase):
|
||||
self.assertEqual(CustomFormPageSubmission.objects.count(), 2)
|
||||
|
||||
|
||||
class TestFormsWithCustomSubmissionsList(TestCase, WagtailTestUtils):
|
||||
|
||||
def create_test_user_without_admin(self, username):
|
||||
return get_user_model().objects.create_user(username=username, password='123')
|
||||
|
||||
def setUp(self):
|
||||
# Create a form page
|
||||
|
||||
home_page = Page.objects.get(url_path='/home/')
|
||||
self.form_page = home_page.add_child(
|
||||
instance=FormPageWithCustomSubmissionListView(
|
||||
title='Willy Wonka Chocolate Ideas',
|
||||
slug='willy-wonka-chocolate-ideas',
|
||||
to_address='willy@wonka.com',
|
||||
from_address='info@wonka.com',
|
||||
subject='Chocolate Idea Submitted!'
|
||||
)
|
||||
)
|
||||
FormFieldForCustomListViewPage.objects.create(
|
||||
page=self.form_page, sort_order=1, label='Your email', field_type='email', required=True,
|
||||
)
|
||||
FormFieldForCustomListViewPage.objects.create(
|
||||
page=self.form_page, sort_order=2, label='Chocolate', field_type='singleline', required=True,
|
||||
)
|
||||
FormFieldForCustomListViewPage.objects.create(
|
||||
page=self.form_page, sort_order=3, label='Ingredients', field_type='multiline', required=True,
|
||||
)
|
||||
self.choices = ['What is chocolate?', 'Mediocre', 'Much excitement', 'Wet my pants excited!']
|
||||
FormFieldForCustomListViewPage.objects.create(
|
||||
page=self.form_page, sort_order=4, label='Your Excitement', field_type='radio', required=True,
|
||||
choices=','.join(self.choices),
|
||||
)
|
||||
|
||||
self.test_user_1 = self.create_test_user_without_admin('user-chocolate-maniac')
|
||||
self.test_user_2 = self.create_test_user_without_admin('user-chocolate-guy')
|
||||
|
||||
# add a couple of initial form submissions for testing ordering
|
||||
new_form_submission = CustomFormPageSubmission.objects.create(
|
||||
page=self.form_page,
|
||||
user=self.test_user_1,
|
||||
form_data=json.dumps({
|
||||
'your-email': 'new@example.com',
|
||||
'chocolate': 'White Chocolate',
|
||||
'ingredients': 'White colouring',
|
||||
'your-excitement': self.choices[2],
|
||||
}),
|
||||
)
|
||||
new_form_submission.submit_time = '2017-10-01T12:00:00.000Z'
|
||||
new_form_submission.save()
|
||||
|
||||
old_form_submission = CustomFormPageSubmission.objects.create(
|
||||
page=self.form_page,
|
||||
user=self.test_user_2,
|
||||
form_data=json.dumps({
|
||||
'your-email': 'old@example.com',
|
||||
'chocolate': 'Dark Chocolate',
|
||||
'ingredients': 'Charcoal',
|
||||
'your-excitement': self.choices[0],
|
||||
}),
|
||||
)
|
||||
old_form_submission.submit_time = '2017-01-01T12:00:00.000Z'
|
||||
old_form_submission.save()
|
||||
|
||||
self.login()
|
||||
|
||||
def make_list_submissions(self):
|
||||
""" Make 100 submissions to test pagination on the forms submissions page """
|
||||
for i in range(120):
|
||||
submission = CustomFormPageSubmission(
|
||||
page=self.form_page,
|
||||
user=self.test_user_1,
|
||||
form_data=json.dumps({
|
||||
'your-email': "foo-%s@bar.com" % i,
|
||||
'chocolate': 'Chocolate No.%s' % i,
|
||||
'your-excitement': self.choices[3],
|
||||
}),
|
||||
)
|
||||
submission.save()
|
||||
|
||||
def test_list_submissions(self):
|
||||
response = self.client.get(reverse('wagtailforms:list_submissions', args=(self.form_page.id,)))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
self.assertEqual(len(response.context['data_rows']), 2)
|
||||
|
||||
# check display of list values within form submissions
|
||||
self.assertContains(response, 'Much excitement')
|
||||
self.assertContains(response, 'White Chocolate')
|
||||
self.assertContains(response, 'Dark Chocolate')
|
||||
|
||||
def test_list_submissions_pagination(self):
|
||||
self.make_list_submissions()
|
||||
|
||||
response = self.client.get(reverse('wagtailforms:list_submissions', args=(self.form_page.id,)), {'p': 2})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailforms/index_submissions.html')
|
||||
|
||||
# test that paginate by 50 is working, should be 3 max pages (~120 values)
|
||||
self.assertContains(response, 'Page 2 of 3')
|
||||
self.assertContains(response, 'Wet my pants excited!', count=50)
|
||||
self.assertEqual(response.context['page_obj'].number, 2)
|
||||
|
||||
def test_list_submissions_csv_export(self):
|
||||
response = self.client.get(
|
||||
reverse('wagtailforms:list_submissions', args=(self.form_page.id,)),
|
||||
{'action': 'CSV'}
|
||||
)
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data_lines = response.content.decode().split("\n")
|
||||
self.assertIn('filename=%s-export' % self.form_page.slug, response.get('Content-Disposition'))
|
||||
self.assertEqual(data_lines[0], 'Username,Submission date,Your email,Chocolate,Ingredients,Your Excitement\r')
|
||||
# first result should be the most recent as order_csv has been reversed
|
||||
self.assertEqual(data_lines[1], 'user-chocolate-maniac,2017-10-01 12:00:00+00:00,new@example.com,White Chocolate,White colouring,Much excitement\r')
|
||||
self.assertEqual(data_lines[2], 'user-chocolate-guy,2017-01-01 12:00:00+00:00,old@example.com,Dark Chocolate,Charcoal,What is chocolate?\r')
|
||||
|
||||
def test_list_submissions_ordering(self):
|
||||
form_submission = CustomFormPageSubmission.objects.create(
|
||||
page=self.form_page,
|
||||
user=self.create_test_user_without_admin('user-aaa-aaa'),
|
||||
form_data=json.dumps({
|
||||
'your-email': 'new@example.com',
|
||||
'chocolate': 'Old chocolate idea',
|
||||
'ingredients': 'Sugar',
|
||||
'your-excitement': self.choices[2],
|
||||
}),
|
||||
)
|
||||
form_submission.submit_time = '2016-01-01T12:00:00.000Z'
|
||||
form_submission.save()
|
||||
|
||||
# check ordering matches default which is overriden to be 'submit_time' (oldest first)
|
||||
response = self.client.get(reverse('wagtailforms:list_submissions', args=(self.form_page.id,)))
|
||||
first_row_values = response.context['data_rows'][0]['fields']
|
||||
self.assertTrue('Old chocolate idea' in first_row_values)
|
||||
|
||||
|
||||
class TestIssue585(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.contrib.forms import views
|
||||
from wagtail.contrib.forms.views import DeleteSubmissionsView, FormPagesListView, get_submissions_list_view
|
||||
|
||||
app_name = 'wagtailforms'
|
||||
urlpatterns = [
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(r'^submissions/(\d+)/$', views.list_submissions, name='list_submissions'),
|
||||
url(r'^submissions/(\d+)/delete/$', views.delete_submissions, name='delete_submissions')
|
||||
url(r'^$', FormPagesListView.as_view(), name='index'),
|
||||
url(r'^submissions/(?P<page_id>\d+)/$', get_submissions_list_view, name='list_submissions'),
|
||||
url(r'^submissions/(?P<page_id>\d+)/delete/$', DeleteSubmissionsView.as_view(), name='delete_submissions')
|
||||
]
|
||||
|
36
wagtail/contrib/forms/utils.py
Normal file
36
wagtail/contrib/forms/utils.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from wagtail.core import hooks
|
||||
from wagtail.core.models import UserPagePermissionsProxy, get_page_models
|
||||
|
||||
|
||||
_FORM_CONTENT_TYPES = None
|
||||
|
||||
|
||||
def get_form_types():
|
||||
global _FORM_CONTENT_TYPES
|
||||
if _FORM_CONTENT_TYPES is None:
|
||||
from wagtail.contrib.forms.models import AbstractForm
|
||||
form_models = [
|
||||
model for model in get_page_models()
|
||||
if issubclass(model, AbstractForm)
|
||||
]
|
||||
|
||||
_FORM_CONTENT_TYPES = list(
|
||||
ContentType.objects.get_for_models(*form_models).values()
|
||||
)
|
||||
return _FORM_CONTENT_TYPES
|
||||
|
||||
|
||||
def get_forms_for_user(user):
|
||||
"""
|
||||
Return a queryset of form pages that this user is allowed to access the submissions for
|
||||
"""
|
||||
editable_forms = UserPagePermissionsProxy(user).editable_pages()
|
||||
editable_forms = editable_forms.filter(content_type__in=get_form_types())
|
||||
|
||||
# Apply hooks
|
||||
for fn in hooks.get_hooks('filter_form_submissions_for_user'):
|
||||
editable_forms = fn(user, editable_forms)
|
||||
|
||||
return editable_forms
|
@ -2,152 +2,311 @@ import csv
|
||||
import datetime
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import InvalidPage
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ungettext
|
||||
from django.views.generic import ListView, TemplateView
|
||||
|
||||
from wagtail.utils.pagination import paginate
|
||||
from wagtail.admin import messages
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.contrib.forms.forms import SelectDateForm
|
||||
from wagtail.contrib.forms.models import get_forms_for_user
|
||||
from wagtail.contrib.forms.utils import get_forms_for_user
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.utils.pagination import DEFAULT_PAGE_KEY
|
||||
|
||||
|
||||
def index(request):
|
||||
form_pages = get_forms_for_user(request.user)
|
||||
|
||||
paginator, form_pages = paginate(request, form_pages)
|
||||
|
||||
return render(request, 'wagtailforms/index.html', {
|
||||
'form_pages': form_pages,
|
||||
})
|
||||
def get_submissions_list_view(request, *args, **kwargs):
|
||||
""" Call the form page's list submissions view class """
|
||||
page_id = kwargs.get('page_id')
|
||||
form_page = get_object_or_404(Page, id=page_id).specific
|
||||
return form_page.serve_submissions_list_view(request, *args, **kwargs)
|
||||
|
||||
|
||||
def delete_submissions(request, page_id):
|
||||
if not get_forms_for_user(request.user).filter(id=page_id).exists():
|
||||
raise PermissionDenied
|
||||
class SafePaginateListView(ListView):
|
||||
""" Listing view with safe pagination, allowing incorrect or out of range values """
|
||||
|
||||
page = get_object_or_404(Page, id=page_id).specific
|
||||
paginate_by = 20
|
||||
page_kwarg = DEFAULT_PAGE_KEY
|
||||
|
||||
# Get submissions
|
||||
submission_ids = request.GET.getlist('selected-submissions')
|
||||
submissions = page.get_submission_class()._default_manager.filter(id__in=submission_ids)
|
||||
def paginate_queryset(self, queryset, page_size):
|
||||
"""Paginate the queryset if needed with nice defaults on invalid param."""
|
||||
paginator = self.get_paginator(
|
||||
queryset,
|
||||
page_size,
|
||||
orphans=self.get_paginate_orphans(),
|
||||
allow_empty_first_page=self.get_allow_empty()
|
||||
)
|
||||
page_kwarg = self.page_kwarg
|
||||
page_request = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 0
|
||||
try:
|
||||
page_number = int(page_request)
|
||||
except ValueError:
|
||||
if page_request == 'last':
|
||||
page_number = paginator.num_pages
|
||||
else:
|
||||
page_number = 0
|
||||
try:
|
||||
if page_number > paginator.num_pages:
|
||||
page_number = paginator.num_pages # page out of range, show last page
|
||||
page = paginator.page(page_number)
|
||||
return (paginator, page, page.object_list, page.has_other_pages())
|
||||
except InvalidPage:
|
||||
page = paginator.page(1)
|
||||
return (paginator, page, page.object_list, page.has_other_pages())
|
||||
return super().paginage_queryset(queryset, page_size)
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
class FormPagesListView(SafePaginateListView):
|
||||
""" Lists the available form pages for the current user """
|
||||
template_name = 'wagtailforms/index.html'
|
||||
context_object_name = 'form_pages'
|
||||
|
||||
def get_queryset(self):
|
||||
""" Return the queryset of form pages for this view """
|
||||
queryset = get_forms_for_user(self.request.user)
|
||||
ordering = self.get_ordering()
|
||||
if ordering:
|
||||
if isinstance(ordering, str):
|
||||
ordering = (ordering,)
|
||||
queryset = queryset.order_by(*ordering)
|
||||
return queryset
|
||||
|
||||
|
||||
class DeleteSubmissionsView(TemplateView):
|
||||
""" Delete the selected submissions """
|
||||
template_name = 'wagtailforms/confirm_delete.html'
|
||||
page = None
|
||||
submissions = None
|
||||
success_url = 'wagtailforms:list_submissions'
|
||||
|
||||
def get_queryset(self):
|
||||
""" Returns a queryset for the selected submissions """
|
||||
submission_ids = self.request.GET.getlist('selected-submissions')
|
||||
submission_class = self.page.get_submission_class()
|
||||
return submission_class._default_manager.filter(id__in=submission_ids)
|
||||
|
||||
def handle_delete(self, submissions):
|
||||
""" Deletes the given queryset """
|
||||
count = submissions.count()
|
||||
submissions.delete()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
self.request,
|
||||
ungettext(
|
||||
"One submission has been deleted.",
|
||||
"%(count)d submissions have been deleted.",
|
||||
'One submission has been deleted.',
|
||||
'%(count)d submissions have been deleted.',
|
||||
count
|
||||
) % {
|
||||
'count': count,
|
||||
}
|
||||
) % {'count': count}
|
||||
)
|
||||
|
||||
return redirect('wagtailforms:list_submissions', page_id)
|
||||
def get_success_url(self):
|
||||
""" Returns the success URL to redirect to after a successful deletion """
|
||||
return self.success_url
|
||||
|
||||
return render(request, 'wagtailforms/confirm_delete.html', {
|
||||
'page': page,
|
||||
'submissions': submissions,
|
||||
})
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Check permissions, set the page and submissions, handle delete """
|
||||
page_id = kwargs.get('page_id')
|
||||
|
||||
if not get_forms_for_user(self.request.user).filter(id=page_id).exists():
|
||||
raise PermissionDenied
|
||||
|
||||
def list_submissions(request, page_id):
|
||||
if not get_forms_for_user(request.user).filter(id=page_id).exists():
|
||||
raise PermissionDenied
|
||||
self.page = get_object_or_404(Page, id=page_id).specific
|
||||
|
||||
form_page = get_object_or_404(Page, id=page_id).specific
|
||||
form_submission_class = form_page.get_submission_class()
|
||||
self.submissions = self.get_queryset()
|
||||
|
||||
data_fields = form_page.get_data_fields()
|
||||
if self.request.method == 'POST':
|
||||
self.handle_delete(self.submissions)
|
||||
return redirect(self.get_success_url(), page_id)
|
||||
|
||||
ordering = form_page.get_field_ordering(request.GET.getlist('order_by'))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
# convert ordering tuples to a list of strings like ['-submit_time']
|
||||
ordering_strings = [
|
||||
'%s%s' % ('-' if order_str[1] == 'descending' else '', order_str[0])
|
||||
for order_str in ordering]
|
||||
def get_context_data(self, **kwargs):
|
||||
""" Get the context for this view """
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
if request.GET.get('action') == 'CSV':
|
||||
# Revert to CSV being sorted submit_time ascending for backwards compatibility
|
||||
submissions = form_submission_class.objects.filter(page=form_page).order_by('submit_time')
|
||||
else:
|
||||
submissions = form_submission_class.objects.filter(page=form_page).order_by(*ordering_strings)
|
||||
|
||||
data_fields_with_ordering = []
|
||||
for name, label in data_fields:
|
||||
order = None
|
||||
for order_value in [o[1] for o in ordering if o[0] == name]:
|
||||
order = order_value
|
||||
data_fields_with_ordering.append({
|
||||
"name": name,
|
||||
"label": label,
|
||||
"order": order,
|
||||
context.update({
|
||||
'page': self.page,
|
||||
'submissions': self.submissions,
|
||||
})
|
||||
|
||||
data_headings = [label for name, label in data_fields]
|
||||
return context
|
||||
|
||||
select_date_form = SelectDateForm(request.GET)
|
||||
if select_date_form.is_valid():
|
||||
date_from = select_date_form.cleaned_data.get('date_from')
|
||||
date_to = select_date_form.cleaned_data.get('date_to')
|
||||
# careful: date_to should be increased by 1 day since the submit_time
|
||||
# is a time so it will always be greater
|
||||
if date_to:
|
||||
date_to += datetime.timedelta(days=1)
|
||||
if date_from and date_to:
|
||||
submissions = submissions.filter(submit_time__range=[date_from, date_to])
|
||||
elif date_from and not date_to:
|
||||
submissions = submissions.filter(submit_time__gte=date_from)
|
||||
elif not date_from and date_to:
|
||||
submissions = submissions.filter(submit_time__lte=date_to)
|
||||
|
||||
if request.GET.get('action') == 'CSV':
|
||||
# return a CSV instead
|
||||
class SubmissionsListView(SafePaginateListView):
|
||||
""" Lists submissions for the provided form page """
|
||||
template_name = 'wagtailforms/index_submissions.html'
|
||||
context_object_name = 'submissions'
|
||||
form_page = None
|
||||
ordering = ('-submit_time',)
|
||||
ordering_csv = ('submit_time',) # keep legacy CSV ordering
|
||||
orderable_fields = ('id', 'submit_time',) # used to validate ordering in URL
|
||||
select_date_form = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Check permissions and set the form page """
|
||||
|
||||
self.form_page = kwargs.get('form_page')
|
||||
|
||||
if not get_forms_for_user(request.user).filter(pk=self.form_page.id).exists():
|
||||
raise PermissionDenied
|
||||
|
||||
self.is_csv_export = (self.request.GET.get('action') == 'CSV')
|
||||
if self.is_csv_export:
|
||||
self.paginate_by = None
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
""" Return queryset of form submissions with filter and order_by applied """
|
||||
submission_class = self.form_page.get_submission_class()
|
||||
queryset = submission_class._default_manager.filter(page=self.form_page)
|
||||
|
||||
filtering = self.get_filtering()
|
||||
if filtering and isinstance(filtering, dict):
|
||||
queryset = queryset.filter(**filtering)
|
||||
|
||||
ordering = self.get_ordering()
|
||||
if ordering:
|
||||
if isinstance(ordering, str):
|
||||
ordering = (ordering,)
|
||||
queryset = queryset.order_by(*ordering)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_paginate_by(self, queryset):
|
||||
""" Get the number of items to paginate by, or ``None`` for no pagination """
|
||||
if self.is_csv_export:
|
||||
return None
|
||||
return self.paginate_by
|
||||
|
||||
def get_validated_ordering(self):
|
||||
""" Return a dict of field names with ordering labels if ordering is valid """
|
||||
orderable_fields = self.orderable_fields or ()
|
||||
ordering = dict()
|
||||
if self.is_csv_export:
|
||||
# Revert to CSV order_by submit_time ascending for backwards compatibility
|
||||
default_ordering = self.ordering_csv or ()
|
||||
else:
|
||||
default_ordering = self.ordering or ()
|
||||
if isinstance(default_ordering, str):
|
||||
default_ordering = (default_ordering,)
|
||||
ordering_strs = self.request.GET.getlist('order_by') or list(default_ordering)
|
||||
for order in ordering_strs:
|
||||
try:
|
||||
_, prefix, field_name = order.rpartition('-')
|
||||
if field_name in orderable_fields:
|
||||
ordering[field_name] = (
|
||||
prefix, 'descending' if prefix == '-' else 'ascending'
|
||||
)
|
||||
except (IndexError, ValueError):
|
||||
continue # invalid ordering specified, skip it
|
||||
return ordering
|
||||
|
||||
def get_ordering(self):
|
||||
""" Return the field or fields to use for ordering the queryset """
|
||||
ordering = self.get_validated_ordering()
|
||||
return [values[0] + name for name, values in ordering.items()]
|
||||
|
||||
def get_filtering(self):
|
||||
""" Return filering as a dict for submissions queryset """
|
||||
self.select_date_form = SelectDateForm(self.request.GET)
|
||||
result = dict()
|
||||
if self.select_date_form.is_valid():
|
||||
date_from = self.select_date_form.cleaned_data.get('date_from')
|
||||
date_to = self.select_date_form.cleaned_data.get('date_to')
|
||||
if date_to:
|
||||
# careful: date_to must be increased by 1 day
|
||||
# as submit_time is a time so will always be greater
|
||||
date_to += datetime.timedelta(days=1)
|
||||
if date_from:
|
||||
result['submit_time__range'] = [date_from, date_to]
|
||||
else:
|
||||
result['submit_time__lte'] = date_to
|
||||
elif date_from:
|
||||
result['submit_time__gte'] = date_from
|
||||
return result
|
||||
|
||||
def get_csv_filename(self):
|
||||
""" Returns the filename for the generated CSV file """
|
||||
return 'export-{}.csv'.format(
|
||||
datetime.datetime.today().strftime('%Y-%m-%d')
|
||||
)
|
||||
|
||||
def get_csv_response(self, context):
|
||||
""" Returns a CSV response """
|
||||
filename = self.get_csv_filename()
|
||||
response = HttpResponse(content_type='text/csv; charset=utf-8')
|
||||
response['Content-Disposition'] = 'attachment;filename=export.csv'
|
||||
|
||||
# Prevents UnicodeEncodeError for labels with non-ansi symbols
|
||||
data_headings = [smart_str(label) for label in data_headings]
|
||||
response['Content-Disposition'] = 'attachment;filename={}'.format(filename)
|
||||
|
||||
writer = csv.writer(response)
|
||||
writer.writerow(data_headings)
|
||||
for s in submissions:
|
||||
data_row = []
|
||||
form_data = s.get_data()
|
||||
for name, label in data_fields:
|
||||
val = form_data.get(name)
|
||||
if isinstance(val, list):
|
||||
val = ', '.join(val)
|
||||
data_row.append(smart_str(val))
|
||||
writer.writerow(context['data_headings'])
|
||||
for data_row in context['data_rows']:
|
||||
writer.writerow(data_row)
|
||||
return response
|
||||
|
||||
paginator, submissions = paginate(request, submissions)
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
if self.is_csv_export:
|
||||
return self.get_csv_response(context)
|
||||
return super().render_to_response(context, **response_kwargs)
|
||||
|
||||
data_rows = []
|
||||
for s in submissions:
|
||||
form_data = s.get_data()
|
||||
data_row = []
|
||||
for name, label in data_fields:
|
||||
val = form_data.get(name)
|
||||
if isinstance(val, list):
|
||||
val = ', '.join(val)
|
||||
data_row.append(val)
|
||||
data_rows.append({
|
||||
"model_id": s.id,
|
||||
"fields": data_row
|
||||
def get_context_data(self, **kwargs):
|
||||
""" Return context for view, handle CSV or normal output """
|
||||
context = super().get_context_data(**kwargs)
|
||||
submissions = context[self.context_object_name]
|
||||
data_fields = self.form_page.get_data_fields()
|
||||
data_rows = []
|
||||
|
||||
if self.is_csv_export:
|
||||
# Build data_rows as list of lists containing formatted data values
|
||||
# Using smart_str prevents UnicodeEncodeError for values with non-ansi symbols
|
||||
for submission in submissions:
|
||||
form_data = submission.get_data()
|
||||
data_row = []
|
||||
for name, label in data_fields:
|
||||
val = form_data.get(name)
|
||||
if isinstance(val, list):
|
||||
val = ', '.join(val)
|
||||
data_row.append(smart_str(val))
|
||||
data_rows.append(data_row)
|
||||
data_headings = [smart_str(label) for name, label in data_fields]
|
||||
else:
|
||||
# Build data_rows as list of dicts containing model_id and fields
|
||||
for submission in submissions:
|
||||
form_data = submission.get_data()
|
||||
data_row = []
|
||||
for name, label in data_fields:
|
||||
val = form_data.get(name)
|
||||
if isinstance(val, list):
|
||||
val = ', '.join(val)
|
||||
data_row.append(val)
|
||||
data_rows.append({
|
||||
'model_id': submission.id,
|
||||
'fields': data_row
|
||||
})
|
||||
# Build data_headings as list of dicts containing model_id and fields
|
||||
ordering_by_field = self.get_validated_ordering()
|
||||
orderable_fields = self.orderable_fields
|
||||
data_headings = []
|
||||
for name, label in data_fields:
|
||||
order_label = None
|
||||
if name in orderable_fields:
|
||||
order = ordering_by_field.get(name)
|
||||
if order:
|
||||
order_label = order[1] # 'ascending' or 'descending'
|
||||
else:
|
||||
order_label = 'orderable' # not ordered yet but can be
|
||||
data_headings.append({
|
||||
'name': name,
|
||||
'label': label,
|
||||
'order': order_label,
|
||||
})
|
||||
|
||||
context.update({
|
||||
'form_page': self.form_page,
|
||||
'select_date_form': self.select_date_form,
|
||||
'data_headings': data_headings,
|
||||
'data_rows': data_rows,
|
||||
'submissions': submissions,
|
||||
})
|
||||
|
||||
return render(request, 'wagtailforms/index_submissions.html', {
|
||||
'form_page': form_page,
|
||||
'select_date_form': select_date_form,
|
||||
'submissions': submissions,
|
||||
'data_fields_with_ordering': data_fields_with_ordering,
|
||||
'data_rows': data_rows
|
||||
})
|
||||
return context
|
||||
|
@ -3,9 +3,9 @@ from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.admin.menu import MenuItem
|
||||
from wagtail.core import hooks
|
||||
from wagtail.contrib.forms import urls
|
||||
from wagtail.contrib.forms.models import get_forms_for_user
|
||||
from wagtail.contrib.forms.utils import get_forms_for_user
|
||||
from wagtail.core import hooks
|
||||
|
||||
|
||||
@hooks.register('register_admin_urls')
|
||||
|
56
wagtail/tests/testapp/migrations/0025_auto_20171207_1657.py
Normal file
56
wagtail/tests/testapp/migrations/0025_auto_20171207_1657.py
Normal file
@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.6 on 2017-12-07 07:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import modelcluster.fields
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailcore', '0040_page_draft_title'),
|
||||
('tests', '0024_tableblockstreampage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FormFieldForCustomListViewPage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
|
||||
('label', models.CharField(help_text='The label of the form field', max_length=255, verbose_name='label')),
|
||||
('field_type', models.CharField(choices=[('singleline', 'Single line text'), ('multiline', 'Multi-line text'), ('email', 'Email'), ('number', 'Number'), ('url', 'URL'), ('checkbox', 'Checkbox'), ('checkboxes', 'Checkboxes'), ('dropdown', 'Drop down'), ('multiselect', 'Multiple select'), ('radio', 'Radio buttons'), ('date', 'Date'), ('datetime', 'Date/time'), ('hidden', 'Hidden field')], max_length=16, verbose_name='field type')),
|
||||
('required', models.BooleanField(default=True, verbose_name='required')),
|
||||
('choices', models.TextField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices')),
|
||||
('default_value', models.CharField(blank=True, help_text='Default value. Comma separated values supported for checkboxes.', max_length=255, verbose_name='default value')),
|
||||
('help_text', models.CharField(blank=True, max_length=255, verbose_name='help text')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'ordering': ['sort_order'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FormPageWithCustomSubmissionListView',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
|
||||
('to_address', models.CharField(blank=True, help_text='Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.', max_length=255, verbose_name='to address')),
|
||||
('from_address', models.CharField(blank=True, max_length=255, verbose_name='from address')),
|
||||
('subject', models.CharField(blank=True, max_length=255, verbose_name='subject')),
|
||||
('intro', wagtail.core.fields.RichTextField(blank=True)),
|
||||
('thank_you_text', wagtail.core.fields.RichTextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='formfieldforcustomlistviewpage',
|
||||
name='page',
|
||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_fields', to='tests.FormPageWithCustomSubmissionListView'),
|
||||
),
|
||||
]
|
@ -37,6 +37,8 @@ from wagtail.snippets.edit_handlers import SnippetChooserPanel
|
||||
from wagtail.snippets.models import register_snippet
|
||||
|
||||
from .forms import ValidatedPageForm
|
||||
from .views import CustomSubmissionsListView
|
||||
|
||||
|
||||
EVENT_AUDIENCE_CHOICES = (
|
||||
('public', "Public"),
|
||||
@ -566,6 +568,48 @@ class CustomFormPageSubmission(AbstractFormSubmission):
|
||||
return form_data
|
||||
|
||||
|
||||
# Custom form page with custom submission listing view and form submission
|
||||
|
||||
class FormFieldForCustomListViewPage(AbstractFormField):
|
||||
page = ParentalKey(
|
||||
'FormPageWithCustomSubmissionListView',
|
||||
related_name='form_fields',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
|
||||
class FormPageWithCustomSubmissionListView(AbstractEmailForm):
|
||||
"""Form Page with customised submissions listing view"""
|
||||
|
||||
intro = RichTextField(blank=True)
|
||||
thank_you_text = RichTextField(blank=True)
|
||||
|
||||
submissions_list_view_class = CustomSubmissionsListView
|
||||
|
||||
def get_submission_class(self):
|
||||
return CustomFormPageSubmission
|
||||
|
||||
def get_data_fields(self):
|
||||
data_fields = [
|
||||
('username', 'Username'),
|
||||
]
|
||||
data_fields += super().get_data_fields()
|
||||
|
||||
return data_fields
|
||||
|
||||
content_panels = [
|
||||
FieldPanel('title', classname="full title"),
|
||||
FieldPanel('intro', classname="full"),
|
||||
InlinePanel('form_fields', label="Form fields"),
|
||||
FieldPanel('thank_you_text', classname="full"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('to_address', classname="full"),
|
||||
FieldPanel('from_address', classname="full"),
|
||||
FieldPanel('subject', classname="full"),
|
||||
], "Email")
|
||||
]
|
||||
|
||||
|
||||
# Snippets
|
||||
class AdvertPlacement(models.Model):
|
||||
page = ParentalKey('wagtailcore.Page', related_name='advert_placements', on_delete=models.CASCADE)
|
||||
|
@ -4,6 +4,7 @@ from django.template.response import TemplateResponse
|
||||
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.utils import user_passes_test
|
||||
from wagtail.contrib.forms.views import SubmissionsListView
|
||||
|
||||
|
||||
def user_is_called_bob(user):
|
||||
@ -22,3 +23,14 @@ def message_test(request):
|
||||
return redirect('testapp_message_test')
|
||||
else:
|
||||
return TemplateResponse(request, 'wagtailadmin/base.html')
|
||||
|
||||
|
||||
class CustomSubmissionsListView(SubmissionsListView):
|
||||
paginate_by = 50
|
||||
ordering = ('submit_time',)
|
||||
ordering_csv = ('-submit_time',)
|
||||
|
||||
def get_csv_filename(self):
|
||||
""" Returns the filename for CSV file with page title at start"""
|
||||
filename = super().get_csv_filename()
|
||||
return self.form_page.slug + '-' + filename
|
||||
|
Loading…
Reference in New Issue
Block a user