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 "../"
[docs] def get_verb(self): """Always returns ``VERB.WIZARD``.""" return VERB.WIZARD