Source code for superdjango.ui.views.forms
# Imports
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from formtools.wizard.views import SessionWizardView
import logging
import os
from ..constants import VERB
from .base import UIBaseMixin
log = logging.getLogger(__name__)
# Exports
__all__ = (
"UIFormMixin",
"UIFormSetMixin",
"UIFormWizardView",
)
# Mixins
[docs]class UIFormMixin(object):
"""Provides form-related methods and attributes."""
auto_complete_fields = None
"""A list of fields that should be filled using auto-complete."""
cancel_text = None
"""The text to display as part of the cancel URL."""
cancel_url = None
"""The URL to which the user should be sent when canceling the confirmation."""
chained_fields = None
"""A list of :py:class:`superdjango.ui.options.utils.ChainedLookup` instances."""
error_message = None
"""The message to be displayed for an invalid (failed) form submission."""
exit_warning = None
"""The message to display if the user has made changes to the form, but clicks something other than submit."""
fieldsets = None
"""A list of Fieldset instances into which form fields should be divided."""
fields_from_get = None
"""A list of field names whose initial value may be acquired from GET. This functionality is NOT provided by the
mixin and must be implemented (if desired) in child classes.
"""
form_class = None
"""The class to use for rendering forms."""
save_and_add = None
"""Indicates "Save and Add Another" functionality should be supported. Set to ``True`` or to the text to
be displayed on the button.
"""
save_and_continue = None
"""Indicates "Save and Continue Editing" functionality should be supported. Set to ``True`` or to the text to
be displayed on the button.
"""
save_as = None
"""Indicates "Save As" (duplicate) functionality should supported. Set ``True`` or to the text to be displayed on
the button. Note that ``save_as_options`` must also be defined on the model UI.
"""
submit_text = None
"""The text to display for the submit button."""
success_message = None
"""The message to be displayed after a valid (successful) form submission."""
success_url = None
"""The URL to which a user is sent after a successful (valid) form submission."""
tabs = None
"""A list of Tab instances into which form fields should be organized."""
# noinspection PyUnresolvedReferences
[docs] def form_valid(self, form, formsets=None):
"""Save the object and redirect.
:param form: The valid form instance.
:param formsets: A list of (valid) formset instances.
:rtype: HttpResponseRedirect
"""
if "save_as" in self.request.POST:
obj = form.save(commit=False)
obj.pk = None
obj.save()
form.save_m2m()
self.ui.save_history(obj, self.request, VERB.DUPLICATE)
# TODO: Determine if save as works when formsets are present.
if formsets:
for fs in formsets:
fs.save_new(form)
# child = fs.save(commit=False)
# setattr(child, fs.fk.name, obj)
# child.save()
url = self.ui.get_url(VERB.SAVE_AS, record=obj)
return HttpResponseRedirect(url)
# noinspection PyAttributeOutsideInit
self.object = self.ui.save_form(form, self.request, self.get_verb())
if formsets:
for fs in formsets:
fs.save()
# Handle submit actions.
if "save_and_add" in self.request.POST:
self.success_url = self.ui.get_url(VERB.CREATE)
elif "save_and_continue" in self.request.POST:
self.success_url = self.ui.get_url(VERB.UPDATE, record=self.object)
else:
pass
self.messages.success(self.get_success_message())
return HttpResponseRedirect(self.get_success_url())
[docs] def form_invalid(self, form, formsets=None):
"""Handle invalid form submission."""
# noinspection PyUnresolvedReferences
context = self.get_context_data(form=form, formsets=formsets)
# noinspection PyUnresolvedReferences
self.messages.error(self.get_error_message())
# noinspection PyUnresolvedReferences
return self.render_to_response(context)
# noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs):
"""Handle GET requests."""
# noinspection PyUnresolvedReferences
form = self.ui.get_form(request)
# noinspection PyUnresolvedReferences
context = self.get_context_data(form=form)
# Call formsets here because self.object (if present) is initialized in get_context_data() call.
context['formsets'] = self.get_formsets(request)
# noinspection PyUnresolvedReferences
return self.render_to_response(context)
[docs] def get_cancel_text(self):
"""Get the text to display for the cancel link or button.
:rtype: str
"""
if self.cancel_text is not None:
return self.cancel_text
return _("Cancel")
[docs] def get_cancel_url(self):
"""Get the URL for canceling the form.
:rtype: str | None
"""
if self.cancel_url is not None:
return self.cancel_url
# noinspection PyUnresolvedReferences
if 'prev' in self.request.GET:
# noinspection PyUnresolvedReferences
return self.request.GET.get("prev")
# noinspection PyUnresolvedReferences
url = self.ui.get_url_history(self.request)
if url is not None:
return url[0]
# noinspection PyUnresolvedReferences
url = self.ui.get_url(VERB.LIST)
if url is not None:
return url
# noinspection PyUnresolvedReferences
# session = self.get_session()
# if session.has("previous_query_string"):
# url += session.get("previous_query_string")
#
# return url
return "../"
[docs] def get_context_data(self, **kwargs):
"""Add form-specific values to the context.
- ``cancel_text``
- ``cancel_url``
- ``form``
- ``submit_text``
"""
# noinspection PyUnresolvedReferences
context = super().get_context_data(**kwargs)
context['cancel_text'] = self.get_cancel_text()
context['cancel_url'] = self.get_cancel_url()
context['save_and_add'] = self.get_save_and_add_text()
context['save_and_continue'] = self.get_save_and_continue_text()
context['save_as'] = self.get_save_as_text()
context['submit_text'] = self.get_submit_text()
return context
[docs] def get_css(self):
"""Get any CSS included in the form.
:rtype: StyleSheet
"""
# noinspection PyUnresolvedReferences
controls = self.get_controls()
# noinspection PyUnresolvedReferences
css = super().get_css()
for c in controls:
css.merge(c.get_form_css())
return css
[docs] def get_error_message(self):
"""Get the error message for an invalid form submission.
:rtype: str | None
"""
# noinspection PyUnresolvedReferences
context = self._get_message_context()
return self.error_message % context
def get_formsets(self, request):
# Inlines are used to generate formsets.
# noinspection PyUnresolvedReferences
inlines = self.ui.get_inlines(request, self.get_verb())
if inlines is None:
return None
# Iterate through the inlines. Note that the list may contain one or more instances.
a = list()
for i in inlines:
# There's nothing to do if form options have not been defined.
if i.form_options is None:
continue
# Ask the inline for a formset.
fs = i.get_formset(request, record=self.object)
# Impart inline options on the formset. This doesn't seem to cause an issue.
fs.orientation = i.form_options.orientation
fs.template = i.form_options.template
fs.verbose_name = i.get_verbose_name()
fs.verbose_name_plural = i.get_verbose_name_plural()
# Add the inline to the result.
a.append(fs)
# It's possible that no inlines have been found with form options.
if len(a) > 0:
return a
return None
[docs] def get_js(self):
"""Get JavaScript to be used in the form.
:rtype: JavaScript
"""
# Get any JavaScript provided by form controls.
# noinspection PyUnresolvedReferences
controls = self.get_controls()
# noinspection PyUnresolvedReferences
js = super().get_js()
for c in controls:
js.merge(c.get_form_js())
# Add options for chained fields.
if self.chained_fields is not None:
for option in self.chained_fields:
# noinspection PyUnresolvedReferences
js.merge(option.get_form_js(self.ui, record=self.object))
# Implement the exit warning.
if self.exit_warning is not None:
context = dict()
if self.exit_warning is True:
pass
else:
context['exit_warning'] = self.exit_warning
template = os.path.join("superdjango", "ui", "js", "exit_warning.js")
js.from_template("exit-warning", template, context=context)
# for i in js:
# print(i.identifier)
return js
[docs] def get_save_and_add_text(self):
"""Get the text to display for the "save and add another" button.
:rtype: str | None
"""
if self.save_and_add is None:
return None
if self.save_and_add is True:
return _("Save and Add Another")
return self.save_and_add
[docs] def get_save_and_continue_text(self):
"""Get the text to display for the "save and continue editing" button.
:rtype: str | None
"""
if self.save_and_continue is None:
return None
if self.save_and_continue is True:
return _("Save and Continue Editing")
return self.save_and_continue
[docs] def get_save_as_text(self):
"""Get the text to display for the "save as" button.
:rtype: str | None
.. note::
Unlike save/add and save/continue, save as is only enabled when ``save_as_options`` have been defined on
the model UI.
"""
# noinspection PyUnresolvedReferences
if self.ui.save_as_options is None:
return None
if self.save_as is None:
return None
# An existing object is required in order to invoke save as. Child classes must set self.object in the get()
# method.
try:
if self.object is None:
return None
except AttributeError:
return None
if self.save_as is True:
return _("Save As")
return self.save_as
[docs] def get_submit_text(self):
"""Get the text to display for the submit button.
:rtype: str
"""
if self.submit_text is not None:
return self.submit_text
return _("Submit")
[docs] def get_success_message(self):
"""Get the message to be displayed after a successful form submision.
:rtype: str | None
"""
# noinspection PyUnresolvedReferences
context = self._get_message_context()
return self.success_message % context
[docs] def get_success_url(self):
"""Get the URL to which the user should be sent on successful form submit.
:rtype: str
"""
# noinspection PyUnresolvedReferences
next_url = self.request.GET.get("next", None)
if next_url is not None:
return next_url
if self.success_url is not None:
return self.success_url
# noinspection PyUnresolvedReferences
url = self.ui.get_url_history(self.request)
if url is not None:
return url[0]
# noinspection PyUnresolvedReferences
url = self.ui.get_url(VERB.LIST)
if url is not None:
return url
# noinspection PyUnresolvedReferences
# session = self.get_session()
# if session.has("previous_query_string"):
# url += session.get("previous_query_string")
#
# return url
return "../"
# noinspection PyUnusedLocal
[docs] def post(self, request, *args, **kwargs):
"""Handle POST requests, i.e form submission."""
# noinspection PyUnresolvedReferences
form = self.ui.get_form(request, data=request.POST, files=request.FILES, **kwargs)
formsets = self.get_formsets(request)
valid = list()
valid.append(form.is_valid())
if formsets:
for fs in formsets:
valid.append(fs.is_valid())
if all(valid):
return self.form_valid(form, formsets=formsets)
return self.form_invalid(form, formsets=formsets)
class UIFormSetMixin(object):
pass
[docs]class UIFormWizardView(UIBaseMixin, SessionWizardView):
"""Create a form wizard."""
callback = None
"""The function to be called when the last form is submitted. See ``done()``. This function should accept the form
list, current request instance, current ModelUI instance, and keyword arguments.
"""
fields = None
"""A list of field names to be included across all forms."""
form_steps = None
"""A list for :py:class:`superdjango.ui.options.FormStep` instances."""
success_url = None
"""The URL to which the user is redirected after successfully submitting the last form."""
template_name_suffix = "_form_wizard"
"""The suffix for templates using this view."""
ui = None
"""The current ModelUI instance."""
[docs] def done(self, form_list, **kwargs):
"""Call the callback function and redirect to the success URL."""
# noinspection PyAttributeOutsideInit
self.object = self.callback(form_list, self.request, self.ui, **kwargs)
return HttpResponseRedirect(self.get_success_url())
[docs] def get_controls(self):
"""Get the controls used to display fields/columns.
:rtype: list[BaseType[BaseControl]]
"""
controls = list()
for field_name in self.fields:
control = self.ui.get_control(field_name)
if control is None:
message = "%s %s has requested a control for field that has not been defined: %s"
# log.warning(message % (self.ui.meta.model_name, self.__class__.__name__, field_name))
continue
controls.append(control)
return controls
# def get_context_data(self, *args, **kwargs):
# return super().get_context_data(*args, **kwargs)
[docs] def get_css(self):
"""Get any CSS to be included in the form.
:rtype: StyleSheet
"""
controls = self.get_controls()
css = super().get_css()
for c in controls:
css.merge(c.get_form_css())
return css
[docs] def get_form_prefix(self, step=None, form=None):
"""There is no need to prefix forms for the model UI."""
return ""
[docs] def get_js(self):
"""Get any JavaScript to be included in the form.
:rtype: JavaScript
"""
controls = self.get_controls()
js = super().get_js()
for c in controls:
js.merge(c.get_form_js())
return js
[docs] def get_success_url(self):
"""Get the URL to which the user is redirected after successfully submitting the last form.
:rtype: str
"""
if self.success_url is not None:
return self.success_url
return "../"