django-fobi/src/fobi/views/class_based.py
2022-11-29 01:21:53 +01:00

1856 lines
61 KiB
Python

import datetime
import json
import logging
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import IntegrityError, models
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.utils.datastructures import MultiValueDictKeyError
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import CreateView, DeleteView, DetailView, UpdateView
from django.views.generic.edit import DeletionMixin
from django.views.generic.list import ListView
from ..base import ( # get_registered_form_handler_plugins
fire_form_callbacks,
form_element_plugin_registry,
form_handler_plugin_registry,
form_wizard_handler_plugin_registry,
get_theme,
run_form_handlers,
run_form_wizard_handlers,
submit_plugin_form_data,
)
from ..constants import (
CALLBACK_BEFORE_FORM_VALIDATION,
CALLBACK_FORM_INVALID,
CALLBACK_FORM_VALID,
CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS,
CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
)
from ..dynamic import assemble_form_class
from ..form_importers import (
ensure_autodiscover as ensure_importers_autodiscover,
form_importer_plugin_registry,
get_form_importer_plugin_urls,
)
from ..forms import (
FormElementEntryFormSet,
FormEntryForm,
FormWizardEntryForm,
FormWizardFormEntryFormSet,
ImportFormEntryForm,
ImportFormWizardEntryForm,
)
from ..models import (
FormElementEntry,
FormEntry,
FormHandlerEntry,
FormWizardEntry,
FormWizardFormEntry,
FormWizardHandlerEntry,
)
from ..permissions.default import (
AddFormElementEntryPermission,
AddFormHandlerEntryPermission,
CreateFormEntryPermission,
DeleteFormElementEntryPermission,
DeleteFormEntryPermission,
DeleteFormHandlerEntryPermission,
EditFormElementEntryPermission,
EditFormEntryPermission,
EditFormHandlerEntryPermission,
ViewDashboardPermission,
ViewFormEntryPermission,
)
from ..settings import DEBUG, GET_PARAM_INITIAL_DATA, SORT_PLUGINS_BY_VALUE
from ..utils import (
append_edit_and_delete_links_to_field,
get_user_form_element_plugins_grouped,
get_user_form_field_plugin_uids,
get_user_form_handler_plugin_uids,
get_user_form_handler_plugins,
get_user_form_wizard_handler_plugin_uids,
get_user_form_wizard_handler_plugins,
get_wizard_files_upload_dir,
perform_form_entry_import,
prepare_form_entry_export_data,
)
__all__ = (
"PermissionMixin",
"AbstractDeletePluginEntryView",
"CreateFormEntryView",
"EditFormEntryView",
"DeleteFormEntryView",
"AddFormElementEntryView",
"EditFormElementEntryView",
"DeleteFormElementEntryView",
"AddFormHandlerEntryView",
"EditFormHandlerEntryView",
"DeleteFormHandlerEntryView",
"ViewFormEntryView",
"ViewFormEntrySubmittedView",
"DashboardView",
)
logger = logging.getLogger(__name__)
# *****************************************************************************
# *****************************************************************************
# *********************************** Generic *********************************
# *****************************************************************************
# *****************************************************************************
class PermissionMixin(View):
"""Mixin for permission-based views."""
permission_classes: tuple = ()
def permission_denied(self, request, message=None, code=None):
"""If request is not permitted, raise."""
raise PermissionDenied()
def get_permissions(self):
"""Return initialized list of permissions required by this view."""
return [permission() for permission in self.permission_classes]
def dispatch(self, request, *args, **kwargs):
"""Dispatch the request."""
self.check_permissions(request)
return super(PermissionMixin, self).dispatch(request, *args, **kwargs)
def check_permissions(self, request):
"""Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, "message", None),
code=getattr(permission, "code", None),
)
def check_object_permissions(self, request, obj):
"""Check if the request should be permitted for a given object.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_object_permission(request, self, obj):
self.permission_denied(
request,
message=getattr(permission, "message", None),
code=getattr(permission, "code", None),
)
class AbstractDeletePluginEntryView(PermissionMixin, DeleteView):
"""Abstract delete view for plugin entries."""
pk_url_kwarg: str
get_user_plugin_uids_func: callable
message: str
html_anchor: str
def _get_queryset(self, request):
"""Get queryset."""
return self.model._default_manager.select_related("form_entry").filter(
form_entry__user__pk=request.user.pk
)
def get_object(self, queryset=None):
"""Get object."""
# TODO: There's a tiny deviation from `_delete_plugin_entry`
# implementation. The message in the latter is fully custom, while
# in this case we're stuck to Django's own implementation.
# Comment added on 2022-07-10.
obj = get_object_or_404(
self._get_queryset(self.request),
pk=self.kwargs.get(self.pk_url_kwarg),
)
self.check_object_permissions(self.request, obj)
return obj
# Add support for browsers which only accept GET and POST for now.
def get(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete."""
self.object = self.get_object()
self._run_before_plugin_entry_delete(request, self.object)
form_entry = self.object.form_entry
plugin = self.object.get_plugin(request=request)
plugin.request = request
plugin._delete_plugin_data()
self.object.delete()
self._run_after_plugin_entry_delete(
request, self.kwargs.get("form_entry_id")
)
messages.info(request, self.message.format(plugin.name))
redirect_url = reverse_lazy(
"fobi.edit_form_entry", kwargs={"form_entry_id": form_entry.pk}
)
return redirect("{0}{1}".format(redirect_url, self.html_anchor))
def _run_before_plugin_entry_delete(self, request, form_entry):
"""Run just before plugin entry has been deleted."""
try:
self.run_before_plugin_entry_delete(request, form_entry)
return True
except:
return False
def run_before_plugin_entry_delete(self, request, form_entry):
"""Run just before plugin entry has been deleted."""
def _run_after_plugin_entry_delete(self, request, form_entry_id):
"""Run after plugin entry has been deleted."""
try:
self.run_after_plugin_entry_delete(request, form_entry_id)
return True
except:
return False
def run_after_plugin_entry_delete(self, request, form_entry_id):
"""Run after plugin entry has been deleted."""
# *****************************************************************************
# *****************************************************************************
# ******************************** Dashboards *********************************
# *****************************************************************************
# *****************************************************************************
# *****************************************************************************
# ********************************** Forms ************************************
# *****************************************************************************
class DashboardView(PermissionMixin, ListView):
"""Dashboard view."""
template_name = None
theme = None
permission_classes = (ViewDashboardPermission,)
def get_queryset(self):
"""
Return the list of items for this view.
The return value must be an iterable and may be an instance of
`QuerySet` in which case `QuerySet` specific behavior will be enabled.
"""
queryset = FormEntry._default_manager.filter(
user__pk=self.request.user.pk
).select_related('user')
ordering = self.get_ordering()
if ordering:
if isinstance(ordering, str):
ordering = (ordering,)
queryset = queryset.order_by(*ordering)
return queryset
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(DashboardView, self).get_context_data(**kwargs)
context.update(
{
"form_entries": self.get_queryset(),
"form_importers": get_form_importer_plugin_urls(),
}
)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.dashboard_template
return [template_name]
# *****************************************************************************
# *****************************************************************************
# ********************************** Builder **********************************
# *****************************************************************************
# *****************************************************************************
# *****************************************************************************
# **************************** Create form entry ******************************
# *****************************************************************************
class CreateFormEntryView(PermissionMixin, CreateView):
"""Create form entry."""
template_name = None
form_class = FormEntryForm
theme = None
permission_classes = (CreateFormEntryPermission,)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(CreateFormEntryView, self).get_context_data(**kwargs)
context["form"] = self.get_form()
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.create_form_entry_template
return [template_name]
def get_form_kwargs(self):
kwargs = super(CreateFormEntryView, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
self.object = None
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
self.object = None
form = self.get_form()
if form.is_valid():
form_entry = form.save(commit=False)
form_entry.user = request.user
self._run_before_form_create(request, form_entry)
try:
form_entry.save()
self._run_after_form_create(request, form_entry)
messages.info(
request,
_("Form {0} was created successfully.").format(
form_entry.name
),
)
return redirect(
"fobi.edit_form_entry", form_entry_id=form_entry.pk
)
except IntegrityError as err:
messages.info(
request,
_("Errors occurred while saving the form: {0}.").format(
str(err)
),
)
return self.render_to_response(self.get_context_data())
def _run_before_form_create(self, request, form_entry):
"""Run just before form_entry has been created/saved."""
try:
self.run_before_form_create(request, form_entry)
return True
except:
return False
def run_before_form_create(self, request, form_entry):
"""Run just before form_entry has been created/saved."""
def _run_after_form_create(self, request, form_entry):
"""Run after form_entry has been created/saved."""
try:
self.run_after_form_create(request, form_entry)
return True
except:
return False
def run_after_form_create(self, request, form_entry):
"""Run after the form_entry has been created/saved."""
# **************************************************************************
# ******************************* Edit form entry **************************
# **************************************************************************
class EditFormEntryView(PermissionMixin, UpdateView):
"""Edit form entry."""
template_name = None
form_class = FormEntryForm
theme = None
pk_url_kwarg = "form_entry_id"
permission_classes = (EditFormEntryPermission,)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(EditFormEntryView, self).get_context_data(**kwargs)
# In case of success, we don't need this (since redirect would happen).
# Thus, fetch only if needed.
form_elements = self.object.formelemententry_set.all()
form_handlers = self.object.formhandlerentry_set.all()[:]
used_form_handler_uids = [
form_handler.plugin_uid for form_handler in form_handlers
]
# The code below (two lines below) is not really used at the moment,
# thus - comment out, but do not remove, as we might need it later on.
# all_form_entries = FormEntry._default_manager \
# .only('id', 'name', 'slug') \
# .filter(user__pk=request.user.pk)
# List of form element plugins allowed to user
user_form_element_plugins = get_user_form_element_plugins_grouped(
self.request.user, sort_by_value=SORT_PLUGINS_BY_VALUE
)
# List of form handler plugins allowed to user
user_form_handler_plugins = get_user_form_handler_plugins(
self.request.user,
exclude_used_singles=True,
used_form_handler_plugin_uids=used_form_handler_uids,
)
# Assembling the form for preview
form_cls = assemble_form_class(
self.object,
origin="edit_form_entry",
origin_kwargs_update_func=append_edit_and_delete_links_to_field,
request=self.request,
)
assembled_form = form_cls()
# In debug mode, try to identify possible problems.
if DEBUG:
assembled_form.as_p()
else:
try:
assembled_form.as_p()
except Exception as err:
logger.error(err)
# If no theme provided, pick a default one.
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
theme.collect_plugin_media(form_elements)
context.update(
{
"form": self.get_form(),
"form_entry": self.object,
"form_elements": form_elements,
"form_handlers": form_handlers,
"user_form_element_plugins": user_form_element_plugins,
"user_form_handler_plugins": user_form_handler_plugins,
"assembled_form": assembled_form,
"fobi_theme": theme,
}
)
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.edit_form_entry_template
return [template_name]
def get_form_kwargs(self):
kwargs = super(EditFormEntryView, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def _get_queryset(self, request):
"""Get queryset."""
return (
FormEntry._default_manager.select_related("user")
.prefetch_related("formelemententry_set")
.filter(user__pk=request.user.pk)
)
def get_object(self, queryset=None):
"""Get object."""
obj = super(EditFormEntryView, self).get_object(queryset)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=self._get_queryset(request))
"""Handle GET requests: instantiate a blank version of the form."""
form_element_entry_formset = FormElementEntryFormSet(
queryset=self.object.formelemententry_set.all(),
# prefix='form_element'
)
return self.render_to_response(
self.get_context_data(
form_element_entry_formset=form_element_entry_formset,
)
)
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
self.object = self.get_object(queryset=self._get_queryset(request))
form = self.get_form()
# This is where we save ordering if it has been changed.
# The `FormElementEntryFormSet` contain ids and positions only.
if "ordering" in request.POST:
form_element_entry_formset = FormElementEntryFormSet(
request.POST,
request.FILES,
queryset=self.object.formelemententry_set.all(),
# prefix = 'form_element'
)
# If form elements aren't properly made (developers's fault)
# there might be problems with saving the ordering - likely
# in case of hidden elements only. Thus, we want to avoid
# errors here.
try:
if form_element_entry_formset.is_valid():
form_element_entry_formset.save()
messages.info(
request, _("Elements ordering edited successfully.")
)
return redirect(
reverse_lazy(
"fobi.edit_form_entry",
kwargs={"form_entry_id": self.object.pk},
)
)
except MultiValueDictKeyError as err:
messages.error(
request,
_(
"Errors occurred while trying to change the "
"elements ordering!"
),
)
return redirect(
reverse_lazy(
"fobi.edit_form_entry",
kwargs={"form_entry_id": self.object.pk},
)
)
else:
form_element_entry_formset = FormElementEntryFormSet(
queryset=self.object.formelemententry_set.all(),
# prefix='form_element'
)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
try:
obj.save()
messages.info(
request,
_("Form {0} was edited successfully.").format(obj.name),
)
return redirect(
reverse_lazy(
"fobi.edit_form_entry", kwargs={"form_entry_id": obj.pk}
)
)
except IntegrityError as err:
messages.info(
request,
_("Errors occurred while saving the form: {0}.").format(
str(err)
),
)
return self.render_to_response(
self.get_context_data(
form_element_entry_formset=form_element_entry_formset,
)
)
# *****************************************************************************
# ********************************* Delete form entry *************************
# *****************************************************************************
class DeleteFormEntryView(PermissionMixin, DeletionMixin):
"""Delete form entry."""
model = FormEntry
success_url = reverse_lazy("fobi.dashboard")
permission_classes = (DeleteFormEntryPermission,)
def get_object(self, queryset=None):
"""Get object."""
obj = get_object_or_404(
FormEntry._default_manager.all(),
pk=self.kwargs.get("form_entry_id"),
user__pk=self.request.user.pk,
)
self.check_object_permissions(self.request, obj)
return obj
# Add support for browsers which only accept GET and POST for now.
def get(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""Delete."""
self.object = self.get_object()
success_url = self.get_success_url()
self._run_before_form_delete(request, self.object)
self.object.delete()
self._run_after_form_delete(request, self.kwargs.get("form_entry_id"))
messages.info(
request,
_("Form {0} was deleted successfully.").format(self.object.name),
)
return redirect(success_url)
def _run_before_form_delete(self, request, form_entry):
"""Run just before form_entry has been deleted."""
try:
self.run_before_form_delete(request, form_entry)
return True
except:
return False
def run_before_form_delete(self, request, form_entry):
"""Run just before form_entry has been deleted."""
def _run_after_form_delete(self, request, form_entry_id):
"""Run after form_entry has been deleted."""
try:
self.run_after_form_delete(request, form_entry_id)
return True
except:
return False
def run_after_form_delete(self, request, form_entry_id):
"""Run after form_entry has been deleted."""
# *****************************************************************************
# **************************** Add form element entry *************************
# *****************************************************************************
class AddFormElementEntryView(PermissionMixin, CreateView):
"""Add form element entry."""
template_name = None
form_class = None
theme = None
permission_classes = (AddFormElementEntryPermission,)
def get_essential_objects(
self,
form_entry_id,
form_element_plugin_uid,
request,
):
"""Get essential objects."""
try:
form_entry = FormEntry._default_manager.prefetch_related(
"formelemententry_set"
).get(pk=form_entry_id)
except ObjectDoesNotExist as err:
raise Http404(_("Form entry not found."))
form_elements = form_entry.formelemententry_set.all()
user_form_element_plugin_uids = get_user_form_field_plugin_uids(
request.user
)
if form_element_plugin_uid not in user_form_element_plugin_uids:
raise Http404(
_(
"Plugin does not exist or you are not allowed "
"to use this plugin!"
)
)
form_element_plugin_cls = form_element_plugin_registry.get(
form_element_plugin_uid
)
form_element_plugin = form_element_plugin_cls(user=request.user)
form_element_plugin.request = request
form_element_plugin_form_cls = form_element_plugin.get_form()
# form = None
obj = FormElementEntry()
obj.form_entry = form_entry
obj.plugin_uid = form_element_plugin_uid
obj.user = request.user
return (
form_entry,
form_elements,
form_element_plugin_cls,
form_element_plugin,
form_element_plugin_form_cls,
user_form_element_plugin_uids,
obj,
)
def do_save_object(
self, form_entry_id, form_entry, obj, form_element_plugin, request
):
"""Do save object."""
# Handling the position
position = 1
records = FormElementEntry.objects.filter(
form_entry=form_entry
).aggregate(models.Max("position"))
if records:
try:
position = records["{0}__max".format("position")] + 1
except TypeError as err:
pass
obj.position = position
# Save the object.
obj.save()
messages.info(
request,
_(
'The form element plugin "{0}" was added ' "successfully."
).format(form_element_plugin.name),
)
return redirect(
"{0}?active_tab=tab-form-elements".format(
reverse_lazy(
"fobi.edit_form_entry",
kwargs={"form_entry_id": form_entry_id},
)
)
)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(AddFormElementEntryView, self).get_context_data(
**kwargs
)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.add_form_element_entry_template
return [template_name]
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
self.object = None
(
form_entry,
form_elements,
form_element_plugin_cls,
form_element_plugin,
form_element_plugin_form_cls,
user_form_element_plugin_uids,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_entry_id"),
self.kwargs.get("form_element_plugin_uid"),
self.request,
)
save_object = False
if not form_element_plugin_form_cls:
save_object = True
if not save_object:
form = form_element_plugin.get_initialised_create_form_or_404()
if save_object:
return self.do_save_object(
self.kwargs.get("form_entry_id"),
form_entry,
obj,
form_element_plugin,
request,
)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_element_plugin=form_element_plugin,
)
)
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
self.object = None
(
form_entry,
form_elements,
form_element_plugin_cls,
form_element_plugin,
form_element_plugin_form_cls,
user_form_element_plugin_uids,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_entry_id"),
self.kwargs.get("form_element_plugin_uid"),
self.request,
)
save_object = False
if not form_element_plugin_form_cls:
save_object = True
if not save_object:
form = form_element_plugin.get_initialised_create_form_or_404(
data=request.POST, files=request.FILES
)
form.validate_plugin_data(form_elements, request=request)
if form.is_valid():
# Saving the plugin form data.
form.save_plugin_data(request=request)
# Getting the plugin data.
obj.plugin_data = form.get_plugin_data(request=request)
save_object = True
if save_object:
return self.do_save_object(
self.kwargs.get("form_entry_id"),
form_entry,
obj,
form_element_plugin,
request,
)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_element_plugin=form_element_plugin,
)
)
# *****************************************************************************
# **************************** Edit form element entry ************************
# *****************************************************************************
class EditFormElementEntryView(PermissionMixin, UpdateView):
"""Edit form element entry view."""
template_name = None
form_class = None
theme = None
pk_url_kwarg = "form_element_entry_id"
permission_classes = (EditFormElementEntryPermission,)
def _get_queryset(self, request):
"""Get queryset."""
return FormElementEntry._default_manager.select_related(
"form_entry", "form_entry__user"
).filter(form_entry__user__pk=request.user.pk)
def get_object(self, queryset=None):
"""Get object."""
obj = super(EditFormElementEntryView, self).get_object(queryset)
self.check_object_permissions(self.request, obj)
return obj
def get_essential_objects(
self,
form_element_entry_id,
request,
):
"""Get essential objects."""
try:
obj = self.get_object(queryset=self._get_queryset(request))
except ObjectDoesNotExist as err:
raise Http404(_("Form element entry not found."))
form_entry = obj.form_entry
form_element_plugin = obj.get_plugin(request=request)
form_element_plugin.request = request
form_element_plugin_form_cls = form_element_plugin.get_form()
return (
form_entry,
form_element_entry_id,
form_element_plugin,
form_element_plugin_form_cls,
obj,
)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(EditFormElementEntryView, self).get_context_data(
**kwargs
)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.edit_form_element_entry_template
return [template_name]
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
(
form_entry,
form_element_entry_id,
form_element_plugin,
form_element_plugin_form_cls,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_element_entry_id"),
self.request,
)
self.object = obj
form = None
if not form_element_plugin_form_cls:
messages.info(
request,
_(
'The form element plugin "{0}" ' "is not configurable!"
).format(form_element_plugin.name),
)
return redirect("fobi.edit_form_entry", form_entry_id=form_entry.pk)
else:
form = form_element_plugin.get_initialised_edit_form_or_404()
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_element_plugin=form_element_plugin,
)
)
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
self.object = None
(
form_entry,
form_element_entry_id,
form_element_plugin,
form_element_plugin_form_cls,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_element_entry_id"),
self.request,
)
self.object = obj
if not form_element_plugin_form_cls:
messages.info(
request,
_(
'The form element plugin "{0}" ' "is not configurable!"
).format(form_element_plugin.name),
)
return redirect("fobi.edit_form_entry", form_entry_id=form_entry.pk)
form = form_element_plugin.get_initialised_edit_form_or_404(
data=request.POST, files=request.FILES
)
form_elements = (
FormElementEntry._default_manager.select_related(
"form_entry", "form_entry__user"
)
.exclude(pk=form_element_entry_id)
.filter(form_entry=form_entry)
)
form.validate_plugin_data(form_elements, request=request)
if form.is_valid():
# Saving the plugin form data.
form.save_plugin_data(request=request)
# Getting the plugin data.
obj.plugin_data = form.get_plugin_data(request=request)
# Save the object.
obj.save()
messages.info(
request,
_(
'The form element plugin "{0}" was edited ' "successfully."
).format(form_element_plugin.name),
)
return redirect("fobi.edit_form_entry", form_entry_id=form_entry.pk)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_element_plugin=form_element_plugin,
)
)
# *****************************************************************************
# **************************** Delete form element entry **********************
# *****************************************************************************
class DeleteFormElementEntryView(AbstractDeletePluginEntryView):
"""Delete form element entry."""
model = FormElementEntry
permission_classes = (DeleteFormElementEntryPermission,)
pk_url_kwarg = "form_element_entry_id"
get_user_plugin_uids_func = get_user_form_field_plugin_uids
message = _('The form element plugin "{0}" was deleted successfully.')
html_anchor = "?active_tab=tab-form-elements"
# *****************************************************************************
# **************************** Add form handler entry *************************
# *****************************************************************************
class AddFormHandlerEntryView(PermissionMixin, CreateView):
"""Add form handler entry."""
template_name = None
form_class = None
theme = None
permission_classes = (AddFormHandlerEntryPermission,)
def get_essential_objects(
self,
form_entry_id,
form_handler_plugin_uid,
request,
):
"""Get essential objects."""
try:
form_entry = FormEntry._default_manager.get(pk=form_entry_id)
except ObjectDoesNotExist as err:
raise Http404(_("Form entry not found."))
# TODO: Form handlers don't have this, while form elements do.
# Find out whether including this improves performance.
# Comment added on 2022-07-10.
# form_elements = form_entry.formelemententry_set.all()
user_form_handler_plugin_uids = get_user_form_handler_plugin_uids(
request.user
)
if form_handler_plugin_uid not in user_form_handler_plugin_uids:
raise Http404(
_(
"Plugin does not exist or you are not allowed "
"to use this plugin!"
)
)
form_handler_plugin_cls = form_handler_plugin_registry.get(
form_handler_plugin_uid
)
# Check if we deal with form handler plugin that is only allowed to be
# used once. In that case, check if it has been used already in the
# current form entry.
if not form_handler_plugin_cls.allow_multiple:
times_used = FormHandlerEntry._default_manager.filter(
form_entry__id=form_entry_id,
plugin_uid=form_handler_plugin_cls.uid,
).count()
if times_used > 0:
raise Http404(
_(
"The {0} plugin can be used only once in a " "form."
).format(form_handler_plugin_cls.name)
)
form_handler_plugin = form_handler_plugin_cls(user=request.user)
form_handler_plugin.request = request
form_handler_plugin_form_cls = form_handler_plugin.get_form()
obj = FormHandlerEntry()
obj.form_entry = form_entry
obj.plugin_uid = form_handler_plugin_uid
obj.user = request.user
return (
form_entry,
# form_elements,
form_handler_plugin_cls,
form_handler_plugin,
form_handler_plugin_form_cls,
user_form_handler_plugin_uids,
obj,
)
def do_save_object(
self, form_entry_id, form_entry, obj, form_handler_plugin, request
):
"""Do save object."""
# Save the object.
obj.save()
messages.info(
request,
_(
'The form handler plugin "{0}" was added ' "successfully."
).format(form_handler_plugin.name),
)
return redirect(
"{0}?active_tab=tab-form-handlers".format(
reverse_lazy(
"fobi.edit_form_entry",
kwargs={"form_entry_id": form_entry_id},
)
)
)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(AddFormHandlerEntryView, self).get_context_data(
**kwargs
)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.add_form_handler_entry_template
return [template_name]
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
self.object = None
(
form_entry,
# form_elements,
form_handler_plugin_cls,
form_handler_plugin,
form_handler_plugin_form_cls,
user_form_handler_plugin_uids,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_entry_id"),
self.kwargs.get("form_handler_plugin_uid"),
self.request,
)
save_object = False
if not form_handler_plugin_form_cls:
save_object = True
if not save_object:
form = form_handler_plugin.get_initialised_create_form_or_404()
if save_object:
return self.do_save_object(
self.kwargs.get("form_entry_id"),
form_entry,
obj,
form_handler_plugin,
request,
)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_handler_plugin=form_handler_plugin,
)
)
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
self.object = None
(
form_entry,
# form_elements,
form_handler_plugin_cls,
form_handler_plugin,
form_handler_plugin_form_cls,
user_form_handler_plugin_uids,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_entry_id"),
self.kwargs.get("form_handler_plugin_uid"),
self.request,
)
save_object = False
if not form_handler_plugin_form_cls:
save_object = True
if not save_object:
form = form_handler_plugin.get_initialised_create_form_or_404(
data=request.POST, files=request.FILES
)
# TODO: Form handlers don't have this, while form elements do.
# Find out whether this is something that could be correct
# for form handlers.
# form.validate_plugin_data(form_elements, request=request)
if form.is_valid():
# Saving the plugin form data.
form.save_plugin_data(request=request)
# Getting the plugin data.
obj.plugin_data = form.get_plugin_data(request=request)
save_object = True
if save_object:
return self.do_save_object(
self.kwargs.get("form_entry_id"),
form_entry,
obj,
form_handler_plugin,
request,
)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_handler_plugin=form_handler_plugin,
)
)
# *****************************************************************************
# **************************** Edit form handler entry ************************
# *****************************************************************************
class EditFormHandlerEntryView(PermissionMixin, UpdateView):
"""Edit form handler entry view."""
template_name = None
form_class = None
theme = None
pk_url_kwarg = "form_handler_entry_id"
permission_classes = (EditFormHandlerEntryPermission,)
def _get_queryset(self, request):
"""Get queryset."""
# TODO: The form element entry has also `form_entry__user` in
# `seleect_related`. Find out if that something that could
# be also be applied here to improve the performance.
return FormHandlerEntry._default_manager.select_related(
"form_entry"
).filter(form_entry__user__pk=request.user.pk)
def get_object(self, queryset=None):
"""Get object."""
obj = super(EditFormHandlerEntryView, self).get_object(queryset)
self.check_object_permissions(self.request, obj)
return obj
def get_essential_objects(
self,
form_handler_entry_id,
request,
):
"""Get essential objects."""
try:
obj = self.get_object(queryset=self._get_queryset(request))
except ObjectDoesNotExist as err:
raise Http404(_("Form element entry not found."))
form_entry = obj.form_entry
form_handler_plugin = obj.get_plugin(request=request)
form_handler_plugin.request = request
form_handler_plugin_form_cls = form_handler_plugin.get_form()
return (
form_entry,
form_handler_entry_id,
form_handler_plugin,
form_handler_plugin_form_cls,
obj,
)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(EditFormHandlerEntryView, self).get_context_data(
**kwargs
)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.edit_form_handler_entry_template
return [template_name]
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
(
form_entry,
form_handler_entry_id,
form_handler_plugin,
form_handler_plugin_form_cls,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_handler_entry_id"),
self.request,
)
self.object = obj
form = None
if not form_handler_plugin_form_cls:
messages.info(
request,
_(
'The form handler plugin "{0}" is not ' "configurable!"
).format(form_handler_plugin.name),
)
return redirect("fobi.edit_form_entry", form_entry_id=form_entry.pk)
else:
form = form_handler_plugin.get_initialised_edit_form_or_404()
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_handler_plugin=form_handler_plugin,
)
)
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
self.object = None
(
form_entry,
form_handler_entry_id,
form_handler_plugin,
form_handler_plugin_form_cls,
obj,
) = self.get_essential_objects(
self.kwargs.get("form_handler_entry_id"),
self.request,
)
self.object = obj
if not form_handler_plugin_form_cls:
messages.info(
request,
_(
'The form handler plugin "{0}" is not ' "configurable!"
).format(form_handler_plugin.name),
)
return redirect("fobi.edit_form_entry", form_entry_id=form_entry.pk)
form = form_handler_plugin.get_initialised_edit_form_or_404(
data=request.POST, files=request.FILES
)
if form.is_valid():
# Saving the plugin form data.
form.save_plugin_data(request=request)
# Getting the plugin data.
obj.plugin_data = form.get_plugin_data(request=request)
# Save the object.
obj.save()
messages.info(
request,
_(
'The form handler plugin "{0}" was edited ' "successfully."
).format(form_handler_plugin.name),
)
return redirect(
"{0}?active_tab=tab-form-handlers".format(
reverse_lazy(
"fobi.edit_form_entry",
kwargs={"form_entry_id": form_entry.pk},
)
)
)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
form_handler_plugin=form_handler_plugin,
)
)
# *****************************************************************************
# **************************** Delete form handler entry **********************
# *****************************************************************************
class DeleteFormHandlerEntryView(AbstractDeletePluginEntryView):
"""Delete form handler entry."""
model = FormHandlerEntry
permission_classes = (DeleteFormHandlerEntryPermission,)
pk_url_kwarg = "form_handler_entry_id"
get_user_plugin_uids_func = get_user_form_handler_plugin_uids
message = _('The form handler plugin "{0}" was deleted successfully.')
html_anchor = "?active_tab=tab-form-handlers"
# *****************************************************************************
# *****************************************************************************
# ******************************** View form entry ****************************
# *****************************************************************************
# *****************************************************************************
# *****************************************************************************
# ******************************** View form entry ****************************
# *****************************************************************************
class AbstractViewFormEntryView(PermissionMixin, DetailView):
"""Abstract view form entry."""
model = FormEntry
slug_url_kwarg = "form_entry_slug"
template_name = None
theme = None
permission_classes = (ViewFormEntryPermission,)
def get_object(self, queryset=None):
"""Get object."""
if queryset is None:
queryset = self._get_queryset(request=self.request)
obj = super(AbstractViewFormEntryView, self).get_object(
queryset=queryset,
)
self.check_object_permissions(self.request, obj)
return obj
def _get_queryset(self, request):
"""Get queryset."""
queryset = FormEntry._default_manager.all().select_related("user")
if not request.user.is_authenticated:
queryset = queryset.filter(is_public=True)
return queryset
class ViewFormEntryView(AbstractViewFormEntryView):
"""View created form."""
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(ViewFormEntryView, self).get_context_data(**kwargs)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.view_form_entry_template
return [template_name]
def get_essential_objects(
self,
form_entry,
request,
):
"""Get essential objects."""
form_element_entries = form_entry.formelemententry_set.all()[:]
# This is where the most of the magic happens. Our form is being built
# dynamically.
form_cls = assemble_form_class(
form_entry,
form_element_entries=form_element_entries,
request=request,
)
return (
form_element_entries,
form_cls,
)
def get_initial_data(self, request, form_entry):
"""Get initial data.
:param request: HTTP request.
:param form_entry: Form entry object.
:return: Dictionary with initial form data.
"""
# Providing initial form data by feeding entire GET dictionary
# to the form, if ``GET_PARAM_INITIAL_DATA`` is present in the
# GET.
if GET_PARAM_INITIAL_DATA in request.GET:
return {"initial": request.GET}
return {}
def inactive_form_response(self, request, form_entry):
context = {
"form_entry": form_entry,
"page_header": (
form_entry.inactive_page_title
or form_entry.title
or form_entry.name
),
}
if not self.template_name:
theme = get_theme(request=request, as_instance=True)
template_name = theme.form_entry_inactive_template
else:
template_name = self.template_name
return render(request, template_name, context)
def get_object(self, queryset=None):
"""Get object."""
obj = super(ViewFormEntryView, self).get_object(queryset=queryset)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
try:
form_entry = self.get_object()
except ObjectDoesNotExist as err:
raise Http404(_("Form entry not found."))
self.object = form_entry
if not form_entry.is_active:
return self.inactive_form_response(request, form_entry)
form_element_entries, form_cls = self.get_essential_objects(
form_entry,
request,
)
# Get initial data.
kwargs = self.get_initial_data(request, form_entry)
form = form_cls(**kwargs)
# In debug mode, try to identify possible problems.
if DEBUG:
form.as_p()
else:
try:
form.as_p()
except Exception as err:
logger.error(err)
theme = get_theme(request=request, as_instance=True)
theme.collect_plugin_media(form_element_entries)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
fobi_form_title=form_entry.title,
)
)
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
try:
form_entry = self.get_object()
except ObjectDoesNotExist as err:
raise Http404(_("Form entry not found."))
self.object = form_entry
if not form_entry.is_active:
return self.inactive_form_response(request, form_entry)
form_element_entries, form_cls = self.get_essential_objects(
form_entry,
request,
)
form = form_cls(request.POST, request.FILES)
# Fire pre form validation callbacks
fire_form_callbacks(
form_entry=form_entry,
request=request,
form=form,
stage=CALLBACK_BEFORE_FORM_VALIDATION,
)
if form.is_valid():
# Fire form valid callbacks, before handling submitted plugin
# form data.
form = fire_form_callbacks(
form_entry=form_entry,
request=request,
form=form,
stage=CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
)
# Fire plugin processors
form = submit_plugin_form_data(
form_entry=form_entry, request=request, form=form
)
# Fire form valid callbacks
form = fire_form_callbacks(
form_entry=form_entry,
request=request,
form=form,
stage=CALLBACK_FORM_VALID,
)
# Run all handlers
handler_responses, handler_errors = run_form_handlers(
form_entry=form_entry,
request=request,
form=form,
form_element_entries=form_element_entries,
)
# Warning that not everything went ok.
if handler_errors:
for handler_error in handler_errors:
messages.warning(
request, _("Error occurred: {0}.").format(handler_error)
)
# Fire post handler callbacks
fire_form_callbacks(
form_entry=form_entry,
request=request,
form=form,
stage=CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS,
)
messages.info(
request,
_("Form {0} was submitted successfully.").format(
form_entry.name
),
)
return redirect(
reverse_lazy(
"fobi.form_entry_submitted", args=[form_entry.slug]
)
)
else:
# Fire post form validation callbacks
fire_form_callbacks(
form_entry=form_entry,
request=request,
form=form,
stage=CALLBACK_FORM_INVALID,
)
# In debug mode, try to identify possible problems.
if DEBUG:
form.as_p()
else:
try:
form.as_p()
except Exception as err:
logger.error(err)
theme = get_theme(request=request, as_instance=True)
theme.collect_plugin_media(form_element_entries)
return self.render_to_response(
self.get_context_data(
form=form,
form_entry=form_entry,
fobi_form_title=form_entry.title,
)
)
# *****************************************************************************
# **************************** View form entry success ************************
# *****************************************************************************
class ViewFormEntrySubmittedView(AbstractViewFormEntryView):
"""View form entry submitted."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
try:
form_entry = self.get_object()
except ObjectDoesNotExist as err:
raise Http404(_("Form entry not found."))
self.object = form_entry
return self.render_to_response(
self.get_context_data(
form_entry_slug=self.kwargs.get(self.slug_url_kwarg),
form_entry=form_entry,
)
)
def get_context_data(self, **kwargs):
"""Get context data."""
context = super(ViewFormEntrySubmittedView, self).get_context_data(
**kwargs
)
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
if theme:
context.update({"fobi_theme": theme})
return context
def get_template_names(self):
"""Get template names."""
template_name = self.template_name
if not template_name:
if not self.theme:
theme = get_theme(request=self.request, as_instance=True)
else:
theme = self.theme
template_name = theme.form_entry_submitted_template
return [template_name]