mirror of https://github.com/django/django.git
Fixed #3639: updated generic create_update views to use newforms. This is a backwards-incompatible change.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7952 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
cd80ce7a3d
commit
7997133a3d
|
@ -0,0 +1,3 @@
|
||||||
|
class GenericViewError(Exception):
|
||||||
|
"""A problem in a generic view."""
|
||||||
|
pass
|
|
@ -1,154 +1,195 @@
|
||||||
from django.core.xheaders import populate_xheaders
|
from django.newforms.models import ModelFormMetaclass, ModelForm
|
||||||
from django.template import loader
|
from django.template import RequestContext, loader
|
||||||
from django import oldforms
|
|
||||||
from django.db.models import FileField
|
|
||||||
from django.contrib.auth.views import redirect_to_login
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
|
from django.core.xheaders import populate_xheaders
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
|
from django.contrib.auth.views import redirect_to_login
|
||||||
|
from django.views.generic import GenericViewError
|
||||||
|
|
||||||
def create_object(request, model, template_name=None,
|
def deprecate_follow(follow):
|
||||||
template_loader=loader, extra_context=None, post_save_redirect=None,
|
|
||||||
login_required=False, follow=None, context_processors=None):
|
|
||||||
"""
|
"""
|
||||||
Generic object-creation function.
|
Issues a DeprecationWarning if follow is anything but None.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_form.html``
|
The old Manipulator-based forms used a follow argument that is no longer
|
||||||
Context:
|
needed for newforms-based forms.
|
||||||
form
|
|
||||||
the form wrapper for the object
|
|
||||||
"""
|
"""
|
||||||
if extra_context is None: extra_context = {}
|
if follow is not None:
|
||||||
if login_required and not request.user.is_authenticated():
|
import warning
|
||||||
return redirect_to_login(request.path)
|
msg = ("Generic views have been changed to use newforms, and the"
|
||||||
|
"'follow' argument is no longer used. Please update your code"
|
||||||
|
"to not use the 'follow' argument.")
|
||||||
|
warning.warn(msg, DeprecationWarning, stacklevel=3)
|
||||||
|
|
||||||
manipulator = model.AddManipulator(follow=follow)
|
def apply_extra_context(extra_context, context):
|
||||||
if request.POST:
|
"""
|
||||||
# If data was POSTed, we're trying to create a new object
|
Adds items from extra_context dict to context. If a value in extra_context
|
||||||
new_data = request.POST.copy()
|
is callable, then it is called and the result is added to context.
|
||||||
|
"""
|
||||||
if model._meta.has_field_type(FileField):
|
for key, value in extra_context.iteritems():
|
||||||
new_data.update(request.FILES)
|
|
||||||
|
|
||||||
# Check for errors
|
|
||||||
errors = manipulator.get_validation_errors(new_data)
|
|
||||||
manipulator.do_html2python(new_data)
|
|
||||||
|
|
||||||
if not errors:
|
|
||||||
# No errors -- this means we can save the data!
|
|
||||||
new_object = manipulator.save(new_data)
|
|
||||||
|
|
||||||
if request.user.is_authenticated():
|
|
||||||
request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
|
|
||||||
|
|
||||||
# Redirect to the new object: first by trying post_save_redirect,
|
|
||||||
# then by obj.get_absolute_url; fail if neither works.
|
|
||||||
if post_save_redirect:
|
|
||||||
return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
|
|
||||||
elif hasattr(new_object, 'get_absolute_url'):
|
|
||||||
return HttpResponseRedirect(new_object.get_absolute_url())
|
|
||||||
else:
|
|
||||||
raise ImproperlyConfigured("No URL to redirect to from generic create view.")
|
|
||||||
else:
|
|
||||||
# No POST, so we want a brand new form without any data or errors
|
|
||||||
errors = {}
|
|
||||||
new_data = manipulator.flatten_data()
|
|
||||||
|
|
||||||
# Create the FormWrapper, template, context, response
|
|
||||||
form = oldforms.FormWrapper(manipulator, new_data, errors)
|
|
||||||
if not template_name:
|
|
||||||
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
|
|
||||||
t = template_loader.get_template(template_name)
|
|
||||||
c = RequestContext(request, {
|
|
||||||
'form': form,
|
|
||||||
}, context_processors)
|
|
||||||
for key, value in extra_context.items():
|
|
||||||
if callable(value):
|
if callable(value):
|
||||||
c[key] = value()
|
context[key] = value()
|
||||||
else:
|
else:
|
||||||
c[key] = value
|
context[key] = value
|
||||||
return HttpResponse(t.render(c))
|
|
||||||
|
|
||||||
def update_object(request, model, object_id=None, slug=None,
|
def get_model_and_form_class(model, form_class):
|
||||||
slug_field='slug', template_name=None, template_loader=loader,
|
|
||||||
extra_context=None, post_save_redirect=None,
|
|
||||||
login_required=False, follow=None, context_processors=None,
|
|
||||||
template_object_name='object'):
|
|
||||||
"""
|
"""
|
||||||
Generic object-update function.
|
Returns a model and form class based on the model and form_class
|
||||||
|
parameters that were passed to the generic view.
|
||||||
|
|
||||||
Templates: ``<app_label>/<model_name>_form.html``
|
If ``form_class`` is given then its associated model will be returned along
|
||||||
Context:
|
with ``form_class`` itself. Otherwise, if ``model`` is given, ``model``
|
||||||
form
|
itself will be returned along with a ``ModelForm`` class created from
|
||||||
the form wrapper for the object
|
``model``.
|
||||||
object
|
|
||||||
the original object being edited
|
|
||||||
"""
|
"""
|
||||||
if extra_context is None: extra_context = {}
|
if form_class:
|
||||||
if login_required and not request.user.is_authenticated():
|
return form_class._meta.model, form_class
|
||||||
return redirect_to_login(request.path)
|
if model:
|
||||||
|
# The inner Meta class fails if model = model is used for some reason.
|
||||||
|
tmp_model = model
|
||||||
|
# TODO: we should be able to construct a ModelForm without creating
|
||||||
|
# and passing in a temporary inner class.
|
||||||
|
class Meta:
|
||||||
|
model = tmp_model
|
||||||
|
class_name = model.__name__ + 'Form'
|
||||||
|
form_class = ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta})
|
||||||
|
return model, form_class
|
||||||
|
raise GenericViewError("Generic view must be called with either a model or"
|
||||||
|
" form_class argument.")
|
||||||
|
|
||||||
# Look up the object to be edited
|
def redirect(post_save_redirect, obj):
|
||||||
|
"""
|
||||||
|
Returns a HttpResponseRedirect to ``post_save_redirect``.
|
||||||
|
|
||||||
|
``post_save_redirect`` should be a string, and can contain named string-
|
||||||
|
substitution place holders of ``obj`` field names.
|
||||||
|
|
||||||
|
If ``post_save_redirect`` is None, then redirect to ``obj``'s URL returned
|
||||||
|
by ``get_absolute_url()``. If ``obj`` has no ``get_absolute_url`` method,
|
||||||
|
then raise ImproperlyConfigured.
|
||||||
|
|
||||||
|
This method is meant to handle the post_save_redirect parameter to the
|
||||||
|
``create_object`` and ``update_object`` views.
|
||||||
|
"""
|
||||||
|
if post_save_redirect:
|
||||||
|
return HttpResponseRedirect(post_save_redirect % obj.__dict__)
|
||||||
|
elif hasattr(obj, 'get_absolute_url'):
|
||||||
|
return HttpResponseRedirect(obj.get_absolute_url())
|
||||||
|
else:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"No URL to redirect to. Either pass a post_save_redirect"
|
||||||
|
" parameter to the generic view or define a get_absolute_url"
|
||||||
|
" method on the Model.")
|
||||||
|
|
||||||
|
def lookup_object(model, object_id, slug, slug_field):
|
||||||
|
"""
|
||||||
|
Return the ``model`` object with the passed ``object_id``. If
|
||||||
|
``object_id`` is None, then return the the object whose ``slug_field``
|
||||||
|
equals the passed ``slug``. If ``slug`` and ``slug_field`` are not passed,
|
||||||
|
then raise Http404 exception.
|
||||||
|
"""
|
||||||
lookup_kwargs = {}
|
lookup_kwargs = {}
|
||||||
if object_id:
|
if object_id:
|
||||||
lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
|
lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
|
||||||
elif slug and slug_field:
|
elif slug and slug_field:
|
||||||
lookup_kwargs['%s__exact' % slug_field] = slug
|
lookup_kwargs['%s__exact' % slug_field] = slug
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
|
raise GenericViewError(
|
||||||
|
"Generic view must be called with either an object_id or a"
|
||||||
|
" slug/slug_field.")
|
||||||
try:
|
try:
|
||||||
object = model.objects.get(**lookup_kwargs)
|
return model.objects.get(**lookup_kwargs)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
|
raise Http404("No %s found for %s"
|
||||||
|
% (model._meta.verbose_name, lookup_kwargs))
|
||||||
|
|
||||||
manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
|
def create_object(request, model=None, template_name=None,
|
||||||
|
template_loader=loader, extra_context=None, post_save_redirect=None,
|
||||||
|
login_required=False, follow=None, context_processors=None,
|
||||||
|
form_class=None):
|
||||||
|
"""
|
||||||
|
Generic object-creation function.
|
||||||
|
|
||||||
if request.POST:
|
Templates: ``<app_label>/<model_name>_form.html``
|
||||||
new_data = request.POST.copy()
|
Context:
|
||||||
if model._meta.has_field_type(FileField):
|
form
|
||||||
new_data.update(request.FILES)
|
the form for the object
|
||||||
errors = manipulator.get_validation_errors(new_data)
|
"""
|
||||||
manipulator.do_html2python(new_data)
|
deprecate_follow(follow)
|
||||||
if not errors:
|
if extra_context is None: extra_context = {}
|
||||||
object = manipulator.save(new_data)
|
if login_required and not request.user.is_authenticated():
|
||||||
|
return redirect_to_login(request.path)
|
||||||
|
|
||||||
|
model, form_class = get_model_and_form_class(model, form_class)
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = form_class(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
new_object = form.save()
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated():
|
||||||
request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
|
request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
|
||||||
|
return redirect(post_save_redirect, new_object)
|
||||||
# Do a post-after-redirect so that reload works, etc.
|
|
||||||
if post_save_redirect:
|
|
||||||
return HttpResponseRedirect(post_save_redirect % object.__dict__)
|
|
||||||
elif hasattr(object, 'get_absolute_url'):
|
|
||||||
return HttpResponseRedirect(object.get_absolute_url())
|
|
||||||
else:
|
|
||||||
raise ImproperlyConfigured("No URL to redirect to from generic create view.")
|
|
||||||
else:
|
else:
|
||||||
errors = {}
|
form = form_class()
|
||||||
# This makes sure the form acurate represents the fields of the place.
|
|
||||||
new_data = manipulator.flatten_data()
|
|
||||||
|
|
||||||
form = oldforms.FormWrapper(manipulator, new_data, errors)
|
# Create the template, context, response
|
||||||
if not template_name:
|
if not template_name:
|
||||||
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
|
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
|
||||||
t = template_loader.get_template(template_name)
|
t = template_loader.get_template(template_name)
|
||||||
c = RequestContext(request, {
|
c = RequestContext(request, {
|
||||||
'form': form,
|
'form': form,
|
||||||
template_object_name: object,
|
|
||||||
}, context_processors)
|
}, context_processors)
|
||||||
for key, value in extra_context.items():
|
apply_extra_context(extra_context, c)
|
||||||
if callable(value):
|
return HttpResponse(t.render(c))
|
||||||
c[key] = value()
|
|
||||||
else:
|
def update_object(request, model=None, object_id=None, slug=None,
|
||||||
c[key] = value
|
slug_field='slug', template_name=None, template_loader=loader,
|
||||||
|
extra_context=None, post_save_redirect=None,
|
||||||
|
login_required=False, follow=None, context_processors=None,
|
||||||
|
template_object_name='object', form_class=None):
|
||||||
|
"""
|
||||||
|
Generic object-update function.
|
||||||
|
|
||||||
|
Templates: ``<app_label>/<model_name>_form.html``
|
||||||
|
Context:
|
||||||
|
form
|
||||||
|
the form for the object
|
||||||
|
object
|
||||||
|
the original object being edited
|
||||||
|
"""
|
||||||
|
deprecate_follow(follow)
|
||||||
|
if extra_context is None: extra_context = {}
|
||||||
|
if login_required and not request.user.is_authenticated():
|
||||||
|
return redirect_to_login(request.path)
|
||||||
|
|
||||||
|
model, form_class = get_model_and_form_class(model, form_class)
|
||||||
|
obj = lookup_object(model, object_id, slug, slug_field)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = form_class(request.POST, request.FILES, instance=obj)
|
||||||
|
if form.is_valid():
|
||||||
|
obj = form.save()
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
|
||||||
|
return redirect(post_save_redirect, obj)
|
||||||
|
else:
|
||||||
|
form = form_class(instance=obj)
|
||||||
|
|
||||||
|
if not template_name:
|
||||||
|
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
|
||||||
|
t = template_loader.get_template(template_name)
|
||||||
|
c = RequestContext(request, {
|
||||||
|
'form': form,
|
||||||
|
template_object_name: obj,
|
||||||
|
}, context_processors)
|
||||||
|
apply_extra_context(extra_context, c)
|
||||||
response = HttpResponse(t.render(c))
|
response = HttpResponse(t.render(c))
|
||||||
populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
|
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete_object(request, model, post_delete_redirect,
|
def delete_object(request, model, post_delete_redirect, object_id=None,
|
||||||
object_id=None, slug=None, slug_field='slug', template_name=None,
|
slug=None, slug_field='slug', template_name=None,
|
||||||
template_loader=loader, extra_context=None,
|
template_loader=loader, extra_context=None, login_required=False,
|
||||||
login_required=False, context_processors=None, template_object_name='object'):
|
context_processors=None, template_object_name='object'):
|
||||||
"""
|
"""
|
||||||
Generic object-delete function.
|
Generic object-delete function.
|
||||||
|
|
||||||
|
@ -165,21 +206,10 @@ def delete_object(request, model, post_delete_redirect,
|
||||||
if login_required and not request.user.is_authenticated():
|
if login_required and not request.user.is_authenticated():
|
||||||
return redirect_to_login(request.path)
|
return redirect_to_login(request.path)
|
||||||
|
|
||||||
# Look up the object to be edited
|
obj = lookup_object(model, object_id, slug, slug_field)
|
||||||
lookup_kwargs = {}
|
|
||||||
if object_id:
|
|
||||||
lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
|
|
||||||
elif slug and slug_field:
|
|
||||||
lookup_kwargs['%s__exact' % slug_field] = slug
|
|
||||||
else:
|
|
||||||
raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
|
|
||||||
try:
|
|
||||||
object = model._default_manager.get(**lookup_kwargs)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
object.delete()
|
obj.delete()
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated():
|
||||||
request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
|
request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
|
||||||
return HttpResponseRedirect(post_delete_redirect)
|
return HttpResponseRedirect(post_delete_redirect)
|
||||||
|
@ -188,13 +218,9 @@ def delete_object(request, model, post_delete_redirect,
|
||||||
template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower())
|
template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower())
|
||||||
t = template_loader.get_template(template_name)
|
t = template_loader.get_template(template_name)
|
||||||
c = RequestContext(request, {
|
c = RequestContext(request, {
|
||||||
template_object_name: object,
|
template_object_name: obj,
|
||||||
}, context_processors)
|
}, context_processors)
|
||||||
for key, value in extra_context.items():
|
apply_extra_context(extra_context, c)
|
||||||
if callable(value):
|
|
||||||
c[key] = value()
|
|
||||||
else:
|
|
||||||
c[key] = value
|
|
||||||
response = HttpResponse(t.render(c))
|
response = HttpResponse(t.render(c))
|
||||||
populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
|
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -701,7 +701,7 @@ A page representing a list of objects.
|
||||||
query string parameter (via ``GET``) or a ``page`` variable specified in
|
query string parameter (via ``GET``) or a ``page`` variable specified in
|
||||||
the URLconf. See `Notes on pagination`_ below.
|
the URLconf. See `Notes on pagination`_ below.
|
||||||
|
|
||||||
* ``page``: The current page number, as an integer. This is 1-based.
|
* ``page``: The current page number, as an integer. This is 1-based.
|
||||||
See `Notes on pagination`_ below.
|
See `Notes on pagination`_ below.
|
||||||
|
|
||||||
* ``template_name``: The full name of a template to use in rendering the
|
* ``template_name``: The full name of a template to use in rendering the
|
||||||
|
@ -809,25 +809,25 @@ specify the page number in the URL in one of two ways:
|
||||||
|
|
||||||
/objects/?page=3
|
/objects/?page=3
|
||||||
|
|
||||||
* To loop over all the available page numbers, use the ``page_range``
|
* To loop over all the available page numbers, use the ``page_range``
|
||||||
variable. You can iterate over the list provided by ``page_range``
|
variable. You can iterate over the list provided by ``page_range``
|
||||||
to create a link to every page of results.
|
to create a link to every page of results.
|
||||||
|
|
||||||
These values and lists are 1-based, not 0-based, so the first page would be
|
These values and lists are 1-based, not 0-based, so the first page would be
|
||||||
represented as page ``1``.
|
represented as page ``1``.
|
||||||
|
|
||||||
For more on pagination, read the `pagination documentation`_.
|
For more on pagination, read the `pagination documentation`_.
|
||||||
|
|
||||||
.. _`pagination documentation`: ../pagination/
|
.. _`pagination documentation`: ../pagination/
|
||||||
|
|
||||||
**New in Django development version:**
|
**New in Django development version:**
|
||||||
|
|
||||||
As a special case, you are also permitted to use ``last`` as a value for
|
As a special case, you are also permitted to use ``last`` as a value for
|
||||||
``page``::
|
``page``::
|
||||||
|
|
||||||
/objects/?page=last
|
/objects/?page=last
|
||||||
|
|
||||||
This allows you to access the final page of results without first having to
|
This allows you to access the final page of results without first having to
|
||||||
determine how many pages there are.
|
determine how many pages there are.
|
||||||
|
|
||||||
Note that ``page`` *must* be either a valid page number or the value ``last``;
|
Note that ``page`` *must* be either a valid page number or the value ``last``;
|
||||||
|
@ -906,19 +906,33 @@ Create/update/delete generic views
|
||||||
The ``django.views.generic.create_update`` module contains a set of functions
|
The ``django.views.generic.create_update`` module contains a set of functions
|
||||||
for creating, editing and deleting objects.
|
for creating, editing and deleting objects.
|
||||||
|
|
||||||
|
**Changed in Django development version:**
|
||||||
|
|
||||||
|
``django.views.generic.create_update.create_object`` and
|
||||||
|
``django.views.generic.create_update.update_object`` now use `newforms`_ to
|
||||||
|
build and display the form.
|
||||||
|
|
||||||
|
.. _newforms: ../newforms/
|
||||||
|
|
||||||
``django.views.generic.create_update.create_object``
|
``django.views.generic.create_update.create_object``
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
**Description:**
|
**Description:**
|
||||||
|
|
||||||
A page that displays a form for creating an object, redisplaying the form with
|
A page that displays a form for creating an object, redisplaying the form with
|
||||||
validation errors (if there are any) and saving the object. This uses the
|
validation errors (if there are any) and saving the object.
|
||||||
automatic manipulators that come with Django models.
|
|
||||||
|
|
||||||
**Required arguments:**
|
**Required arguments:**
|
||||||
|
|
||||||
* ``model``: The Django model class of the object that the form will
|
* Either ``form_class`` or ``model`` is required.
|
||||||
create.
|
|
||||||
|
If you provide ``form_class``, it should be a
|
||||||
|
``django.newforms.ModelForm`` subclass. Use this argument when you need
|
||||||
|
to customize the model's form. See the `ModelForm docs`_ for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
Otherwise, ``model`` should be a Django model class and the form used
|
||||||
|
will be a standard ``ModelForm`` for ``model``.
|
||||||
|
|
||||||
**Optional arguments:**
|
**Optional arguments:**
|
||||||
|
|
||||||
|
@ -959,22 +973,23 @@ If ``template_name`` isn't specified, this view will use the template
|
||||||
|
|
||||||
In addition to ``extra_context``, the template's context will be:
|
In addition to ``extra_context``, the template's context will be:
|
||||||
|
|
||||||
* ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
|
* ``form``: A ``django.newforms.ModelForm`` instance representing the form
|
||||||
for editing the object. This lets you refer to form fields easily in the
|
for creating the object. This lets you refer to form fields easily in the
|
||||||
template system.
|
template system.
|
||||||
|
|
||||||
For example, if ``model`` has two fields, ``name`` and ``address``::
|
For example, if the model has two fields, ``name`` and ``address``::
|
||||||
|
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
<p><label for="id_name">Name:</label> {{ form.name }}</p>
|
<p>{{ form.name.label_tag }} {{ form.name }}</p>
|
||||||
<p><label for="id_address">Address:</label> {{ form.address }}</p>
|
<p>{{ form.address.label_tag }} {{ form.address }}</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
See the `manipulator and formfield documentation`_ for more information
|
See the `newforms documentation`_ for more information about using
|
||||||
about using ``FormWrapper`` objects in templates.
|
``Form`` objects in templates.
|
||||||
|
|
||||||
.. _authentication system: ../authentication/
|
.. _authentication system: ../authentication/
|
||||||
.. _manipulator and formfield documentation: ../forms/
|
.. _ModelForm docs: ../newforms/modelforms
|
||||||
|
.. _newforms documentation: ../newforms/
|
||||||
|
|
||||||
``django.views.generic.create_update.update_object``
|
``django.views.generic.create_update.update_object``
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
@ -987,8 +1002,15 @@ object. This uses the automatic manipulators that come with Django models.
|
||||||
|
|
||||||
**Required arguments:**
|
**Required arguments:**
|
||||||
|
|
||||||
* ``model``: The Django model class of the object that the form will
|
* Either ``form_class`` or ``model`` is required.
|
||||||
create.
|
|
||||||
|
If you provide ``form_class``, it should be a
|
||||||
|
``django.newforms.ModelForm`` subclass. Use this argument when you need
|
||||||
|
to customize the model's form. See the `ModelForm docs`_ for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
Otherwise, ``model`` should be a Django model class and the form used
|
||||||
|
will be a standard ``ModelForm`` for ``model``.
|
||||||
|
|
||||||
* Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
|
* Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
|
||||||
|
|
||||||
|
@ -1041,19 +1063,19 @@ If ``template_name`` isn't specified, this view will use the template
|
||||||
|
|
||||||
In addition to ``extra_context``, the template's context will be:
|
In addition to ``extra_context``, the template's context will be:
|
||||||
|
|
||||||
* ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
|
* ``form``: A ``django.newforms.ModelForm`` instance representing the form
|
||||||
for editing the object. This lets you refer to form fields easily in the
|
for editing the object. This lets you refer to form fields easily in the
|
||||||
template system.
|
template system.
|
||||||
|
|
||||||
For example, if ``model`` has two fields, ``name`` and ``address``::
|
For example, if the model has two fields, ``name`` and ``address``::
|
||||||
|
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
<p><label for="id_name">Name:</label> {{ form.name }}</p>
|
<p>{{ form.name.label_tag }} {{ form.name }}</p>
|
||||||
<p><label for="id_address">Address:</label> {{ form.address }}</p>
|
<p>{{ form.address.label_tag }} {{ form.address }}</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
See the `manipulator and formfield documentation`_ for more information
|
See the `newforms documentation`_ for more information about using
|
||||||
about using ``FormWrapper`` objects in templates.
|
``Form`` objects in templates.
|
||||||
|
|
||||||
* ``object``: The original object being edited. This variable's name
|
* ``object``: The original object being edited. This variable's name
|
||||||
depends on the ``template_object_name`` parameter, which is ``'object'``
|
depends on the ``template_object_name`` parameter, which is ``'object'``
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"pk": "1",
|
||||||
|
"model": "auth.user",
|
||||||
|
"fields": {
|
||||||
|
"username": "testclient",
|
||||||
|
"first_name": "Test",
|
||||||
|
"last_name": "Client",
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false,
|
||||||
|
"is_staff": false,
|
||||||
|
"last_login": "2006-12-17 07:03:31",
|
||||||
|
"groups": [],
|
||||||
|
"user_permissions": [],
|
||||||
|
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
|
||||||
|
"email": "testclient@example.com",
|
||||||
|
"date_joined": "2006-12-17 07:03:31"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"model": "views.article",
|
"model": "views.article",
|
||||||
|
@ -29,7 +47,16 @@
|
||||||
"date_created": "3000-01-01 21:22:23"
|
"date_created": "3000-01-01 21:22:23"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pk": 1,
|
||||||
|
"model": "views.urlarticle",
|
||||||
|
"fields": {
|
||||||
|
"author": 1,
|
||||||
|
"title": "Old Article",
|
||||||
|
"slug": "old_article",
|
||||||
|
"date_created": "2001-01-01 21:22:23"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"model": "views.author",
|
"model": "views.author",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Regression tests for Django built-in views
|
Regression tests for Django built-in views.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
class Author(models.Model):
|
class Author(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
|
@ -14,13 +13,28 @@ class Author(models.Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return '/views/authors/%s/' % self.id
|
return '/views/authors/%s/' % self.id
|
||||||
|
|
||||||
|
class BaseArticle(models.Model):
|
||||||
class Article(models.Model):
|
"""
|
||||||
|
An abstract article Model so that we can create article models with and
|
||||||
|
without a get_absolute_url method (for create_update generic views tests).
|
||||||
|
"""
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
author = models.ForeignKey(Author)
|
author = models.ForeignKey(Author)
|
||||||
date_created = models.DateTimeField()
|
date_created = models.DateTimeField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
class Article(BaseArticle):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UrlArticle(BaseArticle):
|
||||||
|
"""
|
||||||
|
An Article class with a get_absolute_url defined.
|
||||||
|
"""
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return '/urlarticles/%s/' % self.slug
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from defaults import *
|
from defaults import *
|
||||||
from i18n import *
|
from i18n import *
|
||||||
from static import *
|
from static import *
|
||||||
from generic.date_based import *
|
from generic.date_based import *
|
||||||
|
from generic.create_update import *
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from regressiontests.views.models import Article, UrlArticle
|
||||||
|
|
||||||
|
class CreateObjectTest(TestCase):
|
||||||
|
|
||||||
|
fixtures = ['testdata.json']
|
||||||
|
|
||||||
|
def test_login_required_view(self):
|
||||||
|
"""
|
||||||
|
Verifies that an unauthenticated user attempting to access a
|
||||||
|
login_required view gets redirected to the login page and that
|
||||||
|
an authenticated user is let through.
|
||||||
|
"""
|
||||||
|
view_url = '/views/create_update/member/create/article/'
|
||||||
|
response = self.client.get(view_url)
|
||||||
|
self.assertRedirects(response, '/accounts/login/?next=%s' % view_url)
|
||||||
|
# Now login and try again.
|
||||||
|
login = self.client.login(username='testclient', password='password')
|
||||||
|
self.failUnless(login, 'Could not log in')
|
||||||
|
response = self.client.get(view_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, 'views/article_form.html')
|
||||||
|
|
||||||
|
def test_create_article_display_page(self):
|
||||||
|
"""
|
||||||
|
Ensures the generic view returned the page and contains a form.
|
||||||
|
"""
|
||||||
|
view_url = '/views/create_update/create/article/'
|
||||||
|
response = self.client.get(view_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, 'views/article_form.html')
|
||||||
|
if not response.context.get('form'):
|
||||||
|
self.fail('No form found in the response.')
|
||||||
|
|
||||||
|
def test_create_article_with_errors(self):
|
||||||
|
"""
|
||||||
|
POSTs a form that contains validation errors.
|
||||||
|
"""
|
||||||
|
view_url = '/views/create_update/create/article/'
|
||||||
|
num_articles = Article.objects.count()
|
||||||
|
response = self.client.post(view_url, {
|
||||||
|
'title': 'My First Article',
|
||||||
|
})
|
||||||
|
self.assertFormError(response, 'form', 'slug', [u'This field is required.'])
|
||||||
|
self.assertTemplateUsed(response, 'views/article_form.html')
|
||||||
|
self.assertEqual(num_articles, Article.objects.count(),
|
||||||
|
"Number of Articles should not have changed.")
|
||||||
|
|
||||||
|
def test_create_custom_save_article(self):
|
||||||
|
"""
|
||||||
|
Creates a new article using a custom form class with a save method
|
||||||
|
that alters the slug entered.
|
||||||
|
"""
|
||||||
|
view_url = '/views/create_update/create_custom/article/'
|
||||||
|
response = self.client.post(view_url, {
|
||||||
|
'title': 'Test Article',
|
||||||
|
'slug': 'this-should-get-replaced',
|
||||||
|
'author': 1,
|
||||||
|
'date_created': datetime.datetime(2007, 6, 25),
|
||||||
|
})
|
||||||
|
self.assertRedirects(response,
|
||||||
|
'/views/create_update/view/article/some-other-slug/',
|
||||||
|
target_status_code=404)
|
||||||
|
|
||||||
|
class UpdateDeleteObjectTest(TestCase):
|
||||||
|
|
||||||
|
fixtures = ['testdata.json']
|
||||||
|
|
||||||
|
def test_update_object_form_display(self):
|
||||||
|
"""
|
||||||
|
Verifies that the form was created properly and with initial values.
|
||||||
|
"""
|
||||||
|
response = self.client.get('/views/create_update/update/article/old_article/')
|
||||||
|
self.assertTemplateUsed(response, 'views/article_form.html')
|
||||||
|
self.assertEquals(unicode(response.context['form']['title']),
|
||||||
|
u'<input id="id_title" type="text" name="title" value="Old Article" maxlength="100" />')
|
||||||
|
|
||||||
|
def test_update_object(self):
|
||||||
|
"""
|
||||||
|
Verifies the updating of an Article.
|
||||||
|
"""
|
||||||
|
response = self.client.post('/views/create_update/update/article/old_article/', {
|
||||||
|
'title': 'Another Article',
|
||||||
|
'slug': 'another-article-slug',
|
||||||
|
'author': 1,
|
||||||
|
'date_created': datetime.datetime(2007, 6, 25),
|
||||||
|
})
|
||||||
|
article = Article.objects.get(pk=1)
|
||||||
|
self.assertEquals(article.title, "Another Article")
|
||||||
|
|
||||||
|
def test_delete_object_confirm(self):
|
||||||
|
"""
|
||||||
|
Verifies the confirm deletion page is displayed using a GET.
|
||||||
|
"""
|
||||||
|
response = self.client.get('/views/create_update/delete/article/old_article/')
|
||||||
|
self.assertTemplateUsed(response, 'views/article_confirm_delete.html')
|
||||||
|
|
||||||
|
def test_delete_object(self):
|
||||||
|
"""
|
||||||
|
Verifies the object actually gets deleted on a POST.
|
||||||
|
"""
|
||||||
|
view_url = '/views/create_update/delete/article/old_article/'
|
||||||
|
response = self.client.post(view_url)
|
||||||
|
try:
|
||||||
|
Article.objects.get(slug='old_article')
|
||||||
|
except Article.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail('Object was not deleted.')
|
||||||
|
|
||||||
|
class PostSaveRedirectTests(TestCase):
|
||||||
|
"""
|
||||||
|
Verifies that the views redirect to the correct locations depending on
|
||||||
|
if a post_save_redirect was passed and a get_absolute_url method exists
|
||||||
|
on the Model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
fixtures = ['testdata.json']
|
||||||
|
article_model = Article
|
||||||
|
|
||||||
|
create_url = '/views/create_update/create/article/'
|
||||||
|
update_url = '/views/create_update/update/article/old_article/'
|
||||||
|
delete_url = '/views/create_update/delete/article/old_article/'
|
||||||
|
|
||||||
|
create_redirect = '/views/create_update/view/article/my-first-article/'
|
||||||
|
update_redirect = '/views/create_update/view/article/another-article-slug/'
|
||||||
|
delete_redirect = '/views/create_update/'
|
||||||
|
|
||||||
|
def test_create_article(self):
|
||||||
|
num_articles = self.article_model.objects.count()
|
||||||
|
response = self.client.post(self.create_url, {
|
||||||
|
'title': 'My First Article',
|
||||||
|
'slug': 'my-first-article',
|
||||||
|
'author': '1',
|
||||||
|
'date_created': datetime.datetime(2007, 6, 25),
|
||||||
|
})
|
||||||
|
self.assertRedirects(response, self.create_redirect,
|
||||||
|
target_status_code=404)
|
||||||
|
self.assertEqual(num_articles + 1, self.article_model.objects.count(),
|
||||||
|
"A new Article should have been created.")
|
||||||
|
|
||||||
|
def test_update_article(self):
|
||||||
|
num_articles = self.article_model.objects.count()
|
||||||
|
response = self.client.post(self.update_url, {
|
||||||
|
'title': 'Another Article',
|
||||||
|
'slug': 'another-article-slug',
|
||||||
|
'author': 1,
|
||||||
|
'date_created': datetime.datetime(2007, 6, 25),
|
||||||
|
})
|
||||||
|
self.assertRedirects(response, self.update_redirect,
|
||||||
|
target_status_code=404)
|
||||||
|
self.assertEqual(num_articles, self.article_model.objects.count(),
|
||||||
|
"A new Article should not have been created.")
|
||||||
|
|
||||||
|
def test_delete_article(self):
|
||||||
|
num_articles = self.article_model.objects.count()
|
||||||
|
response = self.client.post(self.delete_url)
|
||||||
|
self.assertRedirects(response, self.delete_redirect,
|
||||||
|
target_status_code=404)
|
||||||
|
self.assertEqual(num_articles - 1, self.article_model.objects.count(),
|
||||||
|
"An Article should have been deleted.")
|
||||||
|
|
||||||
|
class NoPostSaveNoAbsoluteUrl(PostSaveRedirectTests):
|
||||||
|
"""
|
||||||
|
Tests that when no post_save_redirect is passed and no get_absolute_url
|
||||||
|
method exists on the Model that the view raises an ImproperlyConfigured
|
||||||
|
error.
|
||||||
|
"""
|
||||||
|
|
||||||
|
create_url = '/views/create_update/no_redirect/create/article/'
|
||||||
|
update_url = '/views/create_update/no_redirect/update/article/old_article/'
|
||||||
|
|
||||||
|
def test_create_article(self):
|
||||||
|
self.assertRaises(ImproperlyConfigured,
|
||||||
|
super(NoPostSaveNoAbsoluteUrl, self).test_create_article)
|
||||||
|
|
||||||
|
def test_update_article(self):
|
||||||
|
self.assertRaises(ImproperlyConfigured,
|
||||||
|
super(NoPostSaveNoAbsoluteUrl, self).test_update_article)
|
||||||
|
|
||||||
|
def test_delete_article(self):
|
||||||
|
"""
|
||||||
|
The delete_object view requires a post_delete_redirect, so skip testing
|
||||||
|
here.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AbsoluteUrlNoPostSave(PostSaveRedirectTests):
|
||||||
|
"""
|
||||||
|
Tests that the views redirect to the Model's get_absolute_url when no
|
||||||
|
post_save_redirect is passed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Article model with get_absolute_url method.
|
||||||
|
article_model = UrlArticle
|
||||||
|
|
||||||
|
create_url = '/views/create_update/no_url/create/article/'
|
||||||
|
update_url = '/views/create_update/no_url/update/article/old_article/'
|
||||||
|
|
||||||
|
create_redirect = '/urlarticles/my-first-article/'
|
||||||
|
update_redirect = '/urlarticles/another-article-slug/'
|
||||||
|
|
||||||
|
def test_delete_article(self):
|
||||||
|
"""
|
||||||
|
The delete_object view requires a post_delete_redirect, so skip testing
|
||||||
|
here.
|
||||||
|
"""
|
||||||
|
pass
|
|
@ -5,6 +5,7 @@ from django.conf.urls.defaults import *
|
||||||
from models import *
|
from models import *
|
||||||
import views
|
import views
|
||||||
|
|
||||||
|
|
||||||
base_dir = path.dirname(path.abspath(__file__))
|
base_dir = path.dirname(path.abspath(__file__))
|
||||||
media_dir = path.join(base_dir, 'media')
|
media_dir = path.join(base_dir, 'media')
|
||||||
locale_dir = path.join(base_dir, 'locale')
|
locale_dir = path.join(base_dir, 'locale')
|
||||||
|
@ -14,35 +15,66 @@ js_info_dict = {
|
||||||
'packages': ('regressiontests.views',),
|
'packages': ('regressiontests.views',),
|
||||||
}
|
}
|
||||||
|
|
||||||
date_based_info_dict = {
|
date_based_info_dict = {
|
||||||
'queryset': Article.objects.all(),
|
'queryset': Article.objects.all(),
|
||||||
'date_field': 'date_created',
|
'date_field': 'date_created',
|
||||||
'month_format': '%m',
|
'month_format': '%m',
|
||||||
}
|
}
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^$', views.index_page),
|
(r'^$', views.index_page),
|
||||||
|
|
||||||
# Default views
|
# Default views
|
||||||
(r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
|
(r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
|
||||||
(r'^non_existing_url/', 'django.views.defaults.page_not_found'),
|
(r'^non_existing_url/', 'django.views.defaults.page_not_found'),
|
||||||
(r'^server_error/', 'django.views.defaults.server_error'),
|
(r'^server_error/', 'django.views.defaults.server_error'),
|
||||||
|
|
||||||
# i18n views
|
# i18n views
|
||||||
(r'^i18n/', include('django.conf.urls.i18n')),
|
(r'^i18n/', include('django.conf.urls.i18n')),
|
||||||
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
|
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
|
||||||
|
|
||||||
# Static views
|
# Static views
|
||||||
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
|
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
|
||||||
|
)
|
||||||
# Date-based generic views
|
|
||||||
(r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
|
# Date-based generic views.
|
||||||
'django.views.generic.date_based.object_detail',
|
urlpatterns += patterns('django.views.generic.date_based',
|
||||||
dict(slug_field='slug', **date_based_info_dict)),
|
(r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
|
||||||
(r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/allow_future/$',
|
'object_detail',
|
||||||
'django.views.generic.date_based.object_detail',
|
dict(slug_field='slug', **date_based_info_dict)),
|
||||||
dict(allow_future=True, slug_field='slug', **date_based_info_dict)),
|
(r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/allow_future/$',
|
||||||
(r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
|
'object_detail',
|
||||||
'django.views.generic.date_based.archive_month',
|
dict(allow_future=True, slug_field='slug', **date_based_info_dict)),
|
||||||
date_based_info_dict),
|
(r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
|
||||||
|
'archive_month',
|
||||||
|
date_based_info_dict),
|
||||||
|
)
|
||||||
|
|
||||||
|
# crud generic views.
|
||||||
|
|
||||||
|
urlpatterns += patterns('django.views.generic.create_update',
|
||||||
|
(r'^create_update/member/create/article/$', 'create_object',
|
||||||
|
dict(login_required=True, model=Article)),
|
||||||
|
(r'^create_update/create/article/$', 'create_object',
|
||||||
|
dict(post_save_redirect='/views/create_update/view/article/%(slug)s/',
|
||||||
|
model=Article)),
|
||||||
|
(r'^create_update/update/article/(?P<slug>[-\w]+)/$', 'update_object',
|
||||||
|
dict(post_save_redirect='/views/create_update/view/article/%(slug)s/',
|
||||||
|
slug_field='slug', model=Article)),
|
||||||
|
(r'^create_update/create_custom/article/$', views.custom_create),
|
||||||
|
(r'^create_update/delete/article/(?P<slug>[-\w]+)/$', 'delete_object',
|
||||||
|
dict(post_delete_redirect='/views/create_update/', slug_field='slug',
|
||||||
|
model=Article)),
|
||||||
|
|
||||||
|
# No post_save_redirect and no get_absolute_url on model.
|
||||||
|
(r'^create_update/no_redirect/create/article/$', 'create_object',
|
||||||
|
dict(model=Article)),
|
||||||
|
(r'^create_update/no_redirect/update/article/(?P<slug>[-\w]+)/$',
|
||||||
|
'update_object', dict(slug_field='slug', model=Article)),
|
||||||
|
|
||||||
|
# get_absolute_url on model, but no passed post_save_redirect.
|
||||||
|
(r'^create_update/no_url/create/article/$', 'create_object',
|
||||||
|
dict(model=UrlArticle)),
|
||||||
|
(r'^create_update/no_url/update/article/(?P<slug>[-\w]+)/$',
|
||||||
|
'update_object', dict(slug_field='slug', model=UrlArticle)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,29 @@
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
import django.newforms as forms
|
||||||
|
from django.views.generic.create_update import create_object
|
||||||
|
|
||||||
|
from models import Article
|
||||||
|
|
||||||
|
|
||||||
def index_page(request):
|
def index_page(request):
|
||||||
"""Dummy index page"""
|
"""Dummy index page"""
|
||||||
return HttpResponse('<html><body>Dummy page</body></html>')
|
return HttpResponse('<html><body>Dummy page</body></html>')
|
||||||
|
|
||||||
|
|
||||||
|
def custom_create(request):
|
||||||
|
"""
|
||||||
|
Calls create_object generic view with a custom form class.
|
||||||
|
"""
|
||||||
|
class SlugChangingArticleForm(forms.ModelForm):
|
||||||
|
"""Custom form class to overwrite the slug."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Article
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.cleaned_data['slug'] = 'some-other-slug'
|
||||||
|
return super(SlugChangingArticleForm, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
return create_object(request,
|
||||||
|
post_save_redirect='/views/create_update/view/article/%(slug)s/',
|
||||||
|
form_class=SlugChangingArticleForm)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
This template intentionally left blank
|
|
@ -1 +1 @@
|
||||||
This template intentionally left blank
|
Article detail template.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Article form template.
|
||||||
|
|
||||||
|
{{ form.errors }}
|
|
@ -0,0 +1 @@
|
||||||
|
UrlArticle detail template.
|
|
@ -0,0 +1,3 @@
|
||||||
|
UrlArticle form template.
|
||||||
|
|
||||||
|
{{ form.errors }}
|
Loading…
Reference in New Issue