A bunch of generics: documentation of generic views; cleaned up existing generic views, and added create/update generic views.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@304 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jacob Kaplan-Moss 2005-07-24 22:21:09 +00:00
parent 8bd30b01e4
commit 371144f134
4 changed files with 508 additions and 8 deletions

View File

@ -0,0 +1,185 @@
from django import models
from django.core.xheaders import populate_xheaders
from django.core import template_loader, formfields
from django.views.auth.login import redirect_to_login
from django.core.extensions import DjangoContext as Context
from django.core.paginator import ObjectPaginator, InvalidPage
from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
from django.core.exceptions import Http404, ObjectDoesNotExist, ImproperlyConfigured
def create_object(request, app_label, module_name, template_name=None,
extra_context=None, post_save_redirect=None, login_required=False):
"""
Generic object-creation function.
Templates: ``<app_label>/<module_name>_form``
Context:
form
the form wrapper for the object
"""
if login_required and request.user.is_anonymous():
return redirect_to_login(request)
mod = models.get_module(app_label, module_name)
manipulator = mod.AddManipulator()
if request.POST:
# If data was POSTed, we're trying to create a new object
new_data = request.POST.copy()
# Check for errors
errors = manipulator.get_validation_errors(new_data)
if not errors:
# No errors -- this means we can save the data!
manipulator.do_html2python(new_data)
new_object = manipulator.save(new_data)
if not request.user.is_anonymous():
request.user.add_message("The %s was created sucessfully." % mod.Klass._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 = {}
# Create the FormWrapper, template, context, response
form = formfields.FormWrapper(manipulator, new_data, errors)
if not template_name:
template_name = "%s/%s_form" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = Context(request, {
'form' : form,
})
if extra_context:
c.update(extra_context)
return HttpResponse(t.render(c))
def update_object(request, app_label, module_name, object_id=None, slug=None,
slug_field=None, template_name=None, extra_lookup_kwargs={},
extra_context=None, post_save_redirect=None, login_required=False):
"""
Generic object-update function.
Templates: ``<app_label>/<module_name>_form``
Context:
form
the form wrapper for the object
object
the original object being edited
"""
if login_required and request.user.is_anonymous():
return redirect_to_login(request)
mod = models.get_module(app_label, module_name)
# Look up the object to be edited
lookup_kwargs = {}
if object_id:
lookup_kwargs['%s__exact' % mod.Klass._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")
lookup_kwargs.update(extra_lookup_kwargs)
try:
object = mod.get_object(**lookup_kwargs)
except ObjectDoesNotExist:
raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
manipulator = mod.ChangeManipulator(object.id)
if request.POST:
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
if not errors:
manipulator.do_html2python(new_data)
manipulator.save(new_data)
if not request.user.is_anonymous():
request.user.add_message("The %s was updated sucessfully." % mod.Klass._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.")
else:
errors = {}
# This makes sure the form acurate represents the fields of the place.
new_data = object.__dict__
form = formfields.FormWrapper(manipulator, new_data, errors)
if not template_name:
template_name = "%s/%s_form" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = Context(request, {
'form' : form,
'object' : object,
})
if extra_context:
c.update(extra_context)
response = HttpResponse(t.render(c))
populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
return response
def delete_object(request, app_label, module_name, post_delete_redirect,
object_id=None, slug=None, slug_field=None, template_name=None,
extra_lookup_kwargs={}, extra_context=None, login_required=False):
"""
Generic object-delete function.
The given template will be used to confirm deletetion if this view is
fetched using GET; for safty, deletion will only be performed if this
view is POSTed.
Templates: ``<app_label>/<module_name>_confirm_delete``
Context:
object
the original object being deleted
"""
if login_required and request.user.is_anonymous():
return redirect_to_login(request)
mod = models.get_module(app_label, module_name)
# Look up the object to be edited
lookup_kwargs = {}
if object_id:
lookup_kwargs['%s__exact' % mod.Klass._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")
lookup_kwargs.update(extra_lookup_kwargs)
try:
object = mod.get_object(**lookup_kwargs)
except ObjectDoesNotExist:
raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
if request.META['REQUEST_METHOD'] == 'POST':
object.delete()
if not request.user.is_anonymous():
request.user.add_message("The %s was deleted." % mod.Klass._meta.verbose_name)
return HttpResponseRedirect(post_delete_redirect)
else:
if not template_name:
template_name = "%s/%s_confirm_delete" % (app_label, module_name)
t = template_loader.get_template(template_name)
c = Context(request, {
'object' : object,
})
if extra_context:
c.update(extra_context)
response = HttpResponse(t.render(c))
populate_xheaders(request, response, app_label, module_name, getattr(object, object._meta.pk.name))
return response

View File

@ -6,7 +6,8 @@ from django.models import get_module
from django.utils.httpwrappers import HttpResponse
import datetime, time
def archive_index(request, app_label, module_name, date_field, num_latest=15, template_name=None, extra_lookup_kwargs={}, extra_context=None):
def archive_index(request, app_label, module_name, date_field, num_latest=15,
template_name=None, extra_lookup_kwargs={}, extra_context=None):
"""
Generic top-level archive of date-based objects.
@ -44,7 +45,8 @@ def archive_index(request, app_label, module_name, date_field, num_latest=15, te
c.update(extra_context)
return HttpResponse(t.render(c))
def archive_year(request, year, app_label, module_name, date_field, template_name=None, extra_lookup_kwargs={}, extra_context=None):
def archive_year(request, year, app_label, module_name, date_field,
template_name=None, extra_lookup_kwargs={}, extra_context=None):
"""
Generic yearly archive view.
@ -76,7 +78,8 @@ def archive_year(request, year, app_label, module_name, date_field, template_nam
c.update(extra_context)
return HttpResponse(t.render(c))
def archive_month(request, year, month, app_label, module_name, date_field, template_name=None, extra_lookup_kwargs={}, extra_context=None):
def archive_month(request, year, month, app_label, module_name, date_field,
template_name=None, extra_lookup_kwargs={}, extra_context=None):
"""
Generic monthly archive view.
@ -122,7 +125,9 @@ def archive_month(request, year, month, app_label, module_name, date_field, temp
c.update(extra_context)
return HttpResponse(t.render(c))
def archive_day(request, year, month, day, app_label, module_name, date_field, template_name=None, extra_lookup_kwargs={}, extra_context=None, allow_empty=False):
def archive_day(request, year, month, day, app_label, module_name, date_field,
template_name=None, extra_lookup_kwargs={}, extra_context=None,
allow_empty=False):
"""
Generic daily archive view.
@ -178,7 +183,9 @@ def archive_today(request, **kwargs):
})
return archive_day(request, **kwargs)
def object_detail(request, year, month, day, app_label, module_name, date_field, object_id=None, slug=None, slug_field=None, template_name=None, extra_lookup_kwargs={}, extra_context=None):
def object_detail(request, year, month, day, app_label, module_name, date_field,
object_id=None, slug=None, slug_field=None, template_name=None,
template_name_field=None, extra_lookup_kwargs={}, extra_context=None):
"""
Generic detail view from year/month/day/slug or year/month/day/id structure.
@ -212,7 +219,11 @@ def object_detail(request, year, month, day, app_label, module_name, date_field,
raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
if not template_name:
template_name = "%s/%s_detail" % (app_label, module_name)
t = template_loader.get_template(template_name)
if template_name_field:
template_name_list = [getattr(object, template_name_field), template_name]
t = template_loader.select_template(template_name_list)
else:
t = template_loader.get_template(template_name)
c = Context(request, {
'object': object,
})

View File

@ -6,7 +6,8 @@ from django.core.extensions import DjangoContext as Context
from django.core.paginator import ObjectPaginator, InvalidPage
from django.core.exceptions import Http404, ObjectDoesNotExist
def object_list(request, app_label, module_name, paginate_by=None, allow_empty=False, template_name=None, extra_lookup_kwargs={}, extra_context=None):
def object_list(request, app_label, module_name, paginate_by=None, allow_empty=False,
template_name=None, extra_lookup_kwargs={}, extra_context=None):
"""
Generic list of objects.
@ -67,7 +68,9 @@ def object_list(request, app_label, module_name, paginate_by=None, allow_empty=F
t = template_loader.get_template(template_name)
return HttpResponse(t.render(c))
def object_detail(request, app_label, module_name, object_id=None, slug=None, slug_field=None, template_name=None, template_name_field=None, extra_lookup_kwargs={}, extra_context=None):
def object_detail(request, app_label, module_name, object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None,
extra_lookup_kwargs={}, extra_context=None):
"""
Generic list of objects.

301
docs/generic_views.txt Normal file
View File

@ -0,0 +1,301 @@
===================
Using generic views
===================
Writing web applications can often be monotonous as we repeat certain patterns
again and again. In Django, the most common of these patterns have been abstracted into
"generic views" that let you quickly provide common views of object without actually
needing to write any views.
Django's generic views contain the following:
* A set of views for doing list/detail interfaces (for example,
Django's `documentation index`_ and `detail pages`_).
* A set of views for year/month/day archive pages and associated
detail and "latest" pages (for example, the Django weblog's year_,
month_, day_, detail_, and latest_ pages).
* A set of views for creating, editing, and deleting objects.
.. _`documentation index`: http://www.djangoproject.com/documentation/
.. _`detail pages`: http://www.djangoproject.com/documentation/faq/
.. _year: http://www.djangoproject.com/weblog/2005/
.. _month: http://www.djangoproject.com/weblog/2005/jul/
.. _day: http://www.djangoproject.com/weblog/2005/jul/20/
.. _detail: http://www.djangoproject.com/weblog/2005/jul/20/autoreload/
.. _latest: http://www.djangoproject.com/weblog/
All of these views are used by creating configuration dictionaries in
your urlconfig files and passing those dicts as the third member of the
urlconf tuple. For example, here's the urlconf for the simple weblog
app that drives the blog on djangoproject.com::
from django.conf.urls.defaults import *
info_dict = {
'app_label': 'blog',
'module_name': 'entries',
'date_field': 'pub_date',
}
urlpatterns = patterns('django.views.generic.date_based',
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>\w+)/$', 'object_detail', dict(info_dict, slug_field='slug')),
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', info_dict),
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', info_dict),
(r'^(?P<year>\d{4})/$', 'archive_year', info_dict),
(r'^/?$', 'archive_index', info_dict),
)
As you can see, this urlconf defines a few options in ``info_dict`` that tell
the generic view which model to use (``blog.entries`` in this case), as well as
some extra information.
Documentation of each generic view follows along with a list of all keyword arguments
that a generic view expects. Remember that as in the example above, arguments may
either come from the URL pattern (as ``month``, ``day``, ``year``, etc. do above) or
from the additional information dict (as for ``app_label``, ``module_name``, etc.).
All the generic views that follow require the ``app_label`` and ``module_name`` keys.
These values are easiest to explain through example::
>>> from django.models.blog import entries
In the above line, ``blog`` is the ``app_label`` (this is the name of the file that
holds all your model definitions) and ``entries`` is the ``module_name`` (this is
either a pluralized, lowercased version of the model class name or the value of
the ``module_name`` option of your model). In the docs below, these keys will not
be repeated, but each generic view requires them.
Using date-based generic views
==============================
Date-based generic views (in the module ``django.views.generic.date_based``)
export six functions for dealing with date-based data. Besides ``app_label``
and ``module_name``, all date-based generic views require that the ``date_field``
argument to passed to them; this is the name of the field that stores the date
the objects should key off of.
Additional, all date-based generic views have the following optional arguments:
======================= ==================================================
Argument Description
======================= ==================================================
``template_name`` Override the default template name used for the
view.
``extra_lookup_kwargs`` A dictionary of extra lookup parameters (see
the `database API docs`_).
``extra_context`` A dict of extra data to put into the template's
context.
======================= ==================================================
.. _`database API docs`: http://www.djangoproject.com/documentation/db_api/
The date-based generic functions are:
``archive_index``
A top-level index page showing the "latest" objects. Has an optional argument,
``num_latest`` which is the number of items to display on the page (defaults
to 15).
Uses the template ``app_label/module_name_archive`` by default.
Has the following template context:
``date_list``
List of years with objects
``latest``
Latest objects by date
``archive_year``
Yearly archive. Requires that the ``year`` argument be present in the URL
pattern.
Uses the template ``app_label/module_name__archive_year`` by default.
Has the following template context:
``date_list``
List of months in this year with objects
``year``
This year
``archive_month``
Monthly archive; requires that ``year`` and ``month`` arguments be given.
Uses the template ``app_label/module_name__archive_month`` by default.
Has the following template context:
``month``
(datetime object) this month
``object_list``
list of objects published in the given month
``archive_day``
Daily archive; requires that ``year``, ``month``, and ``day`` arguments
be given.
Uses the template ``app_label/module_name__archive_day`` by default.
Has the following template context:
``object_list``
list of objects published this day
``day``
(datetime) the day
``previous_day``
(datetime) the previous day
``next_day``
(datetime) the next day, or None if the current day is today
``archive_today``
List of objects for today; exactly the same as ``archive_day``, except
that the year/month/day arguments are not given and today's date is
used instead.
``object_detail``
Individual object page; requires ``year``/``month``/``day`` arguments like
``archive_day``. This function can be used with two types of URLs: either
``/year/month/day/slug/`` or ``/year/month/day/object_id/``.
If you're using the slug-style URLs, you'll need to have a ``slug`` item in
your urlconf, and you'll need to pass a ``slug_field`` key in your info
dict to indicate the name of the slug field.
If your using the object_id-style URLs, you'll just need to have the URL
pattern have an ``object_id`` field.
You can also pass the ``template_name_field`` argument to indicate that the
the object stores the name of its template in a field on the object itself.
Using list/detail generic views
===============================
The list-detail generic views (in the ``django.views.generic.list_detail`` module)
are similar to the data-based ones, except the list-detail views simply have two
views: a list of objects, and an individual object page.
All these views take the same three optional arguments as the date-based ones do
(and they obviously do not accept or require the date field argument).
Individual views are:
``object_list``
List of objects.
Takes the following optional arguments:
======================= =================================================
Argument Description
======================= =================================================
``paginate_by`` If set to an integer, the view will paginate
objects with ``paginate_by`` objects per page.
The view will expect a ``page`` GET param with
the (zero-indexed) page number.
``allow_empty`` If ``False`` and there are no objects to display
the view will raise a 404 instead of displaying
an empty index page.
======================= =================================================
Uses the template ``app_label/module_name__list`` by default.
Has the following template context:
``object_list``
list of objects
``is_paginated``
are the results paginated?
If the results are paginated, the context will have some extra variables:
``results_per_page``
number of objects per page
``has_next``
is there a next page?
``has_previous``
is there a prev page?
``page``
the current page
``next``
the next page
``previous``
the previous page
``pages``
number of pages, total
``object_detail``
Object detail page. This works like and takes the same arguments as
the date-based ``object_detail`` above, except this one obviously
does not take the year/month/day arguments.
Using create/update/delete generic views
========================================
The ``django.views.generic.create_update`` module contains a set of functions
for creating, editing, and deleting objects. These views take the same global
arguments as the above sets of generic views; they also have a
``login_required`` argument which, if ``True``, requires the user to be logged
in to have access to the page (``login_required`` defaults to ``False``).
The create/update/delete views are:
``create_object``
Create a new object. Has an extra optional argument, ``post_save_redirect``,
which is a URL that the view will redirect to after saving the object
(defaults to ``object.get_absolute_url()``).
``post_save_redirect`` may contain dictionary string formatting which will
be interpolated against the object's dict (so you could use
``post_save_redirect="/polls/%(slug)s/"``, for example).
Uses the template ``app_label/module_name__form`` by default (this is the
same template as the ``update_object`` view below; your template can tell
the different by the presence or absence of ``{{ object }}`` in the context.
Has the following template context:
form
the form wrapper for the object
.. admonition:: Note
See the `manipulator and formfield documentation`_ for more information
about using form wrappers in templates.
.. _`manipulator and formfield documentation`: http://www.djangoproject.com/documentation/forms/
``update_object``
Edit an existing object. Has the same extra slug/ID parameters as
``list_detail.object_detail`` does (see above), and the same ``post_save_redirect``
as ``create_object`` does.
Uses the template ``app_label/module_name__form`` by default.
Has the following template context:
form
the form wrapper for the object
object
the original object being edited
``delete_object``
Delete an existing object. The given object will only actually be deleted if
the request method is POST; if this view is fetched with GET it will display
a confirmation page that should contain a form that POSTs to the same URL.
You must provide the ``post_delete_redirect`` argument to this function so
that the view knows where to go after the object is deleted.
If fetched with GET, uses the template
``app_label/module_name_s_confirm_delete`` by default (uses no template if
POSTed; simply deletes the object).
Has the following template context:
object
the object about to be deleted