Source code for superdjango.views.forms
# Imports
from django.http import HttpResponseRedirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from superdjango.exceptions import IMustBeMissingSomething
from .base import BaseView
from .messages import MessageMixin
# Exports
__all__ = (
"ConfirmView",
"CSRFExemptMixin",
"FormMixin",
"FormView",
)
# Mixins
[docs]class CSRFExemptMixin(object):
"""Provides `CSRF`_ exemption for a view.
.. _CSRF: https://docs.djangoproject.com/en/stable/ref/csrf/
.. note::
When used, this mixin should be the left-most class of the view.
"""
[docs] @method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
"""Dispatch the request without CSRF protection."""
# noinspection PyUnresolvedReferences
return super().dispatch(request, *args, **kwargs)
[docs]class FormMixin(MessageMixin):
"""Provides form-related methods and attributes."""
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."""
error_message = None
"""The message to be displayed for an invalid (failed) form submission."""
form_class = None
"""The class to use for rendering forms."""
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."""
# noinspection PyUnusedLocal
[docs] def form_valid(self, form):
"""Process a valid form and redirect to success URL."""
message = self.get_success_message()
if message is not None:
self.messages.success(message)
return HttpResponseRedirect(self.get_success_url())
[docs] def form_invalid(self, form):
"""Handle invalid form submission."""
# noinspection PyUnresolvedReferences
context = self.get_context_data(form=form)
message = self.get_error_message()
if message is not None:
self.messages.error(message)
# noinspection PyUnresolvedReferences
return self.render_to_response(context)
# noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs):
"""Handle GET requests."""
form = self.get_form()
# noinspection PyUnresolvedReferences
context = self.get_context_data(form=form)
# 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
"""
return self.cancel_url
[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['submit_text'] = self.get_submit_text()
return context
[docs] def get_error_message(self):
"""Get the error message for an invalid form submission.
:rtype: str | None
"""
return self.error_message
[docs] def get_form_class(self):
"""Get the form class used by this view."""
if self.form_class is not None:
return self.form_class
raise IMustBeMissingSomething(self.__class__.__name__, "form_class", "get_form_class")
[docs] def get_form(self, data=None, files=None, **kwargs):
"""Get the form instance.
:param data: The form data.
:type data: QueryDict
:param files: Files data.
:type files: QueryDict
:returns: The instantiated form instance.
Any additional keyword arguments are passed directly to the form.
"""
cls = self.get_form_class()
return cls(data=data, files=files, **kwargs)
[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
"""
return self.success_message
[docs] def get_success_url(self):
"""Get the URL to which the user should be sent on successful form submit.
:rtype: str
"""
if self.success_url is not None:
return self.success_url
# noinspection PyUnresolvedReferences
if 'next' in self.request.GET:
# noinspection PyUnresolvedReferences
return self.request.GET.get("next")
raise IMustBeMissingSomething(self.__class__.__name__, "success_url", "get_success_url")
# noinspection PyUnusedLocal
[docs] def post(self, request, *args, **kwargs):
"""Handle POST requests, i.e form submission."""
form = self.get_form(data=request.POST, files=request.FILES)
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
# Views
[docs]class ConfirmView(FormMixin, BaseView):
"""Provide a simple confirmation page before proceeding with another action.
Note that ``form_valid()`` and ``form_invalid()`` are ignored, and that no form or form class is used.
The template is used to provide the confirmation:
.. code-block:: html
<form method="post">
{% csrf_token %}
<p>{%trans "Are you sure you want to do this?" %}</p>
{% html "form_submit" %}
</form>
In order for the view to do something, the ``submit()`` method must be overridden:
.. code-block:: python
from superdjango.ui.views import ConfirmView
class MyConfirmView(ConfirmView):
pattern_name = "my_confirm_view"
pattern_value = 'my/confirmation/'
def submit(self, request):
# Do something after the form has been submitted.
Use the ``confirmed_keyword`` if you need to post data to the confirmation view prior to actual confirmation.
.. code-block:: python
from superdjango.ui.views import ConfirmView
class MyConfirmView(ConfirmView):
confirmed_keyword = "confirmed"
pattern_name = "my_confirm_view"
pattern_value = 'my/confirmation/'
def submit(self, request):
# Do something after the form has been submitted.
The template would also need to include the confirmation keyword:
.. code-block:: html
<form method="post">
{% csrf_token %}
<input type="hidden" name="{{ confirmed_keyword }}" value="1">
<p>{%trans "Are you sure you want to do this?" %}</p>
{% html "form_submit" %}
</form>
"""
confirmed_keyword = "confirmed"
"""The keyword submitted via POST that may used to confirm the next action."""
# noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs):
"""Handle GET requests."""
# noinspection PyAttributeOutsideInit
self.request = request
context = self.get_context_data()
return self.render_to_response(context)
[docs] def get_context_data(self, **kwargs):
"""Adds ``confirmed_keyword``."""
context = super().get_context_data(**kwargs)
context['confirmed_keyword'] = self.confirmed_keyword
return context
# noinspection PyUnusedLocal
[docs] def post(self, request, *args, **kwargs):
"""Handle POST by calling the ``submit()`` method.
The presence of ``confirmed_keyword`` is checked prior to calling ``submit()``. This allows data to be posted to
the view *without* the confirmation in order to re-use the view for confirming additional actions.
"""
# noinspection PyAttributeOutsideInit
self.request = request
confirmed = request.POST.get(self.confirmed_keyword, None)
if confirmed:
self.submit(request)
return HttpResponseRedirect(self.get_success_url())
context = self.get_context_data()
return self.render_to_response(context)
[docs] def submit(self, request):
"""Do whatever is required after the action has been confirmed.
:param request: The current request instance.
.. tip::
``submit()`` does nothing by default. Override this method if you need the confirmation view to do
something after the user has confirmed.
"""
pass