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.template import 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.newforms.models import ModelFormMetaclass, ModelForm
|
||||
from django.template import RequestContext, loader
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.core.xheaders import populate_xheaders
|
||||
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
||||
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,
|
||||
template_loader=loader, extra_context=None, post_save_redirect=None,
|
||||
login_required=False, follow=None, context_processors=None):
|
||||
def deprecate_follow(follow):
|
||||
"""
|
||||
Generic object-creation function.
|
||||
Issues a DeprecationWarning if follow is anything but None.
|
||||
|
||||
Templates: ``<app_label>/<model_name>_form.html``
|
||||
Context:
|
||||
form
|
||||
the form wrapper for the object
|
||||
The old Manipulator-based forms used a follow argument that is no longer
|
||||
needed for newforms-based forms.
|
||||
"""
|
||||
if extra_context is None: extra_context = {}
|
||||
if login_required and not request.user.is_authenticated():
|
||||
return redirect_to_login(request.path)
|
||||
if follow is not None:
|
||||
import warning
|
||||
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)
|
||||
if request.POST:
|
||||
# If data was POSTed, we're trying to create a new object
|
||||
new_data = request.POST.copy()
|
||||
|
||||
if model._meta.has_field_type(FileField):
|
||||
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():
|
||||
def apply_extra_context(extra_context, context):
|
||||
"""
|
||||
Adds items from extra_context dict to context. If a value in extra_context
|
||||
is callable, then it is called and the result is added to context.
|
||||
"""
|
||||
for key, value in extra_context.iteritems():
|
||||
if callable(value):
|
||||
c[key] = value()
|
||||
context[key] = value()
|
||||
else:
|
||||
c[key] = value
|
||||
return HttpResponse(t.render(c))
|
||||
context[key] = value
|
||||
|
||||
def update_object(request, model, object_id=None, slug=None,
|
||||
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'):
|
||||
def get_model_and_form_class(model, form_class):
|
||||
"""
|
||||
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``
|
||||
Context:
|
||||
form
|
||||
the form wrapper for the object
|
||||
object
|
||||
the original object being edited
|
||||
If ``form_class`` is given then its associated model will be returned along
|
||||
with ``form_class`` itself. Otherwise, if ``model`` is given, ``model``
|
||||
itself will be returned along with a ``ModelForm`` class created from
|
||||
``model``.
|
||||
"""
|
||||
if extra_context is None: extra_context = {}
|
||||
if login_required and not request.user.is_authenticated():
|
||||
return redirect_to_login(request.path)
|
||||
if form_class:
|
||||
return form_class._meta.model, form_class
|
||||
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 = {}
|
||||
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 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:
|
||||
object = model.objects.get(**lookup_kwargs)
|
||||
return model.objects.get(**lookup_kwargs)
|
||||
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:
|
||||
new_data = request.POST.copy()
|
||||
if model._meta.has_field_type(FileField):
|
||||
new_data.update(request.FILES)
|
||||
errors = manipulator.get_validation_errors(new_data)
|
||||
manipulator.do_html2python(new_data)
|
||||
if not errors:
|
||||
object = manipulator.save(new_data)
|
||||
Templates: ``<app_label>/<model_name>_form.html``
|
||||
Context:
|
||||
form
|
||||
the form for the object
|
||||
"""
|
||||
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)
|
||||
if request.method == 'POST':
|
||||
form = form_class(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
new_object = 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})
|
||||
|
||||
# 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.")
|
||||
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)
|
||||
else:
|
||||
errors = {}
|
||||
# This makes sure the form acurate represents the fields of the place.
|
||||
new_data = manipulator.flatten_data()
|
||||
form = form_class()
|
||||
|
||||
form = oldforms.FormWrapper(manipulator, new_data, errors)
|
||||
# Create the template, context, response
|
||||
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: object,
|
||||
}, context_processors)
|
||||
for key, value in extra_context.items():
|
||||
if callable(value):
|
||||
c[key] = value()
|
||||
else:
|
||||
c[key] = value
|
||||
apply_extra_context(extra_context, c)
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
def update_object(request, model=None, object_id=None, slug=None,
|
||||
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))
|
||||
populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
|
||||
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
|
||||
return response
|
||||
|
||||
def delete_object(request, model, post_delete_redirect,
|
||||
object_id=None, slug=None, slug_field='slug', template_name=None,
|
||||
template_loader=loader, extra_context=None,
|
||||
login_required=False, context_processors=None, template_object_name='object'):
|
||||
def delete_object(request, model, post_delete_redirect, object_id=None,
|
||||
slug=None, slug_field='slug', template_name=None,
|
||||
template_loader=loader, extra_context=None, login_required=False,
|
||||
context_processors=None, template_object_name='object'):
|
||||
"""
|
||||
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():
|
||||
return redirect_to_login(request.path)
|
||||
|
||||
# Look up the object to be edited
|
||||
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)
|
||||
obj = lookup_object(model, object_id, slug, slug_field)
|
||||
|
||||
if request.method == 'POST':
|
||||
object.delete()
|
||||
obj.delete()
|
||||
if request.user.is_authenticated():
|
||||
request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
|
||||
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())
|
||||
t = template_loader.get_template(template_name)
|
||||
c = RequestContext(request, {
|
||||
template_object_name: object,
|
||||
template_object_name: obj,
|
||||
}, context_processors)
|
||||
for key, value in extra_context.items():
|
||||
if callable(value):
|
||||
c[key] = value()
|
||||
else:
|
||||
c[key] = value
|
||||
apply_extra_context(extra_context, 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
|
||||
|
|
|
@ -701,7 +701,7 @@ A page representing a list of objects.
|
|||
query string parameter (via ``GET``) or a ``page`` variable specified in
|
||||
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.
|
||||
|
||||
* ``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
|
||||
|
||||
* To loop over all the available page numbers, use the ``page_range``
|
||||
variable. You can iterate over the list provided by ``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``
|
||||
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
|
||||
represented as page ``1``.
|
||||
represented as page ``1``.
|
||||
|
||||
For more on pagination, read the `pagination documentation`_.
|
||||
|
||||
.. _`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
|
||||
``page``::
|
||||
|
||||
/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.
|
||||
|
||||
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
|
||||
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``
|
||||
----------------------------------------------------
|
||||
|
||||
**Description:**
|
||||
|
||||
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
|
||||
automatic manipulators that come with Django models.
|
||||
validation errors (if there are any) and saving the object.
|
||||
|
||||
**Required arguments:**
|
||||
|
||||
* ``model``: The Django model class of the object that the form will
|
||||
create.
|
||||
* Either ``form_class`` or ``model`` is required.
|
||||
|
||||
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:**
|
||||
|
||||
|
@ -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:
|
||||
|
||||
* ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
|
||||
for editing the object. This lets you refer to form fields easily in the
|
||||
* ``form``: A ``django.newforms.ModelForm`` instance representing the form
|
||||
for creating the object. This lets you refer to form fields easily in the
|
||||
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">
|
||||
<p><label for="id_name">Name:</label> {{ form.name }}</p>
|
||||
<p><label for="id_address">Address:</label> {{ form.address }}</p>
|
||||
<p>{{ form.name.label_tag }} {{ form.name }}</p>
|
||||
<p>{{ form.address.label_tag }} {{ form.address }}</p>
|
||||
</form>
|
||||
|
||||
See the `manipulator and formfield documentation`_ for more information
|
||||
about using ``FormWrapper`` objects in templates.
|
||||
See the `newforms documentation`_ for more information about using
|
||||
``Form`` objects in templates.
|
||||
|
||||
.. _authentication system: ../authentication/
|
||||
.. _manipulator and formfield documentation: ../forms/
|
||||
.. _ModelForm docs: ../newforms/modelforms
|
||||
.. _newforms documentation: ../newforms/
|
||||
|
||||
``django.views.generic.create_update.update_object``
|
||||
----------------------------------------------------
|
||||
|
@ -987,8 +1002,15 @@ object. This uses the automatic manipulators that come with Django models.
|
|||
|
||||
**Required arguments:**
|
||||
|
||||
* ``model``: The Django model class of the object that the form will
|
||||
create.
|
||||
* Either ``form_class`` or ``model`` is required.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
* ``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
|
||||
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">
|
||||
<p><label for="id_name">Name:</label> {{ form.name }}</p>
|
||||
<p><label for="id_address">Address:</label> {{ form.address }}</p>
|
||||
<p>{{ form.name.label_tag }} {{ form.name }}</p>
|
||||
<p>{{ form.address.label_tag }} {{ form.address }}</p>
|
||||
</form>
|
||||
|
||||
See the `manipulator and formfield documentation`_ for more information
|
||||
about using ``FormWrapper`` objects in templates.
|
||||
See the `newforms documentation`_ for more information about using
|
||||
``Form`` objects in templates.
|
||||
|
||||
* ``object``: The original object being edited. This variable's name
|
||||
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,
|
||||
"model": "views.article",
|
||||
|
@ -29,7 +47,16 @@
|
|||
"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,
|
||||
"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.conf import settings
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
@ -14,13 +13,28 @@ class Author(models.Model):
|
|||
def get_absolute_url(self):
|
||||
return '/views/authors/%s/' % self.id
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
class BaseArticle(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)
|
||||
slug = models.SlugField()
|
||||
author = models.ForeignKey(Author)
|
||||
date_created = models.DateTimeField()
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __unicode__(self):
|
||||
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 i18n 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 *
|
||||
import views
|
||||
|
||||
|
||||
base_dir = path.dirname(path.abspath(__file__))
|
||||
media_dir = path.join(base_dir, 'media')
|
||||
locale_dir = path.join(base_dir, 'locale')
|
||||
|
@ -14,35 +15,66 @@ js_info_dict = {
|
|||
'packages': ('regressiontests.views',),
|
||||
}
|
||||
|
||||
date_based_info_dict = {
|
||||
'queryset': Article.objects.all(),
|
||||
'date_field': 'date_created',
|
||||
'month_format': '%m',
|
||||
}
|
||||
date_based_info_dict = {
|
||||
'queryset': Article.objects.all(),
|
||||
'date_field': 'date_created',
|
||||
'month_format': '%m',
|
||||
}
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', views.index_page),
|
||||
|
||||
|
||||
# Default views
|
||||
(r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
|
||||
(r'^non_existing_url/', 'django.views.defaults.page_not_found'),
|
||||
(r'^server_error/', 'django.views.defaults.server_error'),
|
||||
|
||||
|
||||
# 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),
|
||||
|
||||
|
||||
# Static views
|
||||
(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]+)/$',
|
||||
'django.views.generic.date_based.object_detail',
|
||||
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]+)/allow_future/$',
|
||||
'django.views.generic.date_based.object_detail',
|
||||
dict(allow_future=True, slug_field='slug', **date_based_info_dict)),
|
||||
(r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
|
||||
'django.views.generic.date_based.archive_month',
|
||||
date_based_info_dict),
|
||||
)
|
||||
|
||||
# Date-based generic views.
|
||||
urlpatterns += patterns('django.views.generic.date_based',
|
||||
(r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
|
||||
'object_detail',
|
||||
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]+)/allow_future/$',
|
||||
'object_detail',
|
||||
dict(allow_future=True, slug_field='slug', **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
|
||||
import django.newforms as forms
|
||||
from django.views.generic.create_update import create_object
|
||||
|
||||
from models import Article
|
||||
|
||||
|
||||
def index_page(request):
|
||||
"""Dummy index page"""
|
||||
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