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
|
||||
implementation of a common idiom or pattern found in view development.
|
||||
|
||||
See :doc:`/ref/generic-views`.
|
||||
See :doc:`/ref/class-based-views`.
|
||||
|
||||
model
|
||||
Models store your application's data.
|
||||
|
|
|
@ -195,7 +195,6 @@ Other batteries included
|
|||
* :doc:`Unicode in Django <ref/unicode>`
|
||||
* :doc:`Web design helpers <ref/contrib/webdesign>`
|
||||
* :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
|
||||
==============================
|
||||
|
|
|
@ -134,7 +134,7 @@ these changes.
|
|||
|
||||
* The function-based generic view modules will be removed in favor of their
|
||||
class-based equivalents, outlined :doc:`here
|
||||
</topics/generic-views-migration>`:
|
||||
</topics/class-based-views>`:
|
||||
|
||||
* The :class:`~django.core.servers.basehttp.AdminMediaHandler` will be
|
||||
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.
|
||||
|
||||
For full details on generic views, see the :doc:`generic views documentation
|
||||
</topics/http/generic-views>`.
|
||||
</topics/class-based-views>`.
|
||||
|
||||
Coming soon
|
||||
===========
|
||||
|
|
|
@ -54,7 +54,7 @@ of 1.0. This includes these APIs:
|
|||
- :doc:`HTTP request/response handling </topics/http/index>`, including file
|
||||
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>`.
|
||||
|
||||
|
|
|
@ -6,13 +6,9 @@ Class-based generic views
|
|||
|
||||
.. note::
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
@ -274,10 +274,6 @@ example::
|
|||
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::
|
||||
Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would
|
||||
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
|
||||
|
||||
The :class:`django.contrib.sitemaps.GenericSitemap` class works with any
|
||||
:doc:`generic views </ref/generic-views>` you already have.
|
||||
To use it, create an instance, passing in the same :data:`info_dict` you pass to
|
||||
the generic views. The only requirement is that the dictionary have a
|
||||
:data:`queryset` entry. It may also have a :data:`date_field` entry that specifies a
|
||||
date field for objects retrieved from the :data:`queryset`. This will be used for
|
||||
the :attr:`~Sitemap.lastmod` attribute in the generated sitemap. You may
|
||||
also pass :attr:`~Sitemap.priority` and :attr:`~Sitemap.changefreq`
|
||||
keyword arguments to the :class:`~django.contrib.sitemaps.GenericSitemap`
|
||||
constructor to specify these attributes for all URLs.
|
||||
The :class:`django.contrib.sitemaps.GenericSitemap` class allows you to
|
||||
create a sitemap by passing it a dictionary which has to contain at least
|
||||
a :data:`queryset` entry. This queryset will be used to generate the items
|
||||
of the sitemap. It may also have a :data:`date_field` entry that
|
||||
specifies a date field for objects retrieved from the :data:`queryset`.
|
||||
This will be used for the :attr:`~Sitemap.lastmod` attribute in the
|
||||
generated sitemap. You may also pass :attr:`~Sitemap.priority` and
|
||||
:attr:`~Sitemap.changefreq` keyword arguments to the
|
||||
:class:`~django.contrib.sitemaps.GenericSitemap` constructor to specify
|
||||
these attributes for all URLs.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,11 +24,3 @@ API Reference
|
|||
unicode
|
||||
utils
|
||||
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
|
||||
visible fields on the form, respectively.
|
||||
|
||||
* The ``redirect_to`` generic view (see :doc:`the generic views documentation
|
||||
</ref/generic-views>`) now accepts an additional keyword argument
|
||||
* The ``redirect_to`` generic view
|
||||
now accepts an additional keyword argument
|
||||
``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP
|
||||
permanent redirect (status code 301). If ``False``, the view will emit an HTTP
|
||||
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
|
||||
visible fields on the form, respectively.
|
||||
|
||||
* The ``redirect_to`` generic view (see :doc:`the generic views documentation
|
||||
</ref/generic-views>`) now accepts an additional keyword argument
|
||||
* The ``redirect_to`` generic view
|
||||
now accepts an additional keyword argument
|
||||
``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP
|
||||
permanent redirect (status code 301). If ``False``, the view will emit an HTTP
|
||||
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
|
||||
</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
|
||||
views</topics/generic-views-migration>`.
|
||||
help you `convert your function-based generic views to class-based
|
||||
views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_.
|
||||
|
||||
Logging
|
||||
~~~~~~~
|
||||
|
|
|
@ -81,9 +81,9 @@ used as the basis for reusable applications that can be easily
|
|||
extended.
|
||||
|
||||
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
|
||||
views</topics/generic-views-migration>`.
|
||||
views <https://docs.djangoproject.com/en/1.4/topics/generic-views-migration/>`_.
|
||||
|
||||
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>`
|
||||
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
|
||||
|
|
|
@ -6,13 +6,9 @@ Class-based generic views
|
|||
|
||||
.. note::
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
=============
|
||||
|
||||
See :doc:`/ref/generic-views`.
|
||||
See :doc:`/ref/class-based-views`.
|
||||
|
|
|
@ -416,8 +416,8 @@ Old::
|
|||
from django.conf.urls import patterns, url, include
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', 'django.views.generic.date_based.archive_index'),
|
||||
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'django.views.generic.date_based.archive_month'),
|
||||
(r'^$', 'myapp.views.app_index'),
|
||||
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'myapp.views.month_display'),
|
||||
(r'^tag/(?P<tag>\w+)/$', 'weblog.views.tag'),
|
||||
)
|
||||
|
||||
|
@ -425,9 +425,9 @@ New::
|
|||
|
||||
from django.conf.urls import patterns, url, include
|
||||
|
||||
urlpatterns = patterns('django.views.generic.date_based',
|
||||
(r'^$', 'archive_index'),
|
||||
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$','archive_month'),
|
||||
urlpatterns = patterns('myapp.views',
|
||||
(r'^$', 'app_index'),
|
||||
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$','month_display'),
|
||||
)
|
||||
|
||||
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'
|
||||
|
||||
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
|
||||
options to views.
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ Introductions to all the key parts of Django you'll need to know:
|
|||
forms/index
|
||||
templates
|
||||
class-based-views
|
||||
generic-views-migration
|
||||
files
|
||||
testing
|
||||
auth
|
||||
|
@ -27,11 +26,3 @@ Introductions to all the key parts of Django you'll need to know:
|
|||
serialization
|
||||
settings
|
||||
signals
|
||||
|
||||
Deprecated features
|
||||
-------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
generic-views
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import warnings
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
|
||||
|
@ -8,14 +6,6 @@ class SpecialHeadersTest(TestCase):
|
|||
fixtures = ['data.xml']
|
||||
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):
|
||||
user = User.objects.get(username='super')
|
||||
response = self.client.get('/special_headers/article/1/')
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.views.generic.list_detail import object_detail
|
||||
|
||||
from . import views
|
||||
from .models import Article
|
||||
|
||||
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/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.utils.decorators import decorator_from_middleware
|
||||
from django.views.generic import View
|
||||
from django.middleware.doc import XViewMiddleware
|
||||
|
||||
from .models import Article
|
||||
|
||||
xview_dec = decorator_from_middleware(XViewMiddleware)
|
||||
|
||||
def xview(request):
|
||||
return HttpResponse()
|
||||
|
||||
def xview_xheaders(request, object_id):
|
||||
response = HttpResponse()
|
||||
populate_xheaders(request, response, Article, 1)
|
||||
return response
|
||||
|
||||
class XViewClass(View):
|
||||
def get(self, request):
|
||||
return HttpResponse()
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from . import views
|
||||
from .models import Article, DateArticle, UrlArticle
|
||||
|
@ -35,65 +36,12 @@ urlpatterns = patterns('',
|
|||
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
|
||||
urlpatterns += patterns('django.views.generic.simple',
|
||||
('^nonascii_redirect/$', 'redirect_to',
|
||||
{'url': u'/中文/target/', 'permanent': False}),
|
||||
('^permanent_nonascii_redirect/$', 'redirect_to',
|
||||
{'url': u'/中文/target/', 'permanent': True}),
|
||||
urlpatterns += patterns('',
|
||||
('^nonascii_redirect/$', RedirectView.as_view(
|
||||
url=u'/中文/target/', permanent=False)),
|
||||
('^permanent_nonascii_redirect/$', RedirectView.as_view(
|
||||
url=u'/中文/target/', permanent=True)),
|
||||
)
|
||||
|
||||
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_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,
|
||||
AjaxResponseExceptionReporterFilter)
|
||||
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 .shortcuts import ShortcutTests
|
||||
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.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
@ -11,14 +9,6 @@ from django.test.utils import override_settings
|
|||
class ShortcutTests(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_render_to_response(self):
|
||||
response = self.client.get('/shortcuts/render_to_response/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# coding: utf-8
|
||||
import warnings
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
@ -11,14 +9,6 @@ class URLHandling(TestCase):
|
|||
urls = 'regressiontests.views.generic_urls'
|
||||
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):
|
||||
"""
|
||||
Tests that redirecting to an IRI, requiring encoding before we use it
|
||||
|
|
|
@ -21,25 +21,6 @@ def index_page(request):
|
|||
"""Dummy index page"""
|
||||
return HttpResponse('<html><body>Dummy page</body></html>')
|
||||
|
||||
def custom_create(request):
|
||||
"""
|
||||
Calls create_object generic view with a custom form class.
|
||||
"""
|
||||
class SlugChangingArticleForm(forms.ModelForm):
|
||||
"""Custom form class to overwrite the slug."""
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.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):
|
||||
# Make sure that a callable that raises an exception in the stack frame's
|
||||
# local vars won't hijack the technical 500 response. See:
|
||||
|
|
Loading…
Reference in New Issue