323 lines
12 KiB
Python
323 lines
12 KiB
Python
import datetime
|
|
from collections import OrderedDict
|
|
|
|
from django.contrib.admin.utils import quote
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext, gettext_lazy, ngettext
|
|
from django.views.generic import TemplateView
|
|
from django_filters import DateFromToRangeFilter
|
|
|
|
from wagtail.admin import messages
|
|
from wagtail.admin.filters import DateRangePickerWidget, WagtailFilterSet
|
|
from wagtail.admin.ui.tables import Column, TitleColumn
|
|
from wagtail.admin.views import generic
|
|
from wagtail.admin.views.generic.base import BaseListingView
|
|
from wagtail.admin.views.mixins import SpreadsheetExportMixin
|
|
from wagtail.contrib.forms.utils import get_forms_for_user
|
|
from wagtail.models import Page
|
|
|
|
|
|
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)
|
|
|
|
|
|
class ContentTypeColumn(Column):
|
|
edit_url_name = "wagtailadmin_pages:edit"
|
|
cell_template_name = "wagtailforms/content_type_column.html"
|
|
|
|
def get_url(self, instance):
|
|
return reverse(self.edit_url_name, args=(quote(instance.pk),))
|
|
|
|
def get_cell_context_data(self, instance, parent_context):
|
|
context = super().get_cell_context_data(instance, parent_context)
|
|
context["url"] = self.get_url(instance)
|
|
return context
|
|
|
|
|
|
class FormPagesListView(generic.IndexView):
|
|
"""Lists the available form pages for the current user"""
|
|
|
|
template_name = "wagtailforms/index.html"
|
|
results_template_name = "wagtailforms/index_results.html"
|
|
context_object_name = "form_pages"
|
|
paginate_by = 20
|
|
page_kwarg = "p"
|
|
index_url_name = "wagtailforms:index"
|
|
index_results_url_name = "wagtailforms:index_results"
|
|
page_title = gettext_lazy("Forms")
|
|
header_icon = "form"
|
|
_show_breadcrumbs = True
|
|
columns = [
|
|
TitleColumn(
|
|
"title",
|
|
classname="title",
|
|
label=gettext_lazy("Title"),
|
|
width="50%",
|
|
url_name="wagtailforms:list_submissions",
|
|
sort_key="title",
|
|
),
|
|
ContentTypeColumn(
|
|
"content_type",
|
|
label=gettext_lazy("Origin"),
|
|
width="50%",
|
|
sort_key="content_type",
|
|
),
|
|
]
|
|
model = Page
|
|
is_searchable = False
|
|
|
|
def get_breadcrumbs_items(self):
|
|
return self.breadcrumbs_items + [
|
|
{"url": "", "label": self.page_title, "sublabel": gettext("Pages")},
|
|
]
|
|
|
|
def get_base_queryset(self):
|
|
"""Return the queryset of form pages for this view"""
|
|
return get_forms_for_user(self.request.user).select_related("content_type")
|
|
|
|
|
|
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(
|
|
self.request,
|
|
ngettext(
|
|
"One submission has been deleted.",
|
|
"%(count)d submissions have been deleted.",
|
|
count,
|
|
)
|
|
% {"count": count},
|
|
)
|
|
|
|
def get_success_url(self):
|
|
"""Returns the success URL to redirect to after a successful deletion"""
|
|
return self.success_url
|
|
|
|
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
|
|
|
|
self.page = get_object_or_404(Page, id=page_id).specific
|
|
|
|
self.submissions = self.get_queryset()
|
|
|
|
if self.request.method == "POST":
|
|
self.handle_delete(self.submissions)
|
|
return redirect(self.get_success_url(), page_id)
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Get the context for this view"""
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context.update(
|
|
{
|
|
"page": self.page,
|
|
"submissions": self.submissions,
|
|
}
|
|
)
|
|
|
|
return context
|
|
|
|
|
|
class SubmissionsListFilterSet(WagtailFilterSet):
|
|
date = DateFromToRangeFilter(
|
|
label=gettext_lazy("Submission date"),
|
|
field_name="submit_time",
|
|
widget=DateRangePickerWidget,
|
|
)
|
|
|
|
|
|
class SubmissionsListView(SpreadsheetExportMixin, BaseListingView):
|
|
"""Lists submissions for the provided form page"""
|
|
|
|
template_name = "wagtailforms/submissions_index.html"
|
|
results_template_name = "wagtailforms/list_submissions.html"
|
|
context_object_name = "submissions"
|
|
form_page = None
|
|
default_ordering = ("-submit_time",)
|
|
ordering_csv = ("submit_time",) # keep legacy CSV ordering
|
|
orderable_fields = (
|
|
"id",
|
|
"submit_time",
|
|
) # used to validate ordering in URL
|
|
page_title = gettext_lazy("Form data")
|
|
header_icon = "form"
|
|
paginate_by = 20
|
|
filterset_class = SubmissionsListFilterSet
|
|
forms_index_url_name = "wagtailforms:index"
|
|
index_url_name = "wagtailforms:list_submissions"
|
|
index_results_url_name = "wagtailforms:list_submissions_results"
|
|
_show_breadcrumbs = True
|
|
show_export_buttons = True
|
|
|
|
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
|
|
|
|
if self.is_export:
|
|
data_fields = self.form_page.get_data_fields()
|
|
# Set the export fields and the headings for spreadsheet export
|
|
self.list_export = [field for field, label in data_fields]
|
|
self.export_headings = dict(data_fields)
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_filterset_kwargs(self):
|
|
kwargs = super().get_filterset_kwargs()
|
|
kwargs["queryset"] = self.get_base_queryset()
|
|
return kwargs
|
|
|
|
def get_base_queryset(self):
|
|
"""Return queryset of form submissions"""
|
|
submission_class = self.form_page.get_submission_class()
|
|
queryset = submission_class._default_manager.filter(page=self.form_page)
|
|
return queryset
|
|
|
|
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 = {}
|
|
if self.is_export:
|
|
# Revert to CSV order_by submit_time ascending for backwards compatibility
|
|
default_ordering = self.ordering_csv or ()
|
|
else:
|
|
default_ordering = self.default_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_filename(self):
|
|
"""Returns the base filename for the generated spreadsheet data file"""
|
|
return "{}-export-{}".format(
|
|
self.form_page.slug, datetime.datetime.today().strftime("%Y-%m-%d")
|
|
)
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
if self.is_export:
|
|
return self.as_spreadsheet(
|
|
context["submissions"], self.request.GET.get("export")
|
|
)
|
|
return super().render_to_response(context, **response_kwargs)
|
|
|
|
def to_row_dict(self, item):
|
|
"""Orders the submission dictionary for spreadsheet writing"""
|
|
row_dict = OrderedDict(
|
|
(field, item.get_data().get(field)) for field in self.list_export
|
|
)
|
|
return row_dict
|
|
|
|
def get_index_url(self):
|
|
return reverse(self.index_url_name, args=(self.form_page.id,))
|
|
|
|
def get_index_results_url(self):
|
|
return reverse(self.index_results_url_name, args=(self.form_page.id,))
|
|
|
|
def get_page_subtitle(self):
|
|
return self.form_page.get_admin_display_title()
|
|
|
|
def get_breadcrumbs_items(self):
|
|
return self.breadcrumbs_items + [
|
|
{
|
|
"url": reverse(self.forms_index_url_name),
|
|
"label": gettext("Forms"),
|
|
},
|
|
{
|
|
"url": "",
|
|
"label": self.get_page_title(),
|
|
"sublabel": self.get_page_subtitle(),
|
|
},
|
|
]
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Return context for view"""
|
|
context = super().get_context_data(**kwargs)
|
|
submissions = context[self.context_object_name]
|
|
data_fields = self.form_page.get_data_fields()
|
|
data_rows = []
|
|
context["submissions"] = submissions
|
|
if not self.is_export:
|
|
# 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,
|
|
"data_headings": data_headings,
|
|
"data_rows": data_rows,
|
|
}
|
|
)
|
|
|
|
return context
|