/Lib/site-packages/django/views/generic/edit.py
Python | 334 lines | 178 code | 52 blank | 104 comment | 31 complexity | f0dec32a2345ee9208ba9d9e0fdb34c6 MD5 | raw file
- import inspect
- import re
- import warnings
- from django.core.exceptions import ImproperlyConfigured
- from django.forms import models as model_forms
- from django.http import HttpResponseRedirect
- from django.utils import six
- from django.utils.deprecation import RemovedInDjango110Warning
- from django.utils.encoding import force_text
- from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
- from django.views.generic.detail import (
- BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin,
- )
- PERCENT_PLACEHOLDER_REGEX = re.compile(r'%\([^\)]+\)') # RemovedInDjango110Warning
- class FormMixinBase(type):
- def __new__(cls, name, bases, attrs):
- get_form = attrs.get('get_form')
- if get_form and inspect.isfunction(get_form):
- try:
- inspect.getcallargs(get_form, None)
- except TypeError:
- warnings.warn(
- "`%s.%s.get_form` method must define a default value for "
- "its `form_class` argument." % (attrs['__module__'], name),
- RemovedInDjango110Warning, stacklevel=2
- )
- def get_form_with_form_class(self, form_class=None):
- if form_class is None:
- form_class = self.get_form_class()
- return get_form(self, form_class=form_class)
- attrs['get_form'] = get_form_with_form_class
- return super(FormMixinBase, cls).__new__(cls, name, bases, attrs)
- class FormMixin(six.with_metaclass(FormMixinBase, ContextMixin)):
- """
- A mixin that provides a way to show and handle a form in a request.
- """
- initial = {}
- form_class = None
- success_url = None
- prefix = None
- def get_initial(self):
- """
- Returns the initial data to use for forms on this view.
- """
- return self.initial.copy()
- def get_prefix(self):
- """
- Returns the prefix to use for forms on this view
- """
- return self.prefix
- def get_form_class(self):
- """
- Returns the form class to use in this view
- """
- return self.form_class
- def get_form(self, form_class=None):
- """
- Returns an instance of the form to be used in this view.
- """
- if form_class is None:
- form_class = self.get_form_class()
- return form_class(**self.get_form_kwargs())
- def get_form_kwargs(self):
- """
- Returns the keyword arguments for instantiating the form.
- """
- kwargs = {
- 'initial': self.get_initial(),
- 'prefix': self.get_prefix(),
- }
- if self.request.method in ('POST', 'PUT'):
- kwargs.update({
- 'data': self.request.POST,
- 'files': self.request.FILES,
- })
- return kwargs
- def get_success_url(self):
- """
- Returns the supplied success URL.
- """
- if self.success_url:
- # Forcing possible reverse_lazy evaluation
- url = force_text(self.success_url)
- else:
- raise ImproperlyConfigured(
- "No URL to redirect to. Provide a success_url.")
- return url
- def form_valid(self, form):
- """
- If the form is valid, redirect to the supplied URL.
- """
- return HttpResponseRedirect(self.get_success_url())
- def form_invalid(self, form):
- """
- If the form is invalid, re-render the context data with the
- data-filled form and errors.
- """
- return self.render_to_response(self.get_context_data(form=form))
- class ModelFormMixin(FormMixin, SingleObjectMixin):
- """
- A mixin that provides a way to show and handle a modelform in a request.
- """
- fields = None
- def get_form_class(self):
- """
- Returns the form class to use in this view.
- """
- if self.fields is not None and self.form_class:
- raise ImproperlyConfigured(
- "Specifying both 'fields' and 'form_class' is not permitted."
- )
- if self.form_class:
- return self.form_class
- else:
- if self.model is not None:
- # If a model has been explicitly provided, use it
- model = self.model
- elif hasattr(self, 'object') and self.object is not None:
- # If this view is operating on a single object, use
- # the class of that object
- model = self.object.__class__
- else:
- # Try to get a queryset and extract the model class
- # from that
- model = self.get_queryset().model
- if self.fields is None:
- raise ImproperlyConfigured(
- "Using ModelFormMixin (base class of %s) without "
- "the 'fields' attribute is prohibited." % self.__class__.__name__
- )
- return model_forms.modelform_factory(model, fields=self.fields)
- def get_form_kwargs(self):
- """
- Returns the keyword arguments for instantiating the form.
- """
- kwargs = super(ModelFormMixin, self).get_form_kwargs()
- if hasattr(self, 'object'):
- kwargs.update({'instance': self.object})
- return kwargs
- def get_success_url(self):
- """
- Returns the supplied URL.
- """
- if self.success_url:
- # force_text can be removed with deprecation warning
- self.success_url = force_text(self.success_url)
- if PERCENT_PLACEHOLDER_REGEX.search(self.success_url):
- warnings.warn(
- "%()s placeholder style in success_url is deprecated. "
- "Please replace them by the {} Python format syntax.",
- RemovedInDjango110Warning, stacklevel=2
- )
- url = self.success_url % self.object.__dict__
- else:
- url = self.success_url.format(**self.object.__dict__)
- else:
- try:
- url = self.object.get_absolute_url()
- except AttributeError:
- raise ImproperlyConfigured(
- "No URL to redirect to. Either provide a url or define"
- " a get_absolute_url method on the Model.")
- return url
- def form_valid(self, form):
- """
- If the form is valid, save the associated model.
- """
- self.object = form.save()
- return super(ModelFormMixin, self).form_valid(form)
- class ProcessFormView(View):
- """
- A mixin that renders a form on GET and processes it on POST.
- """
- def get(self, request, *args, **kwargs):
- """
- Handles GET requests and instantiates a blank version of the form.
- """
- form = self.get_form()
- return self.render_to_response(self.get_context_data(form=form))
- def post(self, request, *args, **kwargs):
- """
- Handles POST requests, instantiating a form instance with the passed
- POST variables and then checked for validity.
- """
- form = self.get_form()
- if form.is_valid():
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
- # PUT is a valid HTTP verb for creating (with a known URL) or editing an
- # object, note that browsers only support POST for now.
- def put(self, *args, **kwargs):
- return self.post(*args, **kwargs)
- class BaseFormView(FormMixin, ProcessFormView):
- """
- A base view for displaying a form
- """
- class FormView(TemplateResponseMixin, BaseFormView):
- """
- A view for displaying a form, and rendering a template response.
- """
- class BaseCreateView(ModelFormMixin, ProcessFormView):
- """
- Base view for creating an new object instance.
- Using this base class requires subclassing to provide a response mixin.
- """
- def get(self, request, *args, **kwargs):
- self.object = None
- return super(BaseCreateView, self).get(request, *args, **kwargs)
- def post(self, request, *args, **kwargs):
- self.object = None
- return super(BaseCreateView, self).post(request, *args, **kwargs)
- class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
- """
- View for creating a new object instance,
- with a response rendered by template.
- """
- template_name_suffix = '_form'
- class BaseUpdateView(ModelFormMixin, ProcessFormView):
- """
- Base view for updating an existing object.
- Using this base class requires subclassing to provide a response mixin.
- """
- def get(self, request, *args, **kwargs):
- self.object = self.get_object()
- return super(BaseUpdateView, self).get(request, *args, **kwargs)
- def post(self, request, *args, **kwargs):
- self.object = self.get_object()
- return super(BaseUpdateView, self).post(request, *args, **kwargs)
- class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
- """
- View for updating an object,
- with a response rendered by template.
- """
- template_name_suffix = '_form'
- class DeletionMixin(object):
- """
- A mixin providing the ability to delete objects
- """
- success_url = None
- def delete(self, request, *args, **kwargs):
- """
- Calls the delete() method on the fetched object and then
- redirects to the success URL.
- """
- self.object = self.get_object()
- success_url = self.get_success_url()
- self.object.delete()
- return HttpResponseRedirect(success_url)
- # Add support for browsers which only accept GET and POST for now.
- def post(self, request, *args, **kwargs):
- return self.delete(request, *args, **kwargs)
- def get_success_url(self):
- if self.success_url:
- # force_text can be removed with deprecation warning
- self.success_url = force_text(self.success_url)
- if PERCENT_PLACEHOLDER_REGEX.search(self.success_url):
- warnings.warn(
- "%()s placeholder style in success_url is deprecated. "
- "Please replace them by the {} Python format syntax.",
- RemovedInDjango110Warning, stacklevel=2
- )
- return self.success_url % self.object.__dict__
- else:
- return self.success_url.format(**self.object.__dict__)
- else:
- raise ImproperlyConfigured(
- "No URL to redirect to. Provide a success_url.")
- class BaseDeleteView(DeletionMixin, BaseDetailView):
- """
- Base view for deleting an object.
- Using this base class requires subclassing to provide a response mixin.
- """
- class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView):
- """
- View for deleting an object retrieved with `self.get_object()`,
- with a response rendered by template.
- """
- template_name_suffix = '_confirm_delete'