Fixed #18033 -- Removed function-based generic views, as per official deprecation timeline. Rest in peace! Thanks Anssi Kääriäinen for the review.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17937 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Claude Paroz 2012-04-25 19:17:47 +00:00
parent ea9dc9f4b0
commit 1858e47672
38 changed files with 46 additions and 3335 deletions

View File

@ -1,221 +0,0 @@
from django.forms.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
from django.contrib import messages
import warnings
warnings.warn(
'Function-based generic views have been deprecated; use class-based views instead.',
DeprecationWarning
)
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):
context[key] = value()
else:
context[key] = value
def get_model_and_form_class(model, form_class):
"""
Returns a model and form class based on the model and form_class
parameters that were passed to the generic view.
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 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.")
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 function 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 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 GenericViewError(
"Generic view must be called with either an object_id or a"
" slug/slug_field.")
try:
return model.objects.get(**lookup_kwargs)
except ObjectDoesNotExist:
raise Http404("No %s found for %s"
% (model._meta.verbose_name, lookup_kwargs))
def create_object(request, model=None, template_name=None,
template_loader=loader, extra_context=None, post_save_redirect=None,
login_required=False, context_processors=None, form_class=None):
"""
Generic object-creation function.
Templates: ``<app_label>/<model_name>_form.html``
Context:
form
the form for the object
"""
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()
msg = ugettext("The %(verbose_name)s was created successfully.") %\
{"verbose_name": model._meta.verbose_name}
messages.success(request, msg, fail_silently=True)
return redirect(post_save_redirect, new_object)
else:
form = form_class()
# 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,
}, context_processors)
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,
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
"""
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()
msg = ugettext("The %(verbose_name)s was updated successfully.") %\
{"verbose_name": model._meta.verbose_name}
messages.success(request, msg, fail_silently=True)
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(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'):
"""
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>/<model_name>_confirm_delete.html``
Context:
object
the original object being deleted
"""
if extra_context is None: extra_context = {}
if login_required and not request.user.is_authenticated():
return redirect_to_login(request.path)
obj = lookup_object(model, object_id, slug, slug_field)
if request.method == 'POST':
obj.delete()
msg = ugettext("The %(verbose_name)s was deleted.") %\
{"verbose_name": model._meta.verbose_name}
messages.success(request, msg, fail_silently=True)
return HttpResponseRedirect(post_delete_redirect)
else:
if not template_name:
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: obj,
}, context_processors)
apply_extra_context(extra_context, c)
response = HttpResponse(t.render(c))
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
return response

View File

@ -1,376 +0,0 @@
import datetime
import time
from django.template import loader, RequestContext
from django.core.exceptions import ObjectDoesNotExist
from django.core.xheaders import populate_xheaders
from django.db.models.fields import DateTimeField
from django.http import Http404, HttpResponse
from django.utils import timezone
import warnings
warnings.warn(
'Function-based generic views have been deprecated; use class-based views instead.',
DeprecationWarning
)
def archive_index(request, queryset, date_field, num_latest=15,
template_name=None, template_loader=loader,
extra_context=None, allow_empty=True, context_processors=None,
mimetype=None, allow_future=False, template_object_name='latest'):
"""
Generic top-level archive of date-based objects.
Templates: ``<app_label>/<model_name>_archive.html``
Context:
date_list
List of years
latest
Latest N (defaults to 15) objects by date
"""
if extra_context is None: extra_context = {}
model = queryset.model
if not allow_future:
queryset = queryset.filter(**{'%s__lte' % date_field: timezone.now()})
date_list = queryset.dates(date_field, 'year')[::-1]
if not date_list and not allow_empty:
raise Http404("No %s available" % model._meta.verbose_name)
if date_list and num_latest:
latest = queryset.order_by('-'+date_field)[:num_latest]
else:
latest = None
if not template_name:
template_name = "%s/%s_archive.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'date_list' : date_list,
template_object_name : latest,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
return HttpResponse(t.render(c), mimetype=mimetype)
def archive_year(request, year, queryset, date_field, template_name=None,
template_loader=loader, extra_context=None, allow_empty=False,
context_processors=None, template_object_name='object', mimetype=None,
make_object_list=False, allow_future=False):
"""
Generic yearly archive view.
Templates: ``<app_label>/<model_name>_archive_year.html``
Context:
date_list
List of months in this year with objects
year
This year
object_list
List of objects published in the given month
(Only available if make_object_list argument is True)
"""
if extra_context is None: extra_context = {}
model = queryset.model
now = timezone.now()
lookup_kwargs = {'%s__year' % date_field: year}
# Only bother to check current date if the year isn't in the past and future objects aren't requested.
if int(year) >= now.year and not allow_future:
lookup_kwargs['%s__lte' % date_field] = now
date_list = queryset.filter(**lookup_kwargs).dates(date_field, 'month')
if not date_list and not allow_empty:
raise Http404
if make_object_list:
object_list = queryset.filter(**lookup_kwargs)
else:
object_list = []
if not template_name:
template_name = "%s/%s_archive_year.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'date_list': date_list,
'year': year,
'%s_list' % template_object_name: object_list,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
return HttpResponse(t.render(c), mimetype=mimetype)
def archive_month(request, year, month, queryset, date_field,
month_format='%b', template_name=None, template_loader=loader,
extra_context=None, allow_empty=False, context_processors=None,
template_object_name='object', mimetype=None, allow_future=False):
"""
Generic monthly archive view.
Templates: ``<app_label>/<model_name>_archive_month.html``
Context:
date_list:
List of days in this month with objects
month:
(date) this month
next_month:
(date) the first day of the next month, or None if the next month is in the future
previous_month:
(date) the first day of the previous month
object_list:
list of objects published in the given month
"""
if extra_context is None: extra_context = {}
try:
tt = time.strptime("%s-%s" % (year, month), '%s-%s' % ('%Y', month_format))
date = datetime.date(*tt[:3])
except ValueError:
raise Http404
model = queryset.model
now = timezone.now()
# Calculate first and last day of month, for use in a date-range lookup.
first_day = date.replace(day=1)
if first_day.month == 12:
last_day = first_day.replace(year=first_day.year + 1, month=1)
else:
last_day = first_day.replace(month=first_day.month + 1)
lookup_kwargs = {
'%s__gte' % date_field: first_day,
'%s__lt' % date_field: last_day,
}
# Only bother to check current date if the month isn't in the past and future objects are requested.
if last_day >= now.date() and not allow_future:
lookup_kwargs['%s__lte' % date_field] = now
object_list = queryset.filter(**lookup_kwargs)
date_list = object_list.dates(date_field, 'day')
if not object_list and not allow_empty:
raise Http404
# Calculate the next month, if applicable.
if allow_future:
next_month = last_day
elif last_day <= datetime.date.today():
next_month = last_day
else:
next_month = None
# Calculate the previous month
if first_day.month == 1:
previous_month = first_day.replace(year=first_day.year-1,month=12)
else:
previous_month = first_day.replace(month=first_day.month-1)
if not template_name:
template_name = "%s/%s_archive_month.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'date_list': date_list,
'%s_list' % template_object_name: object_list,
'month': date,
'next_month': next_month,
'previous_month': previous_month,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
return HttpResponse(t.render(c), mimetype=mimetype)
def archive_week(request, year, week, queryset, date_field,
template_name=None, template_loader=loader,
extra_context=None, allow_empty=True, context_processors=None,
template_object_name='object', mimetype=None, allow_future=False):
"""
Generic weekly archive view.
Templates: ``<app_label>/<model_name>_archive_week.html``
Context:
week:
(date) this week
object_list:
list of objects published in the given week
"""
if extra_context is None: extra_context = {}
try:
tt = time.strptime(year+'-0-'+week, '%Y-%w-%U')
date = datetime.date(*tt[:3])
except ValueError:
raise Http404
model = queryset.model
now = timezone.now()
# Calculate first and last day of week, for use in a date-range lookup.
first_day = date
last_day = date + datetime.timedelta(days=7)
lookup_kwargs = {
'%s__gte' % date_field: first_day,
'%s__lt' % date_field: last_day,
}
# Only bother to check current date if the week isn't in the past and future objects aren't requested.
if last_day >= now.date() and not allow_future:
lookup_kwargs['%s__lte' % date_field] = now
object_list = queryset.filter(**lookup_kwargs)
if not object_list and not allow_empty:
raise Http404
if not template_name:
template_name = "%s/%s_archive_week.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'%s_list' % template_object_name: object_list,
'week': date,
})
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
return HttpResponse(t.render(c), mimetype=mimetype)
def archive_day(request, year, month, day, queryset, date_field,
month_format='%b', day_format='%d', template_name=None,
template_loader=loader, extra_context=None, allow_empty=False,
context_processors=None, template_object_name='object',
mimetype=None, allow_future=False):
"""
Generic daily archive view.
Templates: ``<app_label>/<model_name>_archive_day.html``
Context:
object_list:
list of objects published that 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
"""
if extra_context is None: extra_context = {}
try:
tt = time.strptime('%s-%s-%s' % (year, month, day),
'%s-%s-%s' % ('%Y', month_format, day_format))
date = datetime.date(*tt[:3])
except ValueError:
raise Http404
model = queryset.model
now = timezone.now()
if isinstance(model._meta.get_field(date_field), DateTimeField):
lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))}
else:
lookup_kwargs = {date_field: date}
# Only bother to check current date if the date isn't in the past and future objects aren't requested.
if date >= now.date() and not allow_future:
lookup_kwargs['%s__lte' % date_field] = now
object_list = queryset.filter(**lookup_kwargs)
if not allow_empty and not object_list:
raise Http404
# Calculate the next day, if applicable.
if allow_future:
next_day = date + datetime.timedelta(days=1)
elif date < datetime.date.today():
next_day = date + datetime.timedelta(days=1)
else:
next_day = None
if not template_name:
template_name = "%s/%s_archive_day.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'%s_list' % template_object_name: object_list,
'day': date,
'previous_day': date - datetime.timedelta(days=1),
'next_day': next_day,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
return HttpResponse(t.render(c), mimetype=mimetype)
def archive_today(request, **kwargs):
"""
Generic daily archive view for today. Same as archive_day view.
"""
today = datetime.date.today()
kwargs.update({
'year': str(today.year),
'month': today.strftime('%b').lower(),
'day': str(today.day),
})
return archive_day(request, **kwargs)
def object_detail(request, year, month, day, queryset, date_field,
month_format='%b', day_format='%d', object_id=None, slug=None,
slug_field='slug', template_name=None, template_name_field=None,
template_loader=loader, extra_context=None, context_processors=None,
template_object_name='object', mimetype=None, allow_future=False):
"""
Generic detail view from year/month/day/slug or year/month/day/id structure.
Templates: ``<app_label>/<model_name>_detail.html``
Context:
object:
the object to be detailed
"""
if extra_context is None: extra_context = {}
try:
tt = time.strptime('%s-%s-%s' % (year, month, day),
'%s-%s-%s' % ('%Y', month_format, day_format))
date = datetime.date(*tt[:3])
except ValueError:
raise Http404
model = queryset.model
now = timezone.now()
if isinstance(model._meta.get_field(date_field), DateTimeField):
lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))}
else:
lookup_kwargs = {date_field: date}
# Only bother to check current date if the date isn't in the past and future objects aren't requested.
if date >= now.date() and not allow_future:
lookup_kwargs['%s__lte' % date_field] = now
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 detail view must be called with either an object_id or a slug/slugfield")
try:
obj = queryset.get(**lookup_kwargs)
except ObjectDoesNotExist:
raise Http404("No %s found for" % model._meta.verbose_name)
if not template_name:
template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
if template_name_field:
template_name_list = [getattr(obj, template_name_field), template_name]
t = template_loader.select_template(template_name_list)
else:
t = template_loader.get_template(template_name)
c = RequestContext(request, {
template_object_name: obj,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
response = HttpResponse(t.render(c), mimetype=mimetype)
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response

View File

@ -1,152 +0,0 @@
from django.template import loader, RequestContext
from django.http import Http404, HttpResponse
from django.core.xheaders import populate_xheaders
from django.core.paginator import Paginator, InvalidPage
from django.core.exceptions import ObjectDoesNotExist
import warnings
warnings.warn(
'Function-based generic views have been deprecated; use class-based views instead.',
DeprecationWarning
)
def object_list(request, queryset, paginate_by=None, page=None,
allow_empty=True, template_name=None, template_loader=loader,
extra_context=None, context_processors=None, template_object_name='object',
mimetype=None):
"""
Generic list of objects.
Templates: ``<app_label>/<model_name>_list.html``
Context:
object_list
list of objects
is_paginated
are the results paginated?
results_per_page
number of objects per page (if paginated)
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
hits
number of objects, total
last_on_page
the result number of the last of object in the
object_list (1-indexed)
first_on_page
the result number of the first object in the
object_list (1-indexed)
page_range:
A list of the page numbers (1-indexed).
"""
if extra_context is None: extra_context = {}
queryset = queryset._clone()
if paginate_by:
paginator = Paginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
if not page:
page = request.GET.get('page', 1)
try:
page_number = int(page)
except ValueError:
if page == 'last':
page_number = paginator.num_pages
else:
# Page is not 'last', nor can it be converted to an int.
raise Http404
try:
page_obj = paginator.page(page_number)
except InvalidPage:
raise Http404
c = RequestContext(request, {
'%s_list' % template_object_name: page_obj.object_list,
'paginator': paginator,
'page_obj': page_obj,
'is_paginated': page_obj.has_other_pages(),
# Legacy template context stuff. New templates should use page_obj
# to access this instead.
'results_per_page': paginator.per_page,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
'page': page_obj.number,
'next': page_obj.next_page_number(),
'previous': page_obj.previous_page_number(),
'first_on_page': page_obj.start_index(),
'last_on_page': page_obj.end_index(),
'pages': paginator.num_pages,
'hits': paginator.count,
'page_range': paginator.page_range,
}, context_processors)
else:
c = RequestContext(request, {
'%s_list' % template_object_name: queryset,
'paginator': None,
'page_obj': None,
'is_paginated': False,
}, context_processors)
if not allow_empty and len(queryset) == 0:
raise Http404
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
if not template_name:
model = queryset.model
template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
return HttpResponse(t.render(c), mimetype=mimetype)
def object_detail(request, queryset, object_id=None, slug=None,
slug_field='slug', template_name=None, template_name_field=None,
template_loader=loader, extra_context=None,
context_processors=None, template_object_name='object',
mimetype=None):
"""
Generic detail of an object.
Templates: ``<app_label>/<model_name>_detail.html``
Context:
object
the object
"""
if extra_context is None: extra_context = {}
model = queryset.model
if object_id:
queryset = queryset.filter(pk=object_id)
elif slug and slug_field:
queryset = queryset.filter(**{slug_field: slug})
else:
raise AttributeError("Generic detail view must be called with either an object_id or a slug/slug_field.")
try:
obj = queryset.get()
except ObjectDoesNotExist:
raise Http404("No %s found matching the query" % (model._meta.verbose_name))
if not template_name:
template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
if template_name_field:
template_name_list = [getattr(obj, template_name_field), template_name]
t = template_loader.select_template(template_name_list)
else:
t = template_loader.get_template(template_name)
c = RequestContext(request, {
template_object_name: obj,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
response = HttpResponse(t.render(c), mimetype=mimetype)
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response

View File

@ -1,68 +0,0 @@
from django.template import loader, RequestContext
from django.http import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseGone
from django.utils.log import getLogger
import warnings
warnings.warn(
'Function-based generic views have been deprecated; use class-based views instead.',
DeprecationWarning
)
logger = getLogger('django.request')
def direct_to_template(request, template, extra_context=None, mimetype=None, **kwargs):
"""
Render a given template with any extra URL parameters in the context as
``{{ params }}``.
"""
if extra_context is None: extra_context = {}
dictionary = {'params': kwargs}
for key, value in extra_context.items():
if callable(value):
dictionary[key] = value()
else:
dictionary[key] = value
c = RequestContext(request, dictionary)
t = loader.get_template(template)
return HttpResponse(t.render(c), content_type=mimetype)
def redirect_to(request, url, permanent=True, query_string=False, **kwargs):
"""
Redirect to a given URL.
The given url may contain dict-style string formatting, which will be
interpolated against the params in the URL. For example, to redirect from
``/foo/<id>/`` to ``/bar/<id>/``, you could use the following URLconf::
urlpatterns = patterns('',
('^foo/(?P<id>\d+)/$', 'django.views.generic.simple.redirect_to', {'url' : '/bar/%(id)s/'}),
)
If the given url is ``None``, a HttpResponseGone (410) will be issued.
If the ``permanent`` argument is False, then the response will have a 302
HTTP status code. Otherwise, the status code will be 301.
If the ``query_string`` argument is True, then the GET query string
from the request is appended to the URL.
"""
args = request.META.get('QUERY_STRING', '')
if url is not None:
if kwargs:
url = url % kwargs
if args and query_string:
url = "%s?%s" % (url, args)
klass = permanent and HttpResponsePermanentRedirect or HttpResponseRedirect
return klass(url)
else:
logger.warning('Gone: %s', request.path,
extra={
'status_code': 410,
'request': request
})
return HttpResponseGone()

View File

@ -16,7 +16,7 @@ Glossary
A higher-order :term:`view` function that provides an abstract/generic A higher-order :term:`view` function that provides an abstract/generic
implementation of a common idiom or pattern found in view development. implementation of a common idiom or pattern found in view development.
See :doc:`/ref/generic-views`. See :doc:`/ref/class-based-views`.
model model
Models store your application's data. Models store your application's data.

View File

@ -195,7 +195,6 @@ Other batteries included
* :doc:`Unicode in Django <ref/unicode>` * :doc:`Unicode in Django <ref/unicode>`
* :doc:`Web design helpers <ref/contrib/webdesign>` * :doc:`Web design helpers <ref/contrib/webdesign>`
* :doc:`Validators <ref/validators>` * :doc:`Validators <ref/validators>`
* Function-based generic views (Deprecated) :doc:`Overview<topics/generic-views>` | :doc:`Built-in generic views<ref/generic-views>` | :doc:`Migration guide<topics/generic-views-migration>`
The Django open-source project The Django open-source project
============================== ==============================

View File

@ -134,7 +134,7 @@ these changes.
* The function-based generic view modules will be removed in favor of their * The function-based generic view modules will be removed in favor of their
class-based equivalents, outlined :doc:`here class-based equivalents, outlined :doc:`here
</topics/generic-views-migration>`: </topics/class-based-views>`:
* The :class:`~django.core.servers.basehttp.AdminMediaHandler` will be * The :class:`~django.core.servers.basehttp.AdminMediaHandler` will be
removed. In its place use removed. In its place use

View File

@ -320,7 +320,7 @@ function anymore -- generic views can be (and are) used multiple times
Run the server, and use your new polling app based on generic views. Run the server, and use your new polling app based on generic views.
For full details on generic views, see the :doc:`generic views documentation For full details on generic views, see the :doc:`generic views documentation
</topics/http/generic-views>`. </topics/class-based-views>`.
Coming soon Coming soon
=========== ===========

View File

@ -54,7 +54,7 @@ of 1.0. This includes these APIs:
- :doc:`HTTP request/response handling </topics/http/index>`, including file - :doc:`HTTP request/response handling </topics/http/index>`, including file
uploads, middleware, sessions, URL resolution, view, and shortcut APIs. uploads, middleware, sessions, URL resolution, view, and shortcut APIs.
- :doc:`Generic views </topics/http/generic-views>`. - :doc:`Generic views </topics/class-based-views>`.
- :doc:`Internationalization </topics/i18n/index>`. - :doc:`Internationalization </topics/i18n/index>`.

View File

@ -6,13 +6,9 @@ Class-based generic views
.. note:: .. note::
Prior to Django 1.3, generic views were implemented as functions. The Prior to Django 1.3, generic views were implemented as functions. The
function-based implementation has been deprecated in favor of the function-based implementation has been removed in favor of the
class-based approach described here. class-based approach described here.
For details on the previous generic views implementation,
see the :doc:`topic guide </topics/generic-views>` and
:doc:`detailed reference </ref/generic-views>`.
Writing Web applications can be monotonous, because we repeat certain patterns Writing Web applications can be monotonous, because we repeat certain patterns
again and again. Django tries to take away some of that monotony at the model again and again. Django tries to take away some of that monotony at the model
and template layers, but Web developers also experience this boredom at the view and template layers, but Web developers also experience this boredom at the view

View File

@ -274,10 +274,6 @@ example::
fail_silently=True) fail_silently=True)
messages.info(request, 'Hello world.', fail_silently=True) messages.info(request, 'Hello world.', fail_silently=True)
Internally, Django uses this functionality in the create, update, and delete
:doc:`generic views </topics/http/generic-views>` so that they work even if the
message framework is disabled.
.. note:: .. note::
Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would
otherwise occur when the messages framework disabled and one attempts to otherwise occur when the messages framework disabled and one attempts to

View File

@ -240,16 +240,16 @@ The sitemap framework provides a couple convenience classes for common cases:
.. class:: GenericSitemap .. class:: GenericSitemap
The :class:`django.contrib.sitemaps.GenericSitemap` class works with any The :class:`django.contrib.sitemaps.GenericSitemap` class allows you to
:doc:`generic views </ref/generic-views>` you already have. create a sitemap by passing it a dictionary which has to contain at least
To use it, create an instance, passing in the same :data:`info_dict` you pass to a :data:`queryset` entry. This queryset will be used to generate the items
the generic views. The only requirement is that the dictionary have a of the sitemap. It may also have a :data:`date_field` entry that
:data:`queryset` entry. It may also have a :data:`date_field` entry that specifies a specifies a date field for objects retrieved from the :data:`queryset`.
date field for objects retrieved from the :data:`queryset`. This will be used for This will be used for the :attr:`~Sitemap.lastmod` attribute in the
the :attr:`~Sitemap.lastmod` attribute in the generated sitemap. You may generated sitemap. You may also pass :attr:`~Sitemap.priority` and
also pass :attr:`~Sitemap.priority` and :attr:`~Sitemap.changefreq` :attr:`~Sitemap.changefreq` keyword arguments to the
keyword arguments to the :class:`~django.contrib.sitemaps.GenericSitemap` :class:`~django.contrib.sitemaps.GenericSitemap` constructor to specify
constructor to specify these attributes for all URLs. these attributes for all URLs.
Example Example
------- -------

File diff suppressed because it is too large Load Diff

View File

@ -24,11 +24,3 @@ API Reference
unicode unicode
utils utils
validators validators
Deprecated features
-------------------
.. toctree::
:maxdepth: 1
generic-views

View File

@ -88,8 +88,8 @@ Other new features and changes introduced since Django 1.0 include:
which return the list of hidden -- i.e., ``<input type="hidden">`` -- and which return the list of hidden -- i.e., ``<input type="hidden">`` -- and
visible fields on the form, respectively. visible fields on the form, respectively.
* The ``redirect_to`` generic view (see :doc:`the generic views documentation * The ``redirect_to`` generic view
</ref/generic-views>`) now accepts an additional keyword argument now accepts an additional keyword argument
``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP ``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP
permanent redirect (status code 301). If ``False``, the view will emit an HTTP permanent redirect (status code 301). If ``False``, the view will emit an HTTP
temporary redirect (status code 302). temporary redirect (status code 302).

View File

@ -396,8 +396,8 @@ Other new features and changes introduced since Django 1.0 include:
which return the list of hidden -- i.e., ``<input type="hidden">`` -- and which return the list of hidden -- i.e., ``<input type="hidden">`` -- and
visible fields on the form, respectively. visible fields on the form, respectively.
* The ``redirect_to`` generic view (see :doc:`the generic views documentation * The ``redirect_to`` generic view
</ref/generic-views>`) now accepts an additional keyword argument now accepts an additional keyword argument
``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP ``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP
permanent redirect (status code 301). If ``False``, the view will emit an HTTP permanent redirect (status code 301). If ``False``, the view will emit an HTTP
temporary redirect (status code 302). temporary redirect (status code 302).

View File

@ -40,8 +40,8 @@ the basis for reusable applications that can be easily extended.
See :doc:`the documentation on Class-based Generic Views See :doc:`the documentation on Class-based Generic Views
</topics/class-based-views>` for more details. There is also a document to </topics/class-based-views>` for more details. There is also a document to
help you :doc:`convert your function-based generic views to class-based help you `convert your function-based generic views to class-based
views</topics/generic-views-migration>`. views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_.
Logging Logging
~~~~~~~ ~~~~~~~

View File

@ -81,9 +81,9 @@ used as the basis for reusable applications that can be easily
extended. extended.
See :doc:`the documentation on class-based generic views</topics/class-based-views>` See :doc:`the documentation on class-based generic views</topics/class-based-views>`
for more details. There is also a document to help you :doc:`convert for more details. There is also a document to help you `convert
your function-based generic views to class-based your function-based generic views to class-based
views</topics/generic-views-migration>`. views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_.
Logging Logging
~~~~~~~ ~~~~~~~

View File

@ -1472,19 +1472,6 @@ To limit access to a :doc:`class-based generic view </ref/class-based-views>`,
decorate the :meth:`View.dispatch <django.views.generic.base.View.dispatch>` decorate the :meth:`View.dispatch <django.views.generic.base.View.dispatch>`
method on the class. See :ref:`decorating-class-based-views` for details. method on the class. See :ref:`decorating-class-based-views` for details.
Function-based generic views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To limit access to a :doc:`function-based generic view </ref/generic-views>`,
write a thin wrapper around the view, and point your URLconf to your wrapper
instead of the generic view itself. For example::
from django.views.generic.date_based import object_detail
@login_required
def limited_object_detail(*args, **kwargs):
return object_detail(*args, **kwargs)
.. _permissions: .. _permissions:
Permissions Permissions

View File

@ -6,13 +6,9 @@ Class-based generic views
.. note:: .. note::
Prior to Django 1.3, generic views were implemented as functions. The Prior to Django 1.3, generic views were implemented as functions. The
function-based implementation has been deprecated in favor of the function-based implementation has been removed in favor of the
class-based approach described here. class-based approach described here.
For details on the previous generic views implementation,
see the :doc:`topic guide </topics/generic-views>` and
:doc:`detailed reference </ref/generic-views>`.
Writing Web applications can be monotonous, because we repeat certain patterns Writing Web applications can be monotonous, because we repeat certain patterns
again and again. Django tries to take away some of that monotony at the model again and again. Django tries to take away some of that monotony at the model
and template layers, but Web developers also experience this boredom at the view and template layers, but Web developers also experience this boredom at the view

View File

@ -1,159 +0,0 @@
======================================
Migrating function-based generic views
======================================
All the :doc:`function-based generic views</ref/generic-views>`
that existed in Django 1.2 have analogs as :doc:`class-based generic
views</ref/class-based-views>` in Django 1.3. The feature set
exposed in those function-based views can be replicated in a
class-based way.
How to migrate
==============
Replace generic views with generic classes
------------------------------------------
Existing usage of function-based generic views should be replaced with
their class-based analogs:
==================================================== ====================================================
Old function-based generic view New class-based generic view
==================================================== ====================================================
``django.views.generic.simple.direct_to_template`` :class:`django.views.generic.base.TemplateView`
``django.views.generic.simple.redirect_to`` :class:`django.views.generic.base.RedirectView`
``django.views.generic.list_detail.object_list`` :class:`django.views.generic.list.ListView`
``django.views.generic.list_detail.object_detail`` :class:`django.views.generic.detail.DetailView`
``django.views.generic.create_update.create_object`` :class:`django.views.generic.edit.CreateView`
``django.views.generic.create_update.update_object`` :class:`django.views.generic.edit.UpdateView`
``django.views.generic.create_update.delete_object`` :class:`django.views.generic.edit.DeleteView`
``django.views.generic.date_based.archive_index`` :class:`django.views.generic.dates.ArchiveIndexView`
``django.views.generic.date_based.archive_year`` :class:`django.views.generic.dates.YearArchiveView`
``django.views.generic.date_based.archive_month`` :class:`django.views.generic.dates.MonthArchiveView`
``django.views.generic.date_based.archive_week`` :class:`django.views.generic.dates.WeekArchiveView`
``django.views.generic.date_based.archive_day`` :class:`django.views.generic.dates.DayArchiveView`
``django.views.generic.date_based.archive_today`` :class:`django.views.generic.dates.TodayArchiveView`
``django.views.generic.date_based.object_detail`` :class:`django.views.generic.dates.DateDetailView`
==================================================== ====================================================
To do this, replace the reference to the generic view function with
a ``as_view()`` instantiation of the class-based view. For example,
the old-style ``direct_to_template`` pattern::
('^about/$', direct_to_template, {'template': 'about.html'})
can be replaced with an instance of
:class:`~django.views.generic.base.TemplateView`::
('^about/$', TemplateView.as_view(template_name='about.html'))
``template`` argument to ``direct_to_template`` views
-----------------------------------------------------
The ``template`` argument to the ``direct_to_template`` view has been renamed
``template_name``. This has been done to maintain consistency with other views.
``object_id`` argument to detail views
--------------------------------------
The object_id argument to the ``object_detail`` view has been renamed
``pk`` on the :class:`~django.views.generic.detail.DetailView`.
``template_object_name``
------------------------
``template_object_name`` has been renamed ``context_object_name``,
reflecting the fact that the context data can be used for purposes
other than template rendering (e.g., to populate JSON output).
The ``_list`` suffix on list views
----------------------------------
In a function-based :class:`ListView`, the ``template_object_name``
was appended with the suffix ``'_list'`` to yield the final context
variable name. In a class-based ``ListView``, the
``context_object_name`` is used verbatim. The ``'_list'`` suffix
is only applied when generating a default context object name.
The context data for ``object_list`` views
------------------------------------------
The context provided by :class:`~django.views.generic.list.MultipleObjectMixin`
is quite different from that provided by ``object_list``, with most pagination
related variables replaced by a single ``page_obj`` object. The following are no
longer provided:
* ``first_on_page``
* ``has_next``
* ``has_previous``
* ``hits``
* ``last_on_page``
* ``next``
* ``page_range``
* ``page``
* ``pages``
* ``previous``
* ``results_per_page``
``extra_context``
-----------------
Function-based generic views provided an ``extra_context`` argument
as way to insert extra items into the context at time of rendering.
Class-based views don't provide an ``extra_context`` argument.
Instead, you subclass the view, overriding :meth:`get_context_data()`.
For example::
class MyListView(ListView):
def get_context_data(self, **kwargs):
context = super(MyListView, self).get_context_data(**kwargs)
context.update({
'foo': 42,
'bar': 37
})
return context
``post_save_redirect`` argument to create and update views
----------------------------------------------------------
The ``post_save_redirect`` argument to the create and update views
has been renamed ``success_url`` on the
:class:`~django.views.generic.edit.ModelFormMixin`.
``mimetype``
------------
Some function-based generic views provided a ``mimetype`` argument
as way to control the mimetype of the response.
Class-based views don't provide a ``mimetype`` argument. Instead, you
subclass the view, overriding
:meth:`TemplateResponseMixin.render_to_response()` and pass in arguments for
the TemplateResponse constructor. For example::
class MyListView(ListView):
def render_to_response(self, context, **kwargs):
return super(MyListView, self).render_to_response(context,
content_type='application/json', **kwargs)
``context_processors``
----------------------
Some function-based generic views provided a ``context_processors``
argument that could be used to force the use of specialized context
processors when rendering template content.
Class-based views don't provide a ``context_processors`` argument.
Instead, you subclass the view, overriding
:meth:`TemplateResponseMixin.render_to_response()`, and passing in
a context instance that has been instantiated with the processors
you want to use. For example::
class MyListView(ListView):
def render_to_response(self, context, **kwargs):
return super(MyListView, self).render_to_response(
RequestContext(self.request,
context,
processors=[custom_processor]),
**kwargs)

View File

@ -1,511 +0,0 @@
=============
Generic views
=============
.. versionchanged:: 1.3
.. note::
From Django 1.3, function-based generic views have been deprecated in favor
of a class-based approach, described in the class-based views :doc:`topic
guide </topics/class-based-views>` and :doc:`detailed reference
</ref/class-based-views>`.
Writing Web applications can be monotonous, because we repeat certain patterns
again and again. Django tries to take away some of that monotony at the model
and template layers, but Web developers also experience this boredom at the view
level.
Django's *generic views* were developed to ease that pain. They take certain
common idioms and patterns found in view development and abstract them so that
you can quickly write common views of data without having to write too much
code.
We can recognize certain common tasks, like displaying a list of objects, and
write code that displays a list of *any* object. Then the model in question can
be passed as an extra argument to the URLconf.
Django ships with generic views to do the following:
* Perform common "simple" tasks: redirect to a different page and
render a given template.
* Display list and detail pages for a single object. If we were creating an
application to manage conferences then a ``talk_list`` view and a
``registered_user_list`` view would be examples of list views. A single
talk page is an example of what we call a "detail" view.
* Present date-based objects in year/month/day archive pages,
associated detail, and "latest" pages. The Django Weblog's
(https://www.djangoproject.com/weblog/) year, month, and
day archives are built with these, as would be a typical
newspaper's archives.
* Allow users to create, update, and delete objects -- with or
without authorization.
Taken together, these views provide easy interfaces to perform the most common
tasks developers encounter.
Using generic views
===================
All of these views are used by creating configuration dictionaries in
your URLconf files and passing those dictionaries as the third member of the
URLconf tuple for a given pattern.
For example, here's a simple URLconf you could use to present a static "about"
page::
from django.conf.urls import patterns, url, include
from django.views.generic.simple import direct_to_template
urlpatterns = patterns('',
('^about/$', direct_to_template, {
'template': 'about.html'
})
)
Though this might seem a bit "magical" at first glance -- look, a view with no
code! --, actually the ``direct_to_template`` view simply grabs information from
the extra-parameters dictionary and uses that information when rendering the
view.
Because this generic view -- and all the others -- is a regular view function
like any other, we can reuse it inside our own views. As an example, let's
extend our "about" example to map URLs of the form ``/about/<whatever>/`` to
statically rendered ``about/<whatever>.html``. We'll do this by first modifying
the URLconf to point to a view function:
.. parsed-literal::
from django.conf.urls import patterns, url, include
from django.views.generic.simple import direct_to_template
**from books.views import about_pages**
urlpatterns = patterns('',
('^about/$', direct_to_template, {
'template': 'about.html'
}),
**('^about/(\\w+)/$', about_pages),**
)
Next, we'll write the ``about_pages`` view::
from django.http import Http404
from django.template import TemplateDoesNotExist
from django.views.generic.simple import direct_to_template
def about_pages(request, page):
try:
return direct_to_template(request, template="about/%s.html" % page)
except TemplateDoesNotExist:
raise Http404()
Here we're treating ``direct_to_template`` like any other function. Since it
returns an ``HttpResponse``, we can simply return it as-is. The only slightly
tricky business here is dealing with missing templates. We don't want a
nonexistent template to cause a server error, so we catch
``TemplateDoesNotExist`` exceptions and return 404 errors instead.
.. admonition:: Is there a security vulnerability here?
Sharp-eyed readers may have noticed a possible security hole: we're
constructing the template name using interpolated content from the browser
(``template="about/%s.html" % page``). At first glance, this looks like a
classic *directory traversal* vulnerability. But is it really?
Not exactly. Yes, a maliciously crafted value of ``page`` could cause
directory traversal, but although ``page`` *is* taken from the request URL,
not every value will be accepted. The key is in the URLconf: we're using
the regular expression ``\w+`` to match the ``page`` part of the URL, and
``\w`` only accepts letters and numbers. Thus, any malicious characters
(dots and slashes, here) will be rejected by the URL resolver before they
reach the view itself.
Generic views of objects
========================
The ``direct_to_template`` certainly is useful, but Django's generic views
really shine when it comes to presenting views on your database content. Because
it's such a common task, Django comes with a handful of built-in generic views
that make generating list and detail views of objects incredibly easy.
Let's take a look at one of these generic views: the "object list" view. We'll
be using these models::
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __unicode__(self):
return self.name
class Meta:
ordering = ["-name"]
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
To build a list page of all publishers, we'd use a URLconf along these lines::
from django.conf.urls import patterns, url, include
from django.views.generic import list_detail
from books.models import Publisher
publisher_info = {
"queryset" : Publisher.objects.all(),
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info)
)
That's all the Python code we need to write. We still need to write a template,
however. We could explicitly tell the ``object_list`` view which template to use
by including a ``template_name`` key in the extra arguments dictionary, but in
the absence of an explicit template Django will infer one from the object's
name. In this case, the inferred template will be
``"books/publisher_list.html"`` -- the "books" part comes from the name of the
app that defines the model, while the "publisher" bit is just the lowercased
version of the model's name.
.. highlightlang:: html+django
This template will be rendered against a context containing a variable called
``object_list`` that contains all the publisher objects. A very simple template
might look like the following::
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
That's really all there is to it. All the cool features of generic views come
from changing the "info" dictionary passed to the generic view. The
:doc:`generic views reference</ref/generic-views>` documents all the generic
views and all their options in detail; the rest of this document will consider
some of the common ways you might customize and extend generic views.
Extending generic views
=======================
.. highlightlang:: python
There's no question that using generic views can speed up development
substantially. In most projects, however, there comes a moment when the
generic views no longer suffice. Indeed, the most common question asked by new
Django developers is how to make generic views handle a wider array of
situations.
Luckily, in nearly every one of these cases, there are ways to simply extend
generic views to handle a larger array of use cases. These situations usually
fall into a handful of patterns dealt with in the sections that follow.
Making "friendly" template contexts
-----------------------------------
You might have noticed that our sample publisher list template stores all the
books in a variable named ``object_list``. While this works just fine, it isn't
all that "friendly" to template authors: they have to "just know" that they're
dealing with publishers here. A better name for that variable would be
``publisher_list``; that variable's content is pretty obvious.
We can change the name of that variable easily with the ``template_object_name``
argument:
.. parsed-literal::
publisher_info = {
"queryset" : Publisher.objects.all(),
**"template_object_name" : "publisher",**
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info)
)
Providing a useful ``template_object_name`` is always a good idea. Your
coworkers who design templates will thank you.
Adding extra context
--------------------
Often you simply need to present some extra information beyond that provided by
the generic view. For example, think of showing a list of all the books on each
publisher detail page. The ``object_detail`` generic view provides the
publisher to the context, but it seems there's no way to get additional
information in that template.
But there is: all generic views take an extra optional parameter,
``extra_context``. This is a dictionary of extra objects that will be added to
the template's context. So, to provide the list of all books on the detail
detail view, we'd use an info dict like this:
.. parsed-literal::
from books.models import Publisher, **Book**
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
**"extra_context" : {"book_list" : Book.objects.all()}**
}
This would populate a ``{{ book_list }}`` variable in the template context.
This pattern can be used to pass any information down into the template for the
generic view. It's very handy.
However, there's actually a subtle bug here -- can you spot it?
The problem has to do with when the queries in ``extra_context`` are evaluated.
Because this example puts ``Book.objects.all()`` in the URLconf, it will
be evaluated only once (when the URLconf is first loaded). Once you add or
remove books, you'll notice that the generic view doesn't reflect those
changes until you reload the Web server (see :ref:`caching-and-querysets`
for more information about when QuerySets are cached and evaluated).
.. note::
This problem doesn't apply to the ``queryset`` generic view argument. Since
Django knows that particular QuerySet should *never* be cached, the generic
view takes care of clearing the cache when each view is rendered.
The solution is to use a callback in ``extra_context`` instead of a value. Any
callable (i.e., a function) that's passed to ``extra_context`` will be evaluated
when the view is rendered (instead of only once). You could do this with an
explicitly defined function:
.. parsed-literal::
def get_books():
return Book.objects.all()
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : **{"book_list" : get_books}**
}
or you could use a less obvious but shorter version that relies on the fact that
``Book.objects.all`` is itself a callable:
.. parsed-literal::
publisher_info = {
"queryset" : Publisher.objects.all(),
"template_object_name" : "publisher",
"extra_context" : **{"book_list" : Book.objects.all}**
}
Notice the lack of parentheses after ``Book.objects.all``; this references
the function without actually calling it (which the generic view will do later).
Viewing subsets of objects
--------------------------
Now let's take a closer look at this ``queryset`` key we've been using all
along. Most generic views take one of these ``queryset`` arguments -- it's how
the view knows which set of objects to display (see :doc:`/topics/db/queries` for
more information about ``QuerySet`` objects, and see the
:doc:`generic views reference</ref/generic-views>` for the complete details).
To pick a simple example, we might want to order a list of books by
publication date, with the most recent first:
.. parsed-literal::
book_info = {
"queryset" : Book.objects.all().order_by("-publication_date"),
}
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
**(r'^books/$', list_detail.object_list, book_info),**
)
That's a pretty simple example, but it illustrates the idea nicely. Of course,
you'll usually want to do more than just reorder objects. If you want to
present a list of books by a particular publisher, you can use the same
technique:
.. parsed-literal::
**acme_books = {**
**"queryset": Book.objects.filter(publisher__name="Acme Publishing"),**
**"template_name" : "books/acme_list.html"**
**}**
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
**(r'^books/acme/$', list_detail.object_list, acme_books),**
)
Notice that along with a filtered ``queryset``, we're also using a custom
template name. If we didn't, the generic view would use the same template as the
"vanilla" object list, which might not be what we want.
Also notice that this isn't a very elegant way of doing publisher-specific
books. If we want to add another publisher page, we'd need another handful of
lines in the URLconf, and more than a few publishers would get unreasonable.
We'll deal with this problem in the next section.
.. note::
If you get a 404 when requesting ``/books/acme/``, check to ensure you
actually have a Publisher with the name 'ACME Publishing'. Generic
views have an ``allow_empty`` parameter for this case. See the
:doc:`generic views reference</ref/generic-views>` for more details.
Complex filtering with wrapper functions
----------------------------------------
Another common need is to filter down the objects given in a list page by some
key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but
what if we wanted to write a view that displayed all the books by some arbitrary
publisher? We can "wrap" the ``object_list`` generic view to avoid writing a lot
of code by hand. As usual, we'll start by writing a URLconf:
.. parsed-literal::
from books.views import books_by_publisher
urlpatterns = patterns('',
(r'^publishers/$', list_detail.object_list, publisher_info),
**(r'^books/(\\w+)/$', books_by_publisher),**
)
Next, we'll write the ``books_by_publisher`` view itself::
from django.http import Http404
from django.views.generic import list_detail
from books.models import Book, Publisher
def books_by_publisher(request, name):
# Look up the publisher (and raise a 404 if it can't be found).
try:
publisher = Publisher.objects.get(name__iexact=name)
except Publisher.DoesNotExist:
raise Http404
# Use the object_list view for the heavy lifting.
return list_detail.object_list(
request,
queryset = Book.objects.filter(publisher=publisher),
template_name = "books/books_by_publisher.html",
template_object_name = "books",
extra_context = {"publisher" : publisher}
)
This works because there's really nothing special about generic views -- they're
just Python functions. Like any view function, generic views expect a certain
set of arguments and return ``HttpResponse`` objects. Thus, it's incredibly easy
to wrap a small function around a generic view that does additional work before
(or after; see the next section) handing things off to the generic view.
.. note::
Notice that in the preceding example we passed the current publisher being
displayed in the ``extra_context``. This is usually a good idea in wrappers
of this nature; it lets the template know which "parent" object is currently
being browsed.
Performing extra work
---------------------
The last common pattern we'll look at involves doing some extra work before
or after calling the generic view.
Imagine we had a ``last_accessed`` field on our ``Author`` object that we were
using to keep track of the last time anybody looked at that author::
# models.py
class Author(models.Model):
salutation = models.CharField(max_length=10)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
headshot = models.ImageField(upload_to='/tmp')
last_accessed = models.DateTimeField()
The generic ``object_detail`` view, of course, wouldn't know anything about this
field, but once again we could easily write a custom view to keep that field
updated.
First, we'd need to add an author detail bit in the URLconf to point to a
custom view:
.. parsed-literal::
from books.views import author_detail
urlpatterns = patterns('',
#...
**(r'^authors/(?P<author_id>\\d+)/$', author_detail),**
)
Then we'd write our wrapper function::
import datetime
from books.models import Author
from django.views.generic import list_detail
from django.shortcuts import get_object_or_404
def author_detail(request, author_id):
# Look up the Author (and raise a 404 if she's not found)
author = get_object_or_404(Author, pk=author_id)
# Record the last accessed date
author.last_accessed = datetime.datetime.now()
author.save()
# Show the detail page
return list_detail.object_detail(
request,
queryset = Author.objects.all(),
object_id = author_id,
)
.. note::
This code won't actually work unless you create a
``books/author_detail.html`` template.
We can use a similar idiom to alter the response returned by the generic view.
If we wanted to provide a downloadable plain-text version of the list of
authors, we could use a view like this::
def author_list_plaintext(request):
response = list_detail.object_list(
request,
queryset = Author.objects.all(),
mimetype = "text/plain",
template_name = "books/author_list.txt"
)
response["Content-Disposition"] = "attachment; filename=authors.txt"
return response
This works because the generic views return simple ``HttpResponse`` objects
that can be treated like dictionaries to set HTTP headers. This
``Content-Disposition`` business, by the way, instructs the browser to
download and save the page instead of displaying it in the browser.

View File

@ -2,4 +2,4 @@
Generic views Generic views
============= =============
See :doc:`/ref/generic-views`. See :doc:`/ref/class-based-views`.

View File

@ -416,8 +416,8 @@ Old::
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^$', 'django.views.generic.date_based.archive_index'), (r'^$', 'myapp.views.app_index'),
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'django.views.generic.date_based.archive_month'), (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'myapp.views.month_display'),
(r'^tag/(?P<tag>\w+)/$', 'weblog.views.tag'), (r'^tag/(?P<tag>\w+)/$', 'weblog.views.tag'),
) )
@ -425,9 +425,9 @@ New::
from django.conf.urls import patterns, url, include from django.conf.urls import patterns, url, include
urlpatterns = patterns('django.views.generic.date_based', urlpatterns = patterns('myapp.views',
(r'^$', 'archive_index'), (r'^$', 'app_index'),
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$','archive_month'), (r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$','month_display'),
) )
urlpatterns += patterns('weblog.views', urlpatterns += patterns('weblog.views',
@ -579,7 +579,7 @@ In this example, for a request to ``/blog/2005/``, Django will call the
year='2005', foo='bar' year='2005', foo='bar'
This technique is used in :doc:`generic views </ref/generic-views>` and in the This technique is used in the
:doc:`syndication framework </ref/contrib/syndication>` to pass metadata and :doc:`syndication framework </ref/contrib/syndication>` to pass metadata and
options to views. options to views.

View File

@ -12,7 +12,6 @@ Introductions to all the key parts of Django you'll need to know:
forms/index forms/index
templates templates
class-based-views class-based-views
generic-views-migration
files files
testing testing
auth auth
@ -27,11 +26,3 @@ Introductions to all the key parts of Django you'll need to know:
serialization serialization
settings settings
signals signals
Deprecated features
-------------------
.. toctree::
:maxdepth: 1
generic-views

View File

@ -1,5 +1,3 @@
import warnings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
@ -8,14 +6,6 @@ class SpecialHeadersTest(TestCase):
fixtures = ['data.xml'] fixtures = ['data.xml']
urls = 'regressiontests.special_headers.urls' urls = 'regressiontests.special_headers.urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.list_detail')
def tearDown(self):
self.restore_warnings_state()
def test_xheaders(self): def test_xheaders(self):
user = User.objects.get(username='super') user = User.objects.get(username='super')
response = self.client.get('/special_headers/article/1/') response = self.client.get('/special_headers/article/1/')

View File

@ -2,13 +2,12 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.conf.urls import patterns from django.conf.urls import patterns
from django.views.generic.list_detail import object_detail
from . import views from . import views
from .models import Article from .models import Article
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^special_headers/article/(?P<object_id>\d+)/$', object_detail, {'queryset': Article.objects.all()}), (r'^special_headers/article/(?P<object_id>\d+)/$', views.xview_xheaders),
(r'^special_headers/xview/func/$', views.xview_dec(views.xview)), (r'^special_headers/xview/func/$', views.xview_dec(views.xview)),
(r'^special_headers/xview/class/$', views.xview_dec(views.XViewClass.as_view())), (r'^special_headers/xview/class/$', views.xview_dec(views.XViewClass.as_view())),
) )

View File

@ -1,14 +1,21 @@
# -*- coding:utf-8 -*- from django.core.xheaders import populate_xheaders
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.decorators import decorator_from_middleware from django.utils.decorators import decorator_from_middleware
from django.views.generic import View from django.views.generic import View
from django.middleware.doc import XViewMiddleware from django.middleware.doc import XViewMiddleware
from .models import Article
xview_dec = decorator_from_middleware(XViewMiddleware) xview_dec = decorator_from_middleware(XViewMiddleware)
def xview(request): def xview(request):
return HttpResponse() return HttpResponse()
def xview_xheaders(request, object_id):
response = HttpResponse()
populate_xheaders(request, response, Article, 1)
return response
class XViewClass(View): class XViewClass(View):
def get(self, request): def get(self, request):
return HttpResponse() return HttpResponse()

View File

@ -2,6 +2,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.views.generic import RedirectView
from . import views from . import views
from .models import Article, DateArticle, UrlArticle from .models import Article, DateArticle, UrlArticle
@ -35,65 +36,12 @@ urlpatterns = patterns('',
url(u'^中文/target/$', 'regressiontests.views.views.index_page'), url(u'^中文/target/$', 'regressiontests.views.views.index_page'),
) )
# 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_day/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$',
'archive_day',
numeric_days_info_dict),
(r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
'archive_month',
date_based_info_dict),
(r'^date_based/datefield/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
'archive_month',
date_based_datefield_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='/create_update/view/article/%(slug)s/',
model=Article)),
(r'^create_update/update/article/(?P<slug>[-\w]+)/$', 'update_object',
dict(post_save_redirect='/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='/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)),
)
urlpatterns += patterns('django.views.generic.list_detail',
(r'^object_list/page(?P<page>[\w]*)/$', 'object_list', object_list_dict),
(r'^object_list_no_paginate_by/page(?P<page>[0-9]+)/$', 'object_list',
object_list_no_paginate_by),
)
# rediriects, both temporary and permanent, with non-ASCII targets # rediriects, both temporary and permanent, with non-ASCII targets
urlpatterns += patterns('django.views.generic.simple', urlpatterns += patterns('',
('^nonascii_redirect/$', 'redirect_to', ('^nonascii_redirect/$', RedirectView.as_view(
{'url': u'/中文/target/', 'permanent': False}), url=u'/中文/target/', permanent=False)),
('^permanent_nonascii_redirect/$', 'redirect_to', ('^permanent_nonascii_redirect/$', RedirectView.as_view(
{'url': u'/中文/target/', 'permanent': True}), url=u'/中文/target/', permanent=True)),
) )
urlpatterns += patterns('regressiontests.views.views', urlpatterns += patterns('regressiontests.views.views',
@ -107,13 +55,3 @@ urlpatterns += patterns('regressiontests.views.views',
(r'^shortcuts/render/current_app/$', 'render_view_with_current_app'), (r'^shortcuts/render/current_app/$', 'render_view_with_current_app'),
(r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'), (r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
) )
# simple generic views.
urlpatterns += patterns('django.views.generic.simple',
(r'^simple/redirect_to/$', 'redirect_to', dict(url='/simple/target/')),
(r'^simple/redirect_to_temp/$', 'redirect_to', dict(url='/simple/target/', permanent=False)),
(r'^simple/redirect_to_none/$', 'redirect_to', dict(url=None)),
(r'^simple/redirect_to_arg/(?P<id>\d+)/$', 'redirect_to', dict(url='/simple/target_arg/%(id)s/')),
(r'^simple/redirect_to_query/$', 'redirect_to', dict(url='/simple/target/', query_string=True)),
(r'^simple/redirect_to_arg_and_query/(?P<id>\d+)/$', 'redirect_to', dict(url='/simple/target_arg/%(id)s/', query_string=True)),
)

View File

@ -4,11 +4,6 @@ from .debug import (DebugViewTests, ExceptionReporterTests,
ExceptionReporterTests, PlainTextReportTests, ExceptionReporterFilterTests, ExceptionReporterTests, PlainTextReportTests, ExceptionReporterFilterTests,
AjaxResponseExceptionReporterFilter) AjaxResponseExceptionReporterFilter)
from .defaults import DefaultsTests from .defaults import DefaultsTests
from .generic.create_update import (UpdateDeleteObjectTest, CreateObjectTest,
PostSaveRedirectTests, NoPostSaveNoAbsoluteUrl, AbsoluteUrlNoPostSave)
from .generic.date_based import MonthArchiveTest, ObjectDetailTest, DayArchiveTests
from .generic.object_list import ObjectListTest
from .generic.simple import RedirectToTest
from .i18n import JsI18NTests, I18NTests, JsI18NTestsMultiPackage from .i18n import JsI18NTests, I18NTests, JsI18NTestsMultiPackage
from .shortcuts import ShortcutTests from .shortcuts import ShortcutTests
from .specials import URLHandling from .specials import URLHandling

View File

@ -1,255 +0,0 @@
import datetime
import warnings
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from regressiontests.views.models import Article, UrlArticle
class CreateObjectTest(TestCase):
fixtures = ['testdata.json']
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.create_update')
def tearDown(self):
self.restore_warnings_state()
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 = '/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.assertTrue(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 = '/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 = '/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 = '/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,
'/create_update/view/article/some-other-slug/',
target_status_code=404)
class UpdateDeleteObjectTest(TestCase):
fixtures = ['testdata.json']
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.create_update')
def tearDown(self):
self.restore_warnings_state()
def test_update_object_form_display(self):
"""
Verifies that the form was created properly and with initial values.
"""
response = self.client.get('/create_update/update/article/old_article/')
self.assertTemplateUsed(response, 'views/article_form.html')
self.assertHTMLEqual(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('/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.assertEqual(article.title, "Another Article")
def test_delete_object_confirm(self):
"""
Verifies the confirm deletion page is displayed using a GET.
"""
response = self.client.get('/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 = '/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
urls = 'regressiontests.views.generic_urls'
create_url = '/create_update/create/article/'
update_url = '/create_update/update/article/old_article/'
delete_url = '/create_update/delete/article/old_article/'
create_redirect = '/create_update/view/article/my-first-article/'
update_redirect = '/create_update/view/article/another-article-slug/'
delete_redirect = '/create_update/'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.create_update')
def tearDown(self):
self.restore_warnings_state()
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.
"""
urls = 'regressiontests.views.generic_urls'
create_url = '/create_update/no_redirect/create/article/'
update_url = '/create_update/no_redirect/update/article/old_article/'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.create_update')
def tearDown(self):
self.restore_warnings_state()
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.
"""
urls = 'regressiontests.views.generic_urls'
# Article model with get_absolute_url method.
article_model = UrlArticle
create_url = '/create_update/no_url/create/article/'
update_url = '/create_update/no_url/update/article/old_article/'
create_redirect = '/urlarticles/my-first-article/'
update_redirect = '/urlarticles/another-article-slug/'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.create_update')
def tearDown(self):
self.restore_warnings_state()
def test_delete_article(self):
"""
The delete_object view requires a post_delete_redirect, so skip testing
here.
"""
pass

View File

@ -1,171 +0,0 @@
# coding: utf-8
import warnings
from django.test import TestCase
from datetime import datetime, date
from datetime import timedelta
from regressiontests.views.models import Article, Author, DateArticle
class ObjectDetailTest(TestCase):
fixtures = ['testdata.json']
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.date_based')
# Correct the date for the current article
current_article = Article.objects.get(title="Current Article")
current_article.date_created = datetime.now()
current_article.save()
def tearDown(self):
self.restore_warnings_state()
def test_finds_past(self):
"date_based.object_detail can view a page in the past"
response = self.client.get('/date_based/object_detail/2001/01/01/old_article/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['object'].title, "Old Article")
def test_object_detail_finds_today(self):
"date_based.object_detail can view a page from today"
today_url = datetime.now().strftime('%Y/%m/%d')
response = self.client.get('/date_based/object_detail/%s/current_article/' % today_url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['object'].title, "Current Article")
def test_object_detail_ignores_future(self):
"date_based.object_detail can view a page from the future, but only if allowed."
response = self.client.get('/date_based/object_detail/3000/01/01/future_article/')
self.assertEqual(response.status_code, 404)
def test_object_detail_allowed_future_if_enabled(self):
"date_based.object_detail can view a page from the future if explicitly allowed."
response = self.client.get('/date_based/object_detail/3000/01/01/future_article/allow_future/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['object'].title, "Future Article")
class MonthArchiveTest(TestCase):
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.date_based')
def tearDown(self):
self.restore_warnings_state()
def test_archive_month_includes_only_month(self):
"Regression for #3031: Archives around Feburary include only one month"
author = Author(name="John Smith")
author.save()
# 2004 was a leap year, so it should be weird enough to not cheat
first_second_of_feb = datetime(2004, 2, 1, 0, 0, 1)
first_second_of_mar = datetime(2004, 3, 1, 0, 0, 1)
two_seconds = timedelta(0, 2, 0)
article = Article(title="example", author=author)
article.date_created = first_second_of_feb
article.save()
response = self.client.get('/date_based/archive_month/2004/02/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['next_month'], date(2004, 3, 1))
self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
article.date_created = first_second_of_feb-two_seconds
article.save()
response = self.client.get('/date_based/archive_month/2004/02/')
self.assertEqual(response.status_code, 404)
article.date_created = first_second_of_mar-two_seconds
article.save()
response = self.client.get('/date_based/archive_month/2004/02/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['next_month'], date(2004, 3, 1))
self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
article.date_created = first_second_of_mar
article.save()
response = self.client.get('/date_based/archive_month/2004/02/')
self.assertEqual(response.status_code, 404)
article2 = DateArticle(title="example", author=author)
article2.date_created = first_second_of_feb.date()
article2.save()
response = self.client.get('/date_based/datefield/archive_month/2004/02/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['next_month'], date(2004, 3, 1))
self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
article2.date_created = (first_second_of_feb-two_seconds).date()
article2.save()
response = self.client.get('/date_based/datefield/archive_month/2004/02/')
self.assertEqual(response.status_code, 404)
article2.date_created = (first_second_of_mar-two_seconds).date()
article2.save()
response = self.client.get('/date_based/datefield/archive_month/2004/02/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['next_month'], date(2004, 3, 1))
self.assertEqual(response.context['previous_month'], date(2004, 1, 1))
article2.date_created = first_second_of_mar.date()
article2.save()
response = self.client.get('/date_based/datefield/archive_month/2004/02/')
self.assertEqual(response.status_code, 404)
now = datetime.now()
prev_month = now.date().replace(day=1)
if prev_month.month == 1:
prev_month = prev_month.replace(year=prev_month.year-1, month=12)
else:
prev_month = prev_month.replace(month=prev_month.month-1)
article2.date_created = now
article2.save()
response = self.client.get('/date_based/datefield/archive_month/%s/' % now.strftime('%Y/%m'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['next_month'], None)
self.assertEqual(response.context['previous_month'], prev_month)
def test_archive_month_date_list(self):
author = Author(name="John Smith")
author.save()
date1 = datetime(2010, 1, 1, 0, 0, 0)
date2 = datetime(2010, 1, 2, 0, 0, 0)
Article.objects.create(title='example1', author=author, date_created=date1)
Article.objects.create(title='example2', author=author, date_created=date2)
response = self.client.get('/date_based/archive_month/2010/1/')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['date_list']), 2)
self.assertEqual(response.context['date_list'][0], date1)
# Checks that the same date is not included more than once in the list
Article.objects.create(title='example2', author=author, date_created=date2)
response = self.client.get('/date_based/archive_month/2010/1/')
self.assertEqual(len(response.context['date_list']), 2)
class DayArchiveTests(TestCase):
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.date_based')
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.create_update')
def tearDown(self):
self.restore_warnings_state()
def test_year_month_day_format(self):
"""
Make sure day views don't get confused with numeric month formats (#7944)
"""
author = Author.objects.create(name="John Smith")
article = Article.objects.create(title="example", author=author, date_created=datetime(2004, 1, 21, 0, 0, 1))
response = self.client.get('/date_based/archive_day/2004/1/21/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['object_list'][0], article)

View File

@ -1,47 +0,0 @@
import warnings
from django.test import TestCase
class ObjectListTest(TestCase):
fixtures = ['testdata.json']
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.list_detail')
def tearDown(self):
self.restore_warnings_state()
def check_pagination(self, url, expected_status_code, object_count=None):
response = self.client.get(url)
self.assertEqual(response.status_code, expected_status_code)
if object_count:
self.assertEqual(response.context['is_paginated'], True)
self.assertEqual(len(response.context['page_obj'].object_list),
object_count)
return response
def test_finds_pages(self):
# Check page count doesn't start at 0.
self.check_pagination('/object_list/page0/', 404)
# Check basic pages.
self.check_pagination('/object_list/page/', 200, 2)
self.check_pagination('/object_list/page1/', 200, 2)
self.check_pagination('/object_list/page2/', 200, 1)
self.check_pagination('/object_list/page3/', 404)
# Check the special "last" page.
self.check_pagination('/object_list/pagelast/', 200, 1)
self.check_pagination('/object_list/pagenotlast/', 404)
def test_no_paginate_by(self):
# Ensure that the view isn't paginated by default.
url = '/object_list_no_paginate_by/page1/'
response = self.check_pagination(url, 200)
self.assertEqual(response.context['is_paginated'], False)

View File

@ -1,64 +0,0 @@
# coding: utf-8
import warnings
from django.test import TestCase
class RedirectToTest(TestCase):
urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.simple')
def tearDown(self):
self.restore_warnings_state()
def test_redirect_to_returns_permanent_redirect(self):
"simple.redirect_to returns a permanent redirect (301) by default"
response = self.client.get('/simple/redirect_to/')
self.assertEqual(response.status_code, 301)
self.assertEqual('http://testserver/simple/target/', response['Location'])
def test_redirect_to_can_return_a_temporary_redirect(self):
"simple.redirect_to returns a temporary redirect (302) when explicitely asked to"
response = self.client.get('/simple/redirect_to_temp/')
self.assertEqual(response.status_code, 302)
self.assertEqual('http://testserver/simple/target/', response['Location'])
def test_redirect_to_on_empty_url_returns_gone(self):
"simple.redirect_to returns resource gone (410) when given a None url"
response = self.client.get('/simple/redirect_to_none/')
self.assertEqual(response.status_code, 410)
def test_redirect_to_allows_formatted_url_string(self):
"simple.redirect_to uses string interpolation on target url for keyword args"
response = self.client.get('/simple/redirect_to_arg/42/')
self.assertEqual(response.status_code, 301)
self.assertEqual('http://testserver/simple/target_arg/42/', response['Location'])
def test_redirect_to_allows_query_string_to_be_passed(self):
"simple.redirect_to configured with query_string=True passes on any query string"
# the default is to not forward the query string
response = self.client.get('/simple/redirect_to/?param1=foo&param2=bar')
self.assertEqual(response.status_code, 301)
self.assertEqual('http://testserver/simple/target/', response['Location'])
# views configured with query_string=True however passes the query string along
response = self.client.get('/simple/redirect_to_query/?param1=foo&param2=bar')
self.assertEqual(response.status_code, 301)
self.assertEqual('http://testserver/simple/target/?param1=foo&param2=bar', response['Location'])
# Confirm that the contents of the query string are not subject to
# string interpolation (Refs #17111):
response = self.client.get('/simple/redirect_to_query/?param1=foo&param2=hist%C3%B3ria')
self.assertEqual(response.status_code, 301)
self.assertEqual('http://testserver/simple/target/?param1=foo&param2=hist%C3%B3ria', response['Location'])
response = self.client.get('/simple/redirect_to_arg_and_query/99/?param1=foo&param2=hist%C3%B3ria')
self.assertEqual(response.status_code, 301)
self.assertEqual('http://testserver/simple/target_arg/99/?param1=foo&param2=hist%C3%B3ria', response['Location'])
def test_redirect_to_when_meta_contains_no_query_string(self):
"regression for #16705"
# we can't use self.client.get because it always sets QUERY_STRING
response = self.client.request(PATH_INFO='/simple/redirect_to/')
self.assertEqual(response.status_code, 301)

View File

@ -1,5 +1,3 @@
import warnings
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
@ -11,14 +9,6 @@ from django.test.utils import override_settings
class ShortcutTests(TestCase): class ShortcutTests(TestCase):
urls = 'regressiontests.views.generic_urls' urls = 'regressiontests.views.generic_urls'
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.simple')
def tearDown(self):
self.restore_warnings_state()
def test_render_to_response(self): def test_render_to_response(self):
response = self.client.get('/shortcuts/render_to_response/') response = self.client.get('/shortcuts/render_to_response/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View File

@ -1,6 +1,4 @@
# coding: utf-8 # coding: utf-8
import warnings
from django.test import TestCase from django.test import TestCase
@ -11,14 +9,6 @@ class URLHandling(TestCase):
urls = 'regressiontests.views.generic_urls' urls = 'regressiontests.views.generic_urls'
redirect_target = "/%E4%B8%AD%E6%96%87/target/" redirect_target = "/%E4%B8%AD%E6%96%87/target/"
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.views.generic.simple')
def tearDown(self):
self.restore_warnings_state()
def test_combining_redirect(self): def test_combining_redirect(self):
""" """
Tests that redirecting to an IRI, requiring encoding before we use it Tests that redirecting to an IRI, requiring encoding before we use it

View File

@ -21,25 +21,6 @@ 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.instance.slug = 'some-other-slug'
return super(SlugChangingArticleForm, self).save(*args, **kwargs)
from django.views.generic.create_update import create_object
return create_object(request,
post_save_redirect='/create_update/view/article/%(slug)s/',
form_class=SlugChangingArticleForm)
def raises(request): def raises(request):
# Make sure that a callable that raises an exception in the stack frame's # Make sure that a callable that raises an exception in the stack frame's
# local vars won't hijack the technical 500 response. See: # local vars won't hijack the technical 500 response. See: