测试gitnore

This commit is contained in:
ladeng07
2022-05-06 15:45:57 +08:00
parent 12f390949b
commit 51552904f9
2347 changed files with 120102 additions and 53549 deletions
@@ -1,39 +1,22 @@
from django.views.generic.base import RedirectView, TemplateView, View
from django.views.generic.dates import (
ArchiveIndexView,
DateDetailView,
DayArchiveView,
MonthArchiveView,
TodayArchiveView,
WeekArchiveView,
YearArchiveView,
ArchiveIndexView, DateDetailView, DayArchiveView, MonthArchiveView,
TodayArchiveView, WeekArchiveView, YearArchiveView,
)
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.views.generic.edit import (
CreateView, DeleteView, FormView, UpdateView,
)
from django.views.generic.list import ListView
__all__ = [
"View",
"TemplateView",
"RedirectView",
"ArchiveIndexView",
"YearArchiveView",
"MonthArchiveView",
"WeekArchiveView",
"DayArchiveView",
"TodayArchiveView",
"DateDetailView",
"DetailView",
"FormView",
"CreateView",
"UpdateView",
"DeleteView",
"ListView",
"GenericViewError",
'View', 'TemplateView', 'RedirectView', 'ArchiveIndexView',
'YearArchiveView', 'MonthArchiveView', 'WeekArchiveView', 'DayArchiveView',
'TodayArchiveView', 'DateDetailView', 'DetailView', 'FormView',
'CreateView', 'UpdateView', 'DeleteView', 'ListView', 'GenericViewError',
]
class GenericViewError(Exception):
"""A problem in a generic view."""
pass
@@ -1,18 +1,16 @@
import logging
from functools import update_wrapper
from django.core.exceptions import ImproperlyConfigured
from django.http import (
HttpResponse,
HttpResponseGone,
HttpResponseNotAllowed,
HttpResponsePermanentRedirect,
HttpResponseRedirect,
HttpResponse, HttpResponseGone, HttpResponseNotAllowed,
HttpResponsePermanentRedirect, HttpResponseRedirect,
)
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.decorators import classonlymethod
logger = logging.getLogger("django.request")
logger = logging.getLogger('django.request')
class ContextMixin:
@@ -20,11 +18,10 @@ class ContextMixin:
A default context mixin that passes the keyword arguments received by
get_context_data() as the template context.
"""
extra_context = None
def get_context_data(self, **kwargs):
kwargs.setdefault("view", self)
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
@@ -36,16 +33,7 @@ class View:
dispatch-by-method and simple sanity checking.
"""
http_method_names = [
"get",
"post",
"put",
"patch",
"delete",
"head",
"options",
"trace",
]
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
@@ -63,44 +51,37 @@ class View:
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
"The method name %s is not accepted as a keyword argument "
"to %s()." % (key, cls.__name__)
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError(
"%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key)
)
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, "request"):
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# __name__ and __qualname__ are intentionally left unchanged as
# view_class should be used to robustly determine the name of the view
# instead.
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__annotations__ = cls.dispatch.__annotations__
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
# the dispatch method.
view.__dict__.update(cls.dispatch.__dict__)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, "get") and not hasattr(self, "head"):
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
@@ -111,27 +92,23 @@ class View:
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
"Method Not Allowed (%s): %s",
request.method,
request.path,
extra={"status_code": 405, "request": request},
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response.headers["Allow"] = ", ".join(self._allowed_methods())
response.headers["Content-Length"] = "0"
response.headers['Allow'] = ', '.join(self._allowed_methods())
response.headers['Content-Length'] = '0'
return response
def _allowed_methods(self):
@@ -140,7 +117,6 @@ class View:
class TemplateResponseMixin:
"""A mixin that can be used to render a template."""
template_name = None
template_engine = None
response_class = TemplateResponse
@@ -153,13 +129,13 @@ class TemplateResponseMixin:
Pass response_kwargs to the constructor of the response class.
"""
response_kwargs.setdefault("content_type", self.content_type)
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs,
**response_kwargs
)
def get_template_names(self):
@@ -170,8 +146,7 @@ class TemplateResponseMixin:
if self.template_name is None:
raise ImproperlyConfigured(
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'"
)
"'template_name' or an implementation of 'get_template_names()'")
else:
return [self.template_name]
@@ -180,7 +155,6 @@ class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
@@ -188,7 +162,6 @@ class TemplateView(TemplateResponseMixin, ContextMixin, View):
class RedirectView(View):
"""Provide a redirect on any GET request."""
permanent = False
url = None
pattern_name = None
@@ -207,7 +180,7 @@ class RedirectView(View):
else:
return None
args = self.request.META.get("QUERY_STRING", "")
args = self.request.META.get('QUERY_STRING', '')
if args and self.query_string:
url = "%s?%s" % (url, args)
return url
@@ -221,7 +194,8 @@ class RedirectView(View):
return HttpResponseRedirect(url)
else:
logger.warning(
"Gone: %s", request.path, extra={"status_code": 410, "request": request}
'Gone: %s', request.path,
extra={'status_code': 410, 'request': request}
)
return HttpResponseGone()
@@ -9,19 +9,16 @@ from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from django.views.generic.base import View
from django.views.generic.detail import (
BaseDetailView,
SingleObjectTemplateResponseMixin,
BaseDetailView, SingleObjectTemplateResponseMixin,
)
from django.views.generic.list import (
MultipleObjectMixin,
MultipleObjectTemplateResponseMixin,
MultipleObjectMixin, MultipleObjectTemplateResponseMixin,
)
class YearMixin:
"""Mixin for views manipulating year-based data."""
year_format = "%Y"
year_format = '%Y'
year = None
def get_year_format(self):
@@ -36,21 +33,21 @@ class YearMixin:
year = self.year
if year is None:
try:
year = self.kwargs["year"]
year = self.kwargs['year']
except KeyError:
try:
year = self.request.GET["year"]
year = self.request.GET['year']
except KeyError:
raise Http404(_("No year specified"))
return year
def get_next_year(self, date):
"""Get the next valid year."""
return _get_next_prev(self, date, is_previous=False, period="year")
return _get_next_prev(self, date, is_previous=False, period='year')
def get_previous_year(self, date):
"""Get the previous valid year."""
return _get_next_prev(self, date, is_previous=True, period="year")
return _get_next_prev(self, date, is_previous=True, period='year')
def _get_next_year(self, date):
"""
@@ -70,8 +67,7 @@ class YearMixin:
class MonthMixin:
"""Mixin for views manipulating month-based data."""
month_format = "%b"
month_format = '%b'
month = None
def get_month_format(self):
@@ -86,21 +82,21 @@ class MonthMixin:
month = self.month
if month is None:
try:
month = self.kwargs["month"]
month = self.kwargs['month']
except KeyError:
try:
month = self.request.GET["month"]
month = self.request.GET['month']
except KeyError:
raise Http404(_("No month specified"))
return month
def get_next_month(self, date):
"""Get the next valid month."""
return _get_next_prev(self, date, is_previous=False, period="month")
return _get_next_prev(self, date, is_previous=False, period='month')
def get_previous_month(self, date):
"""Get the previous valid month."""
return _get_next_prev(self, date, is_previous=True, period="month")
return _get_next_prev(self, date, is_previous=True, period='month')
def _get_next_month(self, date):
"""
@@ -123,8 +119,7 @@ class MonthMixin:
class DayMixin:
"""Mixin for views manipulating day-based data."""
day_format = "%d"
day_format = '%d'
day = None
def get_day_format(self):
@@ -139,21 +134,21 @@ class DayMixin:
day = self.day
if day is None:
try:
day = self.kwargs["day"]
day = self.kwargs['day']
except KeyError:
try:
day = self.request.GET["day"]
day = self.request.GET['day']
except KeyError:
raise Http404(_("No day specified"))
return day
def get_next_day(self, date):
"""Get the next valid day."""
return _get_next_prev(self, date, is_previous=False, period="day")
return _get_next_prev(self, date, is_previous=False, period='day')
def get_previous_day(self, date):
"""Get the previous valid day."""
return _get_next_prev(self, date, is_previous=True, period="day")
return _get_next_prev(self, date, is_previous=True, period='day')
def _get_next_day(self, date):
"""
@@ -170,8 +165,7 @@ class DayMixin:
class WeekMixin:
"""Mixin for views manipulating week-based data."""
week_format = "%U"
week_format = '%U'
week = None
def get_week_format(self):
@@ -186,21 +180,21 @@ class WeekMixin:
week = self.week
if week is None:
try:
week = self.kwargs["week"]
week = self.kwargs['week']
except KeyError:
try:
week = self.request.GET["week"]
week = self.request.GET['week']
except KeyError:
raise Http404(_("No week specified"))
return week
def get_next_week(self, date):
"""Get the next valid week."""
return _get_next_prev(self, date, is_previous=False, period="week")
return _get_next_prev(self, date, is_previous=False, period='week')
def get_previous_week(self, date):
"""Get the previous valid week."""
return _get_next_prev(self, date, is_previous=True, period="week")
return _get_next_prev(self, date, is_previous=True, period='week')
def _get_next_week(self, date):
"""
@@ -224,9 +218,9 @@ class WeekMixin:
The first day according to the week format is 0 and the last day is 6.
"""
week_format = self.get_week_format()
if week_format in {"%W", "%V"}: # week starts on Monday
if week_format in {'%W', '%V'}: # week starts on Monday
return date.weekday()
elif week_format == "%U": # week starts on Sunday
elif week_format == '%U': # week starts on Sunday
return (date.weekday() + 1) % 7
else:
raise ValueError("unknown week format: %s" % week_format)
@@ -234,16 +228,13 @@ class WeekMixin:
class DateMixin:
"""Mixin class for views manipulating date-based data."""
date_field = None
allow_future = False
def get_date_field(self):
"""Get the name of the date field to be used to filter by."""
if self.date_field is None:
raise ImproperlyConfigured(
"%s.date_field is required." % self.__class__.__name__
)
raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__)
return self.date_field
def get_allow_future(self):
@@ -291,8 +282,8 @@ class DateMixin:
since = self._make_date_lookup_arg(date)
until = self._make_date_lookup_arg(date + datetime.timedelta(days=1))
return {
"%s__gte" % date_field: since,
"%s__lt" % date_field: until,
'%s__gte' % date_field: since,
'%s__lt' % date_field: until,
}
else:
# Skip self._make_date_lookup_arg, it's a no-op in this branch.
@@ -301,29 +292,28 @@ class DateMixin:
class BaseDateListView(MultipleObjectMixin, DateMixin, View):
"""Abstract base class for date-based views displaying a list of objects."""
allow_empty = False
date_list_period = "year"
date_list_period = 'year'
def get(self, request, *args, **kwargs):
self.date_list, self.object_list, extra_context = self.get_dated_items()
context = self.get_context_data(
object_list=self.object_list, date_list=self.date_list, **extra_context
object_list=self.object_list,
date_list=self.date_list,
**extra_context
)
return self.render_to_response(context)
def get_dated_items(self):
"""Obtain the list of dates and items."""
raise NotImplementedError(
"A DateView must provide an implementation of get_dated_items()"
)
raise NotImplementedError('A DateView must provide an implementation of get_dated_items()')
def get_ordering(self):
"""
Return the field or fields to use for ordering the queryset; use the
date field by default.
"""
return "-%s" % self.get_date_field() if self.ordering is None else self.ordering
return '-%s' % self.get_date_field() if self.ordering is None else self.ordering
def get_dated_queryset(self, **lookup):
"""
@@ -338,19 +328,16 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
if not allow_future:
now = timezone.now() if self.uses_datetime_field else timezone_today()
qs = qs.filter(**{"%s__lte" % date_field: now})
qs = qs.filter(**{'%s__lte' % date_field: now})
if not allow_empty:
# When pagination is enabled, it's better to do a cheap query
# than to load the unpaginated queryset in memory.
is_empty = not qs if paginate_by is None else not qs.exists()
if is_empty:
raise Http404(
_("No %(verbose_name_plural)s available")
% {
"verbose_name_plural": qs.model._meta.verbose_name_plural,
}
)
raise Http404(_("No %(verbose_name_plural)s available") % {
'verbose_name_plural': qs.model._meta.verbose_name_plural,
})
return qs
@@ -361,7 +348,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
"""
return self.date_list_period
def get_date_list(self, queryset, date_type=None, ordering="ASC"):
def get_date_list(self, queryset, date_type=None, ordering='ASC'):
"""
Get a date list by calling `queryset.dates/datetimes()`, checking
along the way for empty lists that aren't allowed.
@@ -377,9 +364,8 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
date_list = queryset.dates(date_field, date_type, ordering)
if date_list is not None and not date_list and not allow_empty:
raise Http404(
_("No %(verbose_name_plural)s available")
% {
"verbose_name_plural": queryset.model._meta.verbose_name_plural,
_("No %(verbose_name_plural)s available") % {
'verbose_name_plural': queryset.model._meta.verbose_name_plural,
}
)
@@ -390,13 +376,12 @@ class BaseArchiveIndexView(BaseDateListView):
"""
Base class for archives of date-based items. Requires a response mixin.
"""
context_object_name = "latest"
context_object_name = 'latest'
def get_dated_items(self):
"""Return (date_list, items, extra_context) for this request."""
qs = self.get_dated_queryset()
date_list = self.get_date_list(qs, ordering="DESC")
date_list = self.get_date_list(qs, ordering='DESC')
if not date_list:
qs = qs.none()
@@ -406,14 +391,12 @@ class BaseArchiveIndexView(BaseDateListView):
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
"""Top-level archive of date-based items."""
template_name_suffix = "_archive"
template_name_suffix = '_archive'
class BaseYearArchiveView(YearMixin, BaseDateListView):
"""List of objects published in a given year."""
date_list_period = "month"
date_list_period = 'month'
make_object_list = False
def get_dated_items(self):
@@ -426,8 +409,8 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
since = self._make_date_lookup_arg(date)
until = self._make_date_lookup_arg(self._get_next_year(date))
lookup_kwargs = {
"%s__gte" % date_field: since,
"%s__lt" % date_field: until,
'%s__gte' % date_field: since,
'%s__lt' % date_field: until,
}
qs = self.get_dated_queryset(**lookup_kwargs)
@@ -438,15 +421,11 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
# to find information about the model.
qs = qs.none()
return (
date_list,
qs,
{
"year": date,
"next_year": self.get_next_year(date),
"previous_year": self.get_previous_year(date),
},
)
return (date_list, qs, {
'year': date,
'next_year': self.get_next_year(date),
'previous_year': self.get_previous_year(date),
})
def get_make_object_list(self):
"""
@@ -458,14 +437,12 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView):
"""List of objects published in a given year."""
template_name_suffix = "_archive_year"
template_name_suffix = '_archive_year'
class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
"""List of objects published in a given month."""
date_list_period = "day"
date_list_period = 'day'
def get_dated_items(self):
"""Return (date_list, items, extra_context) for this request."""
@@ -473,35 +450,29 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
month = self.get_month()
date_field = self.get_date_field()
date = _date_from_string(
year, self.get_year_format(), month, self.get_month_format()
)
date = _date_from_string(year, self.get_year_format(),
month, self.get_month_format())
since = self._make_date_lookup_arg(date)
until = self._make_date_lookup_arg(self._get_next_month(date))
lookup_kwargs = {
"%s__gte" % date_field: since,
"%s__lt" % date_field: until,
'%s__gte' % date_field: since,
'%s__lt' % date_field: until,
}
qs = self.get_dated_queryset(**lookup_kwargs)
date_list = self.get_date_list(qs)
return (
date_list,
qs,
{
"month": date,
"next_month": self.get_next_month(date),
"previous_month": self.get_previous_month(date),
},
)
return (date_list, qs, {
'month': date,
'next_month': self.get_next_month(date),
'previous_month': self.get_previous_month(date),
})
class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView):
"""List of objects published in a given month."""
template_name_suffix = "_archive_month"
template_name_suffix = '_archive_month'
class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
@@ -514,71 +485,55 @@ class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
date_field = self.get_date_field()
week_format = self.get_week_format()
week_choices = {"%W": "1", "%U": "0", "%V": "1"}
week_choices = {'%W': '1', '%U': '0', '%V': '1'}
try:
week_start = week_choices[week_format]
except KeyError:
raise ValueError(
"Unknown week format %r. Choices are: %s"
% (
week_format,
", ".join(sorted(week_choices)),
)
)
raise ValueError('Unknown week format %r. Choices are: %s' % (
week_format,
', '.join(sorted(week_choices)),
))
year_format = self.get_year_format()
if week_format == "%V" and year_format != "%G":
if week_format == '%V' and year_format != '%G':
raise ValueError(
"ISO week directive '%s' is incompatible with the year "
"directive '%s'. Use the ISO year '%%G' instead."
% (
week_format,
year_format,
"directive '%s'. Use the ISO year '%%G' instead." % (
week_format, year_format,
)
)
date = _date_from_string(year, year_format, week_start, "%w", week, week_format)
date = _date_from_string(year, year_format, week_start, '%w', week, week_format)
since = self._make_date_lookup_arg(date)
until = self._make_date_lookup_arg(self._get_next_week(date))
lookup_kwargs = {
"%s__gte" % date_field: since,
"%s__lt" % date_field: until,
'%s__gte' % date_field: since,
'%s__lt' % date_field: until,
}
qs = self.get_dated_queryset(**lookup_kwargs)
return (
None,
qs,
{
"week": date,
"next_week": self.get_next_week(date),
"previous_week": self.get_previous_week(date),
},
)
return (None, qs, {
'week': date,
'next_week': self.get_next_week(date),
'previous_week': self.get_previous_week(date),
})
class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView):
"""List of objects published in a given week."""
template_name_suffix = "_archive_week"
template_name_suffix = '_archive_week'
class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView):
"""List of objects published on a given day."""
def get_dated_items(self):
"""Return (date_list, items, extra_context) for this request."""
year = self.get_year()
month = self.get_month()
day = self.get_day()
date = _date_from_string(
year,
self.get_year_format(),
month,
self.get_month_format(),
day,
self.get_day_format(),
)
date = _date_from_string(year, self.get_year_format(),
month, self.get_month_format(),
day, self.get_day_format())
return self._get_dated_items(date)
@@ -590,22 +545,17 @@ class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView):
lookup_kwargs = self._make_single_date_lookup(date)
qs = self.get_dated_queryset(**lookup_kwargs)
return (
None,
qs,
{
"day": date,
"previous_day": self.get_previous_day(date),
"next_day": self.get_next_day(date),
"previous_month": self.get_previous_month(date),
"next_month": self.get_next_month(date),
},
)
return (None, qs, {
'day': date,
'previous_day': self.get_previous_day(date),
'next_day': self.get_next_day(date),
'previous_month': self.get_previous_month(date),
'next_month': self.get_next_month(date)
})
class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView):
"""List of objects published on a given day."""
template_name_suffix = "_archive_day"
@@ -619,7 +569,6 @@ class BaseTodayArchiveView(BaseDayArchiveView):
class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView):
"""List of objects published today."""
template_name_suffix = "_archive_day"
@@ -628,35 +577,26 @@ class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailV
Detail view of a single object on a single date; this differs from the
standard DetailView by accepting a year/month/day in the URL.
"""
def get_object(self, queryset=None):
"""Get the object this request displays."""
year = self.get_year()
month = self.get_month()
day = self.get_day()
date = _date_from_string(
year,
self.get_year_format(),
month,
self.get_month_format(),
day,
self.get_day_format(),
)
date = _date_from_string(year, self.get_year_format(),
month, self.get_month_format(),
day, self.get_day_format())
# Use a custom queryset if provided
qs = self.get_queryset() if queryset is None else queryset
if not self.get_allow_future() and date > datetime.date.today():
raise Http404(
_(
"Future %(verbose_name_plural)s not available because "
"%(class_name)s.allow_future is False."
)
% {
"verbose_name_plural": qs.model._meta.verbose_name_plural,
"class_name": self.__class__.__name__,
}
)
raise Http404(_(
"Future %(verbose_name_plural)s not available because "
"%(class_name)s.allow_future is False."
) % {
'verbose_name_plural': qs.model._meta.verbose_name_plural,
'class_name': self.__class__.__name__,
})
# Filter down a queryset from self.queryset using the date from the
# URL. This'll get passed as the queryset to DetailView.get_object,
@@ -672,13 +612,10 @@ class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView):
Detail view of a single object on a single date; this differs from the
standard DetailView by accepting a year/month/day in the URL.
"""
template_name_suffix = "_detail"
template_name_suffix = '_detail'
def _date_from_string(
year, year_format, month="", month_format="", day="", day_format="", delim="__"
):
def _date_from_string(year, year_format, month='', month_format='', day='', day_format='', delim='__'):
"""
Get a datetime.date object given a format string and a year, month, and day
(only year is mandatory). Raise a 404 for an invalid date.
@@ -688,13 +625,10 @@ def _date_from_string(
try:
return datetime.datetime.strptime(datestr, format).date()
except ValueError:
raise Http404(
_("Invalid date string “%(datestr)s” given format “%(format)s")
% {
"datestr": datestr,
"format": format,
}
)
raise Http404(_('Invalid date string “%(datestr)s” given format “%(format)s') % {
'datestr': datestr,
'format': format,
})
def _get_next_prev(generic_view, date, is_previous, period):
@@ -727,8 +661,8 @@ def _get_next_prev(generic_view, date, is_previous, period):
allow_empty = generic_view.get_allow_empty()
allow_future = generic_view.get_allow_future()
get_current = getattr(generic_view, "_get_current_%s" % period)
get_next = getattr(generic_view, "_get_next_%s" % period)
get_current = getattr(generic_view, '_get_current_%s' % period)
get_next = getattr(generic_view, '_get_next_%s' % period)
# Bounds of the current interval
start, end = get_current(date), get_next(date)
@@ -752,10 +686,10 @@ def _get_next_prev(generic_view, date, is_previous, period):
# Construct a lookup and an ordering depending on whether we're doing
# a previous date or a next date lookup.
if is_previous:
lookup = {"%s__lt" % date_field: generic_view._make_date_lookup_arg(start)}
ordering = "-%s" % date_field
lookup = {'%s__lt' % date_field: generic_view._make_date_lookup_arg(start)}
ordering = '-%s' % date_field
else:
lookup = {"%s__gte" % date_field: generic_view._make_date_lookup_arg(end)}
lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(end)}
ordering = date_field
# Filter out objects in the future if appropriate.
@@ -766,7 +700,7 @@ def _get_next_prev(generic_view, date, is_previous, period):
now = timezone.now()
else:
now = timezone_today()
lookup["%s__lte" % date_field] = now
lookup['%s__lte' % date_field] = now
qs = generic_view.get_queryset().filter(**lookup).order_by(ordering)
@@ -9,13 +9,12 @@ class SingleObjectMixin(ContextMixin):
"""
Provide the ability to retrieve a single object for further manipulation.
"""
model = None
queryset = None
slug_field = "slug"
slug_field = 'slug'
context_object_name = None
slug_url_kwarg = "slug"
pk_url_kwarg = "pk"
slug_url_kwarg = 'slug'
pk_url_kwarg = 'pk'
query_pk_and_slug = False
def get_object(self, queryset=None):
@@ -52,10 +51,8 @@ class SingleObjectMixin(ContextMixin):
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(
_("No %(verbose_name)s found matching the query")
% {"verbose_name": queryset.model._meta.verbose_name}
)
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_queryset(self):
@@ -72,7 +69,9 @@ class SingleObjectMixin(ContextMixin):
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
return self.queryset.all()
@@ -93,7 +92,7 @@ class SingleObjectMixin(ContextMixin):
"""Insert the single object into the context dict."""
context = {}
if self.object:
context["object"] = self.object
context['object'] = self.object
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
@@ -103,7 +102,6 @@ class SingleObjectMixin(ContextMixin):
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
@@ -112,7 +110,7 @@ class BaseDetailView(SingleObjectMixin, View):
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
template_name_field = None
template_name_suffix = "_detail"
template_name_suffix = '_detail'
def get_template_names(self):
"""
@@ -143,25 +141,17 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
# only use this if the object in question is a model.
if isinstance(self.object, models.Model):
object_meta = self.object._meta
names.append(
"%s/%s%s.html"
% (
object_meta.app_label,
object_meta.model_name,
self.template_name_suffix,
)
)
elif getattr(self, "model", None) is not None and issubclass(
self.model, models.Model
):
names.append(
"%s/%s%s.html"
% (
self.model._meta.app_label,
self.model._meta.model_name,
self.template_name_suffix,
)
)
names.append("%s/%s%s.html" % (
object_meta.app_label,
object_meta.model_name,
self.template_name_suffix
))
elif getattr(self, 'model', None) is not None and issubclass(self.model, models.Model):
names.append("%s/%s%s.html" % (
self.model._meta.app_label,
self.model._meta.model_name,
self.template_name_suffix
))
# If we still haven't managed to find any template names, we should
# re-raise the ImproperlyConfigured to alert the user.
@@ -1,20 +1,14 @@
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.forms import Form
from django.forms import models as model_forms
from django.http import HttpResponseRedirect
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
from django.views.generic.detail import (
BaseDetailView,
SingleObjectMixin,
SingleObjectTemplateResponseMixin,
BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin,
)
class FormMixin(ContextMixin):
"""Provide a way to show and handle a form in a request."""
initial = {}
form_class = None
success_url = None
@@ -41,17 +35,15 @@ class FormMixin(ContextMixin):
def get_form_kwargs(self):
"""Return the keyword arguments for instantiating the form."""
kwargs = {
"initial": self.get_initial(),
"prefix": self.get_prefix(),
'initial': self.get_initial(),
'prefix': self.get_prefix(),
}
if self.request.method in ("POST", "PUT"):
kwargs.update(
{
"data": self.request.POST,
"files": self.request.FILES,
}
)
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_success_url(self):
@@ -70,14 +62,13 @@ class FormMixin(ContextMixin):
def get_context_data(self, **kwargs):
"""Insert the form into the context dict."""
if "form" not in kwargs:
kwargs["form"] = self.get_form()
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super().get_context_data(**kwargs)
class ModelFormMixin(FormMixin, SingleObjectMixin):
"""Provide a way to show and handle a ModelForm in a request."""
fields = None
def get_form_class(self):
@@ -92,7 +83,7 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif getattr(self, "object", None) is not None:
elif getattr(self, 'object', None) is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
@@ -112,8 +103,8 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
def get_form_kwargs(self):
"""Return the keyword arguments for instantiating the form."""
kwargs = super().get_form_kwargs()
if hasattr(self, "object"):
kwargs.update({"instance": self.object})
if hasattr(self, 'object'):
kwargs.update({'instance': self.object})
return kwargs
def get_success_url(self):
@@ -126,8 +117,7 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
except AttributeError:
raise ImproperlyConfigured(
"No URL to redirect to. Either provide a url or define"
" a get_absolute_url method on the Model."
)
" a get_absolute_url method on the Model.")
return url
def form_valid(self, form):
@@ -138,7 +128,6 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
class ProcessFormView(View):
"""Render a form on GET and processes it on POST."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
@@ -174,7 +163,6 @@ class BaseCreateView(ModelFormMixin, ProcessFormView):
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = None
return super().get(request, *args, **kwargs)
@@ -188,8 +176,7 @@ class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object, with a response rendered by a template.
"""
template_name_suffix = "_form"
template_name_suffix = '_form'
class BaseUpdateView(ModelFormMixin, ProcessFormView):
@@ -198,7 +185,6 @@ class BaseUpdateView(ModelFormMixin, ProcessFormView):
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
@@ -210,13 +196,11 @@ class BaseUpdateView(ModelFormMixin, ProcessFormView):
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
"""View for updating an object, with a response rendered by a template."""
template_name_suffix = "_form"
template_name_suffix = '_form'
class DeletionMixin:
"""Provide the ability to delete objects."""
success_url = None
def delete(self, request, *args, **kwargs):
@@ -237,58 +221,21 @@ class DeletionMixin:
if self.success_url:
return self.success_url.format(**self.object.__dict__)
else:
raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
raise ImproperlyConfigured(
"No URL to redirect to. Provide a success_url.")
# RemovedInDjango50Warning.
class DeleteViewCustomDeleteWarning(Warning):
pass
class BaseDeleteView(DeletionMixin, FormMixin, BaseDetailView):
class BaseDeleteView(DeletionMixin, BaseDetailView):
"""
Base view for deleting an object.
Using this base class requires subclassing to provide a response mixin.
"""
form_class = Form
def __init__(self, *args, **kwargs):
# RemovedInDjango50Warning.
if self.__class__.delete is not DeletionMixin.delete:
warnings.warn(
f"DeleteView uses FormMixin to handle POST requests. As a "
f"consequence, any custom deletion logic in "
f"{self.__class__.__name__}.delete() handler should be moved "
f"to form_valid().",
DeleteViewCustomDeleteWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)
def post(self, request, *args, **kwargs):
# Set self.object before the usual form processing flow.
# Inlined because having DeletionMixin as the first base, for
# get_success_url(), makes leveraging super() with ProcessFormView
# overly complex.
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView):
"""
View for deleting an object retrieved with self.get_object(), with a
response rendered by a template.
"""
template_name_suffix = "_confirm_delete"
template_name_suffix = '_confirm_delete'
@@ -8,7 +8,6 @@ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
class MultipleObjectMixin(ContextMixin):
"""A mixin for views manipulating multiple objects."""
allow_empty = True
queryset = None
model = None
@@ -16,7 +15,7 @@ class MultipleObjectMixin(ContextMixin):
paginate_orphans = 0
context_object_name = None
paginator_class = Paginator
page_kwarg = "page"
page_kwarg = 'page'
ordering = None
def get_queryset(self):
@@ -36,7 +35,9 @@ class MultipleObjectMixin(ContextMixin):
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
ordering = self.get_ordering()
if ordering:
@@ -53,30 +54,25 @@ class MultipleObjectMixin(ContextMixin):
def paginate_queryset(self, queryset, page_size):
"""Paginate the queryset, if needed."""
paginator = self.get_paginator(
queryset,
page_size,
orphans=self.get_paginate_orphans(),
allow_empty_first_page=self.get_allow_empty(),
)
queryset, page_size, orphans=self.get_paginate_orphans(),
allow_empty_first_page=self.get_allow_empty())
page_kwarg = self.page_kwarg
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
try:
page_number = int(page)
except ValueError:
if page == "last":
if page == 'last':
page_number = paginator.num_pages
else:
raise Http404(
_("Page is not “last”, nor can it be converted to an int.")
)
raise Http404(_('Page is not “last”, nor can it be converted to an int.'))
try:
page = paginator.page(page_number)
return (paginator, page, page.object_list, page.has_other_pages())
except InvalidPage as e:
raise Http404(
_("Invalid page (%(page_number)s): %(message)s")
% {"page_number": page_number, "message": str(e)}
)
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
'page_number': page_number,
'message': str(e)
})
def get_paginate_by(self, queryset):
"""
@@ -84,17 +80,12 @@ class MultipleObjectMixin(ContextMixin):
"""
return self.paginate_by
def get_paginator(
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
):
def get_paginator(self, queryset, per_page, orphans=0,
allow_empty_first_page=True, **kwargs):
"""Return an instance of the paginator for this view."""
return self.paginator_class(
queryset,
per_page,
orphans=orphans,
allow_empty_first_page=allow_empty_first_page,
**kwargs,
)
queryset, per_page, orphans=orphans,
allow_empty_first_page=allow_empty_first_page, **kwargs)
def get_paginate_orphans(self):
"""
@@ -114,8 +105,8 @@ class MultipleObjectMixin(ContextMixin):
"""Get the name of the item to be used in the context."""
if self.context_object_name:
return self.context_object_name
elif hasattr(object_list, "model"):
return "%s_list" % object_list.model._meta.model_name
elif hasattr(object_list, 'model'):
return '%s_list' % object_list.model._meta.model_name
else:
return None
@@ -125,21 +116,19 @@ class MultipleObjectMixin(ContextMixin):
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(
queryset, page_size
)
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
"paginator": paginator,
"page_obj": page,
"is_paginated": is_paginated,
"object_list": queryset,
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
"paginator": None,
"page_obj": None,
"is_paginated": False,
"object_list": queryset,
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
@@ -149,7 +138,6 @@ class MultipleObjectMixin(ContextMixin):
class BaseListView(MultipleObjectMixin, View):
"""A base view for displaying a list of objects."""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
@@ -158,27 +146,21 @@ class BaseListView(MultipleObjectMixin, View):
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(
self.object_list, "exists"
):
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404(
_("Empty list and “%(class_name)s.allow_empty” is False.")
% {
"class_name": self.__class__.__name__,
}
)
raise Http404(_('Empty list and “%(class_name)s.allow_empty” is False.') % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
"""Mixin for responding with a template and list of objects."""
template_name_suffix = "_list"
template_name_suffix = '_list'
def get_template_names(self):
"""
@@ -196,18 +178,14 @@ class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
# app and model name. This name gets put at the end of the template
# name list so that user-supplied names override the automatically-
# generated ones.
if hasattr(self.object_list, "model"):
if hasattr(self.object_list, 'model'):
opts = self.object_list.model._meta
names.append(
"%s/%s%s.html"
% (opts.app_label, opts.model_name, self.template_name_suffix)
)
names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
elif not names:
raise ImproperlyConfigured(
"%(cls)s requires either a 'template_name' attribute "
"or a get_queryset() method that returns a QuerySet."
% {
"cls": self.__class__.__name__,
"or a get_queryset() method that returns a QuerySet." % {
'cls': self.__class__.__name__,
}
)
return names