mirror of https://github.com/django/django.git
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:
parent
ea9dc9f4b0
commit
1858e47672
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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()
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
==============================
|
==============================
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
===========
|
===========
|
||||||
|
|
|
@ -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>`.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -24,11 +24,3 @@ API Reference
|
||||||
unicode
|
unicode
|
||||||
utils
|
utils
|
||||||
validators
|
validators
|
||||||
|
|
||||||
Deprecated features
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
generic-views
|
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
|
@ -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.
|
|
|
@ -2,4 +2,4 @@
|
||||||
Generic views
|
Generic views
|
||||||
=============
|
=============
|
||||||
|
|
||||||
See :doc:`/ref/generic-views`.
|
See :doc:`/ref/class-based-views`.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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/')
|
||||||
|
|
|
@ -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())),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)),
|
|
||||||
)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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)
|
|
|
@ -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¶m2=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¶m2=bar')
|
|
||||||
self.assertEqual(response.status_code, 301)
|
|
||||||
self.assertEqual('http://testserver/simple/target/?param1=foo¶m2=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¶m2=hist%C3%B3ria')
|
|
||||||
self.assertEqual(response.status_code, 301)
|
|
||||||
self.assertEqual('http://testserver/simple/target/?param1=foo¶m2=hist%C3%B3ria', response['Location'])
|
|
||||||
response = self.client.get('/simple/redirect_to_arg_and_query/99/?param1=foo¶m2=hist%C3%B3ria')
|
|
||||||
self.assertEqual(response.status_code, 301)
|
|
||||||
self.assertEqual('http://testserver/simple/target_arg/99/?param1=foo¶m2=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)
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue