docker setup
This commit is contained in:
@ -0,0 +1,3 @@
|
||||
from django.views.generic.base import View
|
||||
|
||||
__all__ = ["View"]
|
160
srcs/.venv/lib/python3.11/site-packages/django/views/csrf.py
Normal file
160
srcs/.venv/lib/python3.11/site-packages/django/views/csrf.py
Normal file
@ -0,0 +1,160 @@
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.template import Context, Engine, TemplateDoesNotExist, loader
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.version import get_docs_version
|
||||
|
||||
# We include the template inline since we need to be able to reliably display
|
||||
# this error message, especially for the sake of developers, and there isn't any
|
||||
# other way of making it available independent of what is in the settings file.
|
||||
|
||||
# Only the text appearing with DEBUG=False is translated. Normal translation
|
||||
# tags cannot be used with this inline templates as makemessages would not be
|
||||
# able to discover the strings.
|
||||
|
||||
CSRF_FAILURE_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="robots" content="NONE,NOARCHIVE">
|
||||
<title>403 Forbidden</title>
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; background:#eee; color:#000; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; margin-bottom:.4em; }
|
||||
h1 span { font-size:60%; color:#666; font-weight:normal; }
|
||||
#info { background:#f6f6f6; }
|
||||
#info ul { margin: 0.5em 4em; }
|
||||
#info p, #summary p { padding-top:10px; }
|
||||
#summary { background: #ffc; }
|
||||
#explanation { background:#eee; border-bottom: 0px none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="summary">
|
||||
<h1>{{ title }} <span>(403)</span></h1>
|
||||
<p>{{ main }}</p>
|
||||
{% if no_referer %}
|
||||
<p>{{ no_referer1 }}</p>
|
||||
<p>{{ no_referer2 }}</p>
|
||||
<p>{{ no_referer3 }}</p>
|
||||
{% endif %}
|
||||
{% if no_cookie %}
|
||||
<p>{{ no_cookie1 }}</p>
|
||||
<p>{{ no_cookie2 }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if DEBUG %}
|
||||
<div id="info">
|
||||
<h2>Help</h2>
|
||||
{% if reason %}
|
||||
<p>Reason given for failure:</p>
|
||||
<pre>
|
||||
{{ reason }}
|
||||
</pre>
|
||||
{% endif %}
|
||||
|
||||
<p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
|
||||
<a
|
||||
href="https://docs.djangoproject.com/en/{{ docs_version }}/ref/csrf/">Django’s
|
||||
CSRF mechanism</a> has not been used correctly. For POST forms, you need to
|
||||
ensure:</p>
|
||||
|
||||
<ul>
|
||||
<li>Your browser is accepting cookies.</li>
|
||||
|
||||
<li>The view function passes a <code>request</code> to the template’s <a
|
||||
href="https://docs.djangoproject.com/en/dev/topics/templates/#django.template.backends.base.Template.render"><code>render</code></a>
|
||||
method.</li>
|
||||
|
||||
<li>In the template, there is a <code>{% templatetag openblock %} csrf_token
|
||||
{% templatetag closeblock %}</code> template tag inside each POST form that
|
||||
targets an internal URL.</li>
|
||||
|
||||
<li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
|
||||
<code>csrf_protect</code> on any views that use the <code>csrf_token</code>
|
||||
template tag, as well as those that accept the POST data.</li>
|
||||
|
||||
<li>The form has a valid CSRF token. After logging in in another browser
|
||||
tab or hitting the back button after a login, you may need to reload the
|
||||
page with the form, because the token is rotated after a login.</li>
|
||||
</ul>
|
||||
|
||||
<p>You’re seeing the help section of this page because you have <code>DEBUG =
|
||||
True</code> in your Django settings file. Change that to <code>False</code>,
|
||||
and only the initial error message will be displayed. </p>
|
||||
|
||||
<p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="explanation">
|
||||
<p><small>{{ more }}</small></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
""" # NOQA
|
||||
CSRF_FAILURE_TEMPLATE_NAME = "403_csrf.html"
|
||||
|
||||
|
||||
def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME):
|
||||
"""
|
||||
Default view used when request fails CSRF protection
|
||||
"""
|
||||
from django.middleware.csrf import REASON_NO_CSRF_COOKIE, REASON_NO_REFERER
|
||||
|
||||
c = {
|
||||
"title": _("Forbidden"),
|
||||
"main": _("CSRF verification failed. Request aborted."),
|
||||
"reason": reason,
|
||||
"no_referer": reason == REASON_NO_REFERER,
|
||||
"no_referer1": _(
|
||||
"You are seeing this message because this HTTPS site requires a "
|
||||
"“Referer header” to be sent by your web browser, but none was "
|
||||
"sent. This header is required for security reasons, to ensure "
|
||||
"that your browser is not being hijacked by third parties."
|
||||
),
|
||||
"no_referer2": _(
|
||||
"If you have configured your browser to disable “Referer” headers, "
|
||||
"please re-enable them, at least for this site, or for HTTPS "
|
||||
"connections, or for “same-origin” requests."
|
||||
),
|
||||
"no_referer3": _(
|
||||
'If you are using the <meta name="referrer" '
|
||||
'content="no-referrer"> tag or including the “Referrer-Policy: '
|
||||
"no-referrer” header, please remove them. The CSRF protection "
|
||||
"requires the “Referer” header to do strict referer checking. If "
|
||||
"you’re concerned about privacy, use alternatives like "
|
||||
'<a rel="noreferrer" …> for links to third-party sites.'
|
||||
),
|
||||
"no_cookie": reason == REASON_NO_CSRF_COOKIE,
|
||||
"no_cookie1": _(
|
||||
"You are seeing this message because this site requires a CSRF "
|
||||
"cookie when submitting forms. This cookie is required for "
|
||||
"security reasons, to ensure that your browser is not being "
|
||||
"hijacked by third parties."
|
||||
),
|
||||
"no_cookie2": _(
|
||||
"If you have configured your browser to disable cookies, please "
|
||||
"re-enable them, at least for this site, or for “same-origin” "
|
||||
"requests."
|
||||
),
|
||||
"DEBUG": settings.DEBUG,
|
||||
"docs_version": get_docs_version(),
|
||||
"more": _("More information is available with DEBUG=True."),
|
||||
}
|
||||
try:
|
||||
t = loader.get_template(template_name)
|
||||
except TemplateDoesNotExist:
|
||||
if template_name == CSRF_FAILURE_TEMPLATE_NAME:
|
||||
# If the default template doesn't exist, use the string template.
|
||||
t = Engine().from_string(CSRF_FAILURE_TEMPLATE)
|
||||
c = Context(c)
|
||||
else:
|
||||
# Raise if a developer-specified template doesn't exist.
|
||||
raise
|
||||
return HttpResponseForbidden(t.render(c))
|
647
srcs/.venv/lib/python3.11/site-packages/django/views/debug.py
Normal file
647
srcs/.venv/lib/python3.11/site-packages/django/views/debug.py
Normal file
@ -0,0 +1,647 @@
|
||||
import functools
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponse, HttpResponseNotFound
|
||||
from django.template import Context, Engine, TemplateDoesNotExist
|
||||
from django.template.defaultfilters import pprint
|
||||
from django.urls import resolve
|
||||
from django.utils import timezone
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
from django.utils.version import PY311, get_docs_version
|
||||
|
||||
# Minimal Django templates engine to render the error templates
|
||||
# regardless of the project's TEMPLATES setting. Templates are
|
||||
# read directly from the filesystem so that the error handler
|
||||
# works even if the template loader is broken.
|
||||
DEBUG_ENGINE = Engine(
|
||||
debug=True,
|
||||
libraries={"i18n": "django.templatetags.i18n"},
|
||||
)
|
||||
|
||||
|
||||
def builtin_template_path(name):
|
||||
"""
|
||||
Return a path to a builtin template.
|
||||
|
||||
Avoid calling this function at the module level or in a class-definition
|
||||
because __file__ may not exist, e.g. in frozen environments.
|
||||
"""
|
||||
return Path(__file__).parent / "templates" / name
|
||||
|
||||
|
||||
class ExceptionCycleWarning(UserWarning):
|
||||
pass
|
||||
|
||||
|
||||
class CallableSettingWrapper:
|
||||
"""
|
||||
Object to wrap callable appearing in settings.
|
||||
* Not to call in the debug page (#21345).
|
||||
* Not to break the debug page if the callable forbidding to set attributes
|
||||
(#23070).
|
||||
"""
|
||||
|
||||
def __init__(self, callable_setting):
|
||||
self._wrapped = callable_setting
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._wrapped)
|
||||
|
||||
|
||||
def technical_500_response(request, exc_type, exc_value, tb, status_code=500):
|
||||
"""
|
||||
Create a technical server error response. The last three arguments are
|
||||
the values returned from sys.exc_info() and friends.
|
||||
"""
|
||||
reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb)
|
||||
if request.accepts("text/html"):
|
||||
html = reporter.get_traceback_html()
|
||||
return HttpResponse(html, status=status_code)
|
||||
else:
|
||||
text = reporter.get_traceback_text()
|
||||
return HttpResponse(
|
||||
text, status=status_code, content_type="text/plain; charset=utf-8"
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def get_default_exception_reporter_filter():
|
||||
# Instantiate the default filter for the first time and cache it.
|
||||
return import_string(settings.DEFAULT_EXCEPTION_REPORTER_FILTER)()
|
||||
|
||||
|
||||
def get_exception_reporter_filter(request):
|
||||
default_filter = get_default_exception_reporter_filter()
|
||||
return getattr(request, "exception_reporter_filter", default_filter)
|
||||
|
||||
|
||||
def get_exception_reporter_class(request):
|
||||
default_exception_reporter_class = import_string(
|
||||
settings.DEFAULT_EXCEPTION_REPORTER
|
||||
)
|
||||
return getattr(
|
||||
request, "exception_reporter_class", default_exception_reporter_class
|
||||
)
|
||||
|
||||
|
||||
def get_caller(request):
|
||||
resolver_match = request.resolver_match
|
||||
if resolver_match is None:
|
||||
try:
|
||||
resolver_match = resolve(request.path)
|
||||
except Http404:
|
||||
pass
|
||||
return "" if resolver_match is None else resolver_match._func_path
|
||||
|
||||
|
||||
class SafeExceptionReporterFilter:
|
||||
"""
|
||||
Use annotations made by the sensitive_post_parameters and
|
||||
sensitive_variables decorators to filter out sensitive information.
|
||||
"""
|
||||
|
||||
cleansed_substitute = "********************"
|
||||
hidden_settings = _lazy_re_compile(
|
||||
"API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE", flags=re.I
|
||||
)
|
||||
|
||||
def cleanse_setting(self, key, value):
|
||||
"""
|
||||
Cleanse an individual setting key/value of sensitive content. If the
|
||||
value is a dictionary, recursively cleanse the keys in that dictionary.
|
||||
"""
|
||||
if key == settings.SESSION_COOKIE_NAME:
|
||||
is_sensitive = True
|
||||
else:
|
||||
try:
|
||||
is_sensitive = self.hidden_settings.search(key)
|
||||
except TypeError:
|
||||
is_sensitive = False
|
||||
|
||||
if is_sensitive:
|
||||
cleansed = self.cleansed_substitute
|
||||
elif isinstance(value, dict):
|
||||
cleansed = {k: self.cleanse_setting(k, v) for k, v in value.items()}
|
||||
elif isinstance(value, list):
|
||||
cleansed = [self.cleanse_setting("", v) for v in value]
|
||||
elif isinstance(value, tuple):
|
||||
cleansed = tuple([self.cleanse_setting("", v) for v in value])
|
||||
else:
|
||||
cleansed = value
|
||||
|
||||
if callable(cleansed):
|
||||
cleansed = CallableSettingWrapper(cleansed)
|
||||
|
||||
return cleansed
|
||||
|
||||
def get_safe_settings(self):
|
||||
"""
|
||||
Return a dictionary of the settings module with values of sensitive
|
||||
settings replaced with stars (*********).
|
||||
"""
|
||||
settings_dict = {}
|
||||
for k in dir(settings):
|
||||
if k.isupper():
|
||||
settings_dict[k] = self.cleanse_setting(k, getattr(settings, k))
|
||||
return settings_dict
|
||||
|
||||
def get_safe_request_meta(self, request):
|
||||
"""
|
||||
Return a dictionary of request.META with sensitive values redacted.
|
||||
"""
|
||||
if not hasattr(request, "META"):
|
||||
return {}
|
||||
return {k: self.cleanse_setting(k, v) for k, v in request.META.items()}
|
||||
|
||||
def get_safe_cookies(self, request):
|
||||
"""
|
||||
Return a dictionary of request.COOKIES with sensitive values redacted.
|
||||
"""
|
||||
if not hasattr(request, "COOKIES"):
|
||||
return {}
|
||||
return {k: self.cleanse_setting(k, v) for k, v in request.COOKIES.items()}
|
||||
|
||||
def is_active(self, request):
|
||||
"""
|
||||
This filter is to add safety in production environments (i.e. DEBUG
|
||||
is False). If DEBUG is True then your site is not safe anyway.
|
||||
This hook is provided as a convenience to easily activate or
|
||||
deactivate the filter on a per request basis.
|
||||
"""
|
||||
return settings.DEBUG is False
|
||||
|
||||
def get_cleansed_multivaluedict(self, request, multivaluedict):
|
||||
"""
|
||||
Replace the keys in a MultiValueDict marked as sensitive with stars.
|
||||
This mitigates leaking sensitive POST parameters if something like
|
||||
request.POST['nonexistent_key'] throws an exception (#21098).
|
||||
"""
|
||||
sensitive_post_parameters = getattr(request, "sensitive_post_parameters", [])
|
||||
if self.is_active(request) and sensitive_post_parameters:
|
||||
multivaluedict = multivaluedict.copy()
|
||||
for param in sensitive_post_parameters:
|
||||
if param in multivaluedict:
|
||||
multivaluedict[param] = self.cleansed_substitute
|
||||
return multivaluedict
|
||||
|
||||
def get_post_parameters(self, request):
|
||||
"""
|
||||
Replace the values of POST parameters marked as sensitive with
|
||||
stars (*********).
|
||||
"""
|
||||
if request is None:
|
||||
return {}
|
||||
else:
|
||||
sensitive_post_parameters = getattr(
|
||||
request, "sensitive_post_parameters", []
|
||||
)
|
||||
if self.is_active(request) and sensitive_post_parameters:
|
||||
cleansed = request.POST.copy()
|
||||
if sensitive_post_parameters == "__ALL__":
|
||||
# Cleanse all parameters.
|
||||
for k in cleansed:
|
||||
cleansed[k] = self.cleansed_substitute
|
||||
return cleansed
|
||||
else:
|
||||
# Cleanse only the specified parameters.
|
||||
for param in sensitive_post_parameters:
|
||||
if param in cleansed:
|
||||
cleansed[param] = self.cleansed_substitute
|
||||
return cleansed
|
||||
else:
|
||||
return request.POST
|
||||
|
||||
def cleanse_special_types(self, request, value):
|
||||
try:
|
||||
# If value is lazy or a complex object of another kind, this check
|
||||
# might raise an exception. isinstance checks that lazy
|
||||
# MultiValueDicts will have a return value.
|
||||
is_multivalue_dict = isinstance(value, MultiValueDict)
|
||||
except Exception as e:
|
||||
return "{!r} while evaluating {!r}".format(e, value)
|
||||
|
||||
if is_multivalue_dict:
|
||||
# Cleanse MultiValueDicts (request.POST is the one we usually care about)
|
||||
value = self.get_cleansed_multivaluedict(request, value)
|
||||
return value
|
||||
|
||||
def get_traceback_frame_variables(self, request, tb_frame):
|
||||
"""
|
||||
Replace the values of variables marked as sensitive with
|
||||
stars (*********).
|
||||
"""
|
||||
# Loop through the frame's callers to see if the sensitive_variables
|
||||
# decorator was used.
|
||||
current_frame = tb_frame.f_back
|
||||
sensitive_variables = None
|
||||
while current_frame is not None:
|
||||
if (
|
||||
current_frame.f_code.co_name == "sensitive_variables_wrapper"
|
||||
and "sensitive_variables_wrapper" in current_frame.f_locals
|
||||
):
|
||||
# The sensitive_variables decorator was used, so we take note
|
||||
# of the sensitive variables' names.
|
||||
wrapper = current_frame.f_locals["sensitive_variables_wrapper"]
|
||||
sensitive_variables = getattr(wrapper, "sensitive_variables", None)
|
||||
break
|
||||
current_frame = current_frame.f_back
|
||||
|
||||
cleansed = {}
|
||||
if self.is_active(request) and sensitive_variables:
|
||||
if sensitive_variables == "__ALL__":
|
||||
# Cleanse all variables
|
||||
for name in tb_frame.f_locals:
|
||||
cleansed[name] = self.cleansed_substitute
|
||||
else:
|
||||
# Cleanse specified variables
|
||||
for name, value in tb_frame.f_locals.items():
|
||||
if name in sensitive_variables:
|
||||
value = self.cleansed_substitute
|
||||
else:
|
||||
value = self.cleanse_special_types(request, value)
|
||||
cleansed[name] = value
|
||||
else:
|
||||
# Potentially cleanse the request and any MultiValueDicts if they
|
||||
# are one of the frame variables.
|
||||
for name, value in tb_frame.f_locals.items():
|
||||
cleansed[name] = self.cleanse_special_types(request, value)
|
||||
|
||||
if (
|
||||
tb_frame.f_code.co_name == "sensitive_variables_wrapper"
|
||||
and "sensitive_variables_wrapper" in tb_frame.f_locals
|
||||
):
|
||||
# For good measure, obfuscate the decorated function's arguments in
|
||||
# the sensitive_variables decorator's frame, in case the variables
|
||||
# associated with those arguments were meant to be obfuscated from
|
||||
# the decorated function's frame.
|
||||
cleansed["func_args"] = self.cleansed_substitute
|
||||
cleansed["func_kwargs"] = self.cleansed_substitute
|
||||
|
||||
return cleansed.items()
|
||||
|
||||
|
||||
class ExceptionReporter:
|
||||
"""Organize and coordinate reporting on exceptions."""
|
||||
|
||||
@property
|
||||
def html_template_path(self):
|
||||
return builtin_template_path("technical_500.html")
|
||||
|
||||
@property
|
||||
def text_template_path(self):
|
||||
return builtin_template_path("technical_500.txt")
|
||||
|
||||
def __init__(self, request, exc_type, exc_value, tb, is_email=False):
|
||||
self.request = request
|
||||
self.filter = get_exception_reporter_filter(self.request)
|
||||
self.exc_type = exc_type
|
||||
self.exc_value = exc_value
|
||||
self.tb = tb
|
||||
self.is_email = is_email
|
||||
|
||||
self.template_info = getattr(self.exc_value, "template_debug", None)
|
||||
self.template_does_not_exist = False
|
||||
self.postmortem = None
|
||||
|
||||
def _get_raw_insecure_uri(self):
|
||||
"""
|
||||
Return an absolute URI from variables available in this request. Skip
|
||||
allowed hosts protection, so may return insecure URI.
|
||||
"""
|
||||
return "{scheme}://{host}{path}".format(
|
||||
scheme=self.request.scheme,
|
||||
host=self.request._get_raw_host(),
|
||||
path=self.request.get_full_path(),
|
||||
)
|
||||
|
||||
def get_traceback_data(self):
|
||||
"""Return a dictionary containing traceback information."""
|
||||
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
|
||||
self.template_does_not_exist = True
|
||||
self.postmortem = self.exc_value.chain or [self.exc_value]
|
||||
|
||||
frames = self.get_traceback_frames()
|
||||
for i, frame in enumerate(frames):
|
||||
if "vars" in frame:
|
||||
frame_vars = []
|
||||
for k, v in frame["vars"]:
|
||||
v = pprint(v)
|
||||
# Trim large blobs of data
|
||||
if len(v) > 4096:
|
||||
v = "%s… <trimmed %d bytes string>" % (v[0:4096], len(v))
|
||||
frame_vars.append((k, v))
|
||||
frame["vars"] = frame_vars
|
||||
frames[i] = frame
|
||||
|
||||
unicode_hint = ""
|
||||
if self.exc_type and issubclass(self.exc_type, UnicodeError):
|
||||
start = getattr(self.exc_value, "start", None)
|
||||
end = getattr(self.exc_value, "end", None)
|
||||
if start is not None and end is not None:
|
||||
unicode_str = self.exc_value.args[1]
|
||||
unicode_hint = force_str(
|
||||
unicode_str[max(start - 5, 0) : min(end + 5, len(unicode_str))],
|
||||
"ascii",
|
||||
errors="replace",
|
||||
)
|
||||
from django import get_version
|
||||
|
||||
if self.request is None:
|
||||
user_str = None
|
||||
else:
|
||||
try:
|
||||
user_str = str(self.request.user)
|
||||
except Exception:
|
||||
# request.user may raise OperationalError if the database is
|
||||
# unavailable, for example.
|
||||
user_str = "[unable to retrieve the current user]"
|
||||
|
||||
c = {
|
||||
"is_email": self.is_email,
|
||||
"unicode_hint": unicode_hint,
|
||||
"frames": frames,
|
||||
"request": self.request,
|
||||
"request_meta": self.filter.get_safe_request_meta(self.request),
|
||||
"request_COOKIES_items": self.filter.get_safe_cookies(self.request).items(),
|
||||
"user_str": user_str,
|
||||
"filtered_POST_items": list(
|
||||
self.filter.get_post_parameters(self.request).items()
|
||||
),
|
||||
"settings": self.filter.get_safe_settings(),
|
||||
"sys_executable": sys.executable,
|
||||
"sys_version_info": "%d.%d.%d" % sys.version_info[0:3],
|
||||
"server_time": timezone.now(),
|
||||
"django_version_info": get_version(),
|
||||
"sys_path": sys.path,
|
||||
"template_info": self.template_info,
|
||||
"template_does_not_exist": self.template_does_not_exist,
|
||||
"postmortem": self.postmortem,
|
||||
}
|
||||
if self.request is not None:
|
||||
c["request_GET_items"] = self.request.GET.items()
|
||||
c["request_FILES_items"] = self.request.FILES.items()
|
||||
c["request_insecure_uri"] = self._get_raw_insecure_uri()
|
||||
c["raising_view_name"] = get_caller(self.request)
|
||||
|
||||
# Check whether exception info is available
|
||||
if self.exc_type:
|
||||
c["exception_type"] = self.exc_type.__name__
|
||||
if self.exc_value:
|
||||
c["exception_value"] = str(self.exc_value)
|
||||
if exc_notes := getattr(self.exc_value, "__notes__", None):
|
||||
c["exception_notes"] = "\n" + "\n".join(exc_notes)
|
||||
if frames:
|
||||
c["lastframe"] = frames[-1]
|
||||
return c
|
||||
|
||||
def get_traceback_html(self):
|
||||
"""Return HTML version of debug 500 HTTP error page."""
|
||||
with self.html_template_path.open(encoding="utf-8") as fh:
|
||||
t = DEBUG_ENGINE.from_string(fh.read())
|
||||
c = Context(self.get_traceback_data(), use_l10n=False)
|
||||
return t.render(c)
|
||||
|
||||
def get_traceback_text(self):
|
||||
"""Return plain text version of debug 500 HTTP error page."""
|
||||
with self.text_template_path.open(encoding="utf-8") as fh:
|
||||
t = DEBUG_ENGINE.from_string(fh.read())
|
||||
c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
|
||||
return t.render(c)
|
||||
|
||||
def _get_source(self, filename, loader, module_name):
|
||||
source = None
|
||||
if hasattr(loader, "get_source"):
|
||||
try:
|
||||
source = loader.get_source(module_name)
|
||||
except ImportError:
|
||||
pass
|
||||
if source is not None:
|
||||
source = source.splitlines()
|
||||
if source is None:
|
||||
try:
|
||||
with open(filename, "rb") as fp:
|
||||
source = fp.read().splitlines()
|
||||
except OSError:
|
||||
pass
|
||||
return source
|
||||
|
||||
def _get_lines_from_file(
|
||||
self, filename, lineno, context_lines, loader=None, module_name=None
|
||||
):
|
||||
"""
|
||||
Return context_lines before and after lineno from file.
|
||||
Return (pre_context_lineno, pre_context, context_line, post_context).
|
||||
"""
|
||||
source = self._get_source(filename, loader, module_name)
|
||||
if source is None:
|
||||
return None, [], None, []
|
||||
|
||||
# If we just read the source from a file, or if the loader did not
|
||||
# apply tokenize.detect_encoding to decode the source into a
|
||||
# string, then we should do that ourselves.
|
||||
if isinstance(source[0], bytes):
|
||||
encoding = "ascii"
|
||||
for line in source[:2]:
|
||||
# File coding may be specified. Match pattern from PEP-263
|
||||
# (https://www.python.org/dev/peps/pep-0263/)
|
||||
match = re.search(rb"coding[:=]\s*([-\w.]+)", line)
|
||||
if match:
|
||||
encoding = match[1].decode("ascii")
|
||||
break
|
||||
source = [str(sline, encoding, "replace") for sline in source]
|
||||
|
||||
lower_bound = max(0, lineno - context_lines)
|
||||
upper_bound = lineno + context_lines
|
||||
|
||||
try:
|
||||
pre_context = source[lower_bound:lineno]
|
||||
context_line = source[lineno]
|
||||
post_context = source[lineno + 1 : upper_bound]
|
||||
except IndexError:
|
||||
return None, [], None, []
|
||||
return lower_bound, pre_context, context_line, post_context
|
||||
|
||||
def _get_explicit_or_implicit_cause(self, exc_value):
|
||||
explicit = getattr(exc_value, "__cause__", None)
|
||||
suppress_context = getattr(exc_value, "__suppress_context__", None)
|
||||
implicit = getattr(exc_value, "__context__", None)
|
||||
return explicit or (None if suppress_context else implicit)
|
||||
|
||||
def get_traceback_frames(self):
|
||||
# Get the exception and all its causes
|
||||
exceptions = []
|
||||
exc_value = self.exc_value
|
||||
while exc_value:
|
||||
exceptions.append(exc_value)
|
||||
exc_value = self._get_explicit_or_implicit_cause(exc_value)
|
||||
if exc_value in exceptions:
|
||||
warnings.warn(
|
||||
"Cycle in the exception chain detected: exception '%s' "
|
||||
"encountered again." % exc_value,
|
||||
ExceptionCycleWarning,
|
||||
)
|
||||
# Avoid infinite loop if there's a cyclic reference (#29393).
|
||||
break
|
||||
|
||||
frames = []
|
||||
# No exceptions were supplied to ExceptionReporter
|
||||
if not exceptions:
|
||||
return frames
|
||||
|
||||
# In case there's just one exception, take the traceback from self.tb
|
||||
exc_value = exceptions.pop()
|
||||
tb = self.tb if not exceptions else exc_value.__traceback__
|
||||
while True:
|
||||
frames.extend(self.get_exception_traceback_frames(exc_value, tb))
|
||||
try:
|
||||
exc_value = exceptions.pop()
|
||||
except IndexError:
|
||||
break
|
||||
tb = exc_value.__traceback__
|
||||
return frames
|
||||
|
||||
def get_exception_traceback_frames(self, exc_value, tb):
|
||||
exc_cause = self._get_explicit_or_implicit_cause(exc_value)
|
||||
exc_cause_explicit = getattr(exc_value, "__cause__", True)
|
||||
if tb is None:
|
||||
yield {
|
||||
"exc_cause": exc_cause,
|
||||
"exc_cause_explicit": exc_cause_explicit,
|
||||
"tb": None,
|
||||
"type": "user",
|
||||
}
|
||||
while tb is not None:
|
||||
# Support for __traceback_hide__ which is used by a few libraries
|
||||
# to hide internal frames.
|
||||
if tb.tb_frame.f_locals.get("__traceback_hide__"):
|
||||
tb = tb.tb_next
|
||||
continue
|
||||
filename = tb.tb_frame.f_code.co_filename
|
||||
function = tb.tb_frame.f_code.co_name
|
||||
lineno = tb.tb_lineno - 1
|
||||
loader = tb.tb_frame.f_globals.get("__loader__")
|
||||
module_name = tb.tb_frame.f_globals.get("__name__") or ""
|
||||
(
|
||||
pre_context_lineno,
|
||||
pre_context,
|
||||
context_line,
|
||||
post_context,
|
||||
) = self._get_lines_from_file(
|
||||
filename,
|
||||
lineno,
|
||||
7,
|
||||
loader,
|
||||
module_name,
|
||||
)
|
||||
if pre_context_lineno is None:
|
||||
pre_context_lineno = lineno
|
||||
pre_context = []
|
||||
context_line = "<source code not available>"
|
||||
post_context = []
|
||||
|
||||
colno = tb_area_colno = ""
|
||||
if PY311:
|
||||
_, _, start_column, end_column = next(
|
||||
itertools.islice(
|
||||
tb.tb_frame.f_code.co_positions(), tb.tb_lasti // 2, None
|
||||
)
|
||||
)
|
||||
if start_column and end_column:
|
||||
underline = "^" * (end_column - start_column)
|
||||
spaces = " " * (start_column + len(str(lineno + 1)) + 2)
|
||||
colno = f"\n{spaces}{underline}"
|
||||
tb_area_spaces = " " * (
|
||||
4
|
||||
+ start_column
|
||||
- (len(context_line) - len(context_line.lstrip()))
|
||||
)
|
||||
tb_area_colno = f"\n{tb_area_spaces}{underline}"
|
||||
yield {
|
||||
"exc_cause": exc_cause,
|
||||
"exc_cause_explicit": exc_cause_explicit,
|
||||
"tb": tb,
|
||||
"type": "django" if module_name.startswith("django.") else "user",
|
||||
"filename": filename,
|
||||
"function": function,
|
||||
"lineno": lineno + 1,
|
||||
"vars": self.filter.get_traceback_frame_variables(
|
||||
self.request, tb.tb_frame
|
||||
),
|
||||
"id": id(tb),
|
||||
"pre_context": pre_context,
|
||||
"context_line": context_line,
|
||||
"post_context": post_context,
|
||||
"pre_context_lineno": pre_context_lineno + 1,
|
||||
"colno": colno,
|
||||
"tb_area_colno": tb_area_colno,
|
||||
}
|
||||
tb = tb.tb_next
|
||||
|
||||
|
||||
def technical_404_response(request, exception):
|
||||
"""Create a technical 404 error response. `exception` is the Http404."""
|
||||
try:
|
||||
error_url = exception.args[0]["path"]
|
||||
except (IndexError, TypeError, KeyError):
|
||||
error_url = request.path_info[1:] # Trim leading slash
|
||||
|
||||
try:
|
||||
tried = exception.args[0]["tried"]
|
||||
except (IndexError, TypeError, KeyError):
|
||||
resolved = True
|
||||
tried = request.resolver_match.tried if request.resolver_match else None
|
||||
else:
|
||||
resolved = False
|
||||
if not tried or ( # empty URLconf
|
||||
request.path == "/"
|
||||
and len(tried) == 1
|
||||
and len(tried[0]) == 1 # default URLconf
|
||||
and getattr(tried[0][0], "app_name", "")
|
||||
== getattr(tried[0][0], "namespace", "")
|
||||
== "admin"
|
||||
):
|
||||
return default_urlconf(request)
|
||||
|
||||
urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
|
||||
if isinstance(urlconf, types.ModuleType):
|
||||
urlconf = urlconf.__name__
|
||||
|
||||
with builtin_template_path("technical_404.html").open(encoding="utf-8") as fh:
|
||||
t = DEBUG_ENGINE.from_string(fh.read())
|
||||
reporter_filter = get_default_exception_reporter_filter()
|
||||
c = Context(
|
||||
{
|
||||
"urlconf": urlconf,
|
||||
"root_urlconf": settings.ROOT_URLCONF,
|
||||
"request_path": error_url,
|
||||
"urlpatterns": tried,
|
||||
"resolved": resolved,
|
||||
"reason": str(exception),
|
||||
"request": request,
|
||||
"settings": reporter_filter.get_safe_settings(),
|
||||
"raising_view_name": get_caller(request),
|
||||
}
|
||||
)
|
||||
return HttpResponseNotFound(t.render(c))
|
||||
|
||||
|
||||
def default_urlconf(request):
|
||||
"""Create an empty URLconf 404 error response."""
|
||||
with builtin_template_path("default_urlconf.html").open(encoding="utf-8") as fh:
|
||||
t = DEBUG_ENGINE.from_string(fh.read())
|
||||
c = Context(
|
||||
{
|
||||
"version": get_docs_version(),
|
||||
}
|
||||
)
|
||||
|
||||
return HttpResponse(t.render(c))
|
@ -0,0 +1,66 @@
|
||||
from functools import wraps
|
||||
|
||||
from django.middleware.cache import CacheMiddleware
|
||||
from django.utils.cache import add_never_cache_headers, patch_cache_control
|
||||
from django.utils.decorators import decorator_from_middleware_with_args
|
||||
|
||||
|
||||
def cache_page(timeout, *, cache=None, key_prefix=None):
|
||||
"""
|
||||
Decorator for views that tries getting the page from the cache and
|
||||
populates the cache if the page isn't in the cache yet.
|
||||
|
||||
The cache is keyed by the URL and some data from the headers.
|
||||
Additionally there is the key prefix that is used to distinguish different
|
||||
cache areas in a multi-site setup. You could use the
|
||||
get_current_site().domain, for example, as that is unique across a Django
|
||||
project.
|
||||
|
||||
Additionally, all headers from the response's Vary header will be taken
|
||||
into account on caching -- just like the middleware does.
|
||||
"""
|
||||
return decorator_from_middleware_with_args(CacheMiddleware)(
|
||||
page_timeout=timeout,
|
||||
cache_alias=cache,
|
||||
key_prefix=key_prefix,
|
||||
)
|
||||
|
||||
|
||||
def cache_control(**kwargs):
|
||||
def _cache_controller(viewfunc):
|
||||
@wraps(viewfunc)
|
||||
def _cache_controlled(request, *args, **kw):
|
||||
# Ensure argument looks like a request.
|
||||
if not hasattr(request, "META"):
|
||||
raise TypeError(
|
||||
"cache_control didn't receive an HttpRequest. If you are "
|
||||
"decorating a classmethod, be sure to use "
|
||||
"@method_decorator."
|
||||
)
|
||||
response = viewfunc(request, *args, **kw)
|
||||
patch_cache_control(response, **kwargs)
|
||||
return response
|
||||
|
||||
return _cache_controlled
|
||||
|
||||
return _cache_controller
|
||||
|
||||
|
||||
def never_cache(view_func):
|
||||
"""
|
||||
Decorator that adds headers to a response so that it will never be cached.
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def _wrapper_view_func(request, *args, **kwargs):
|
||||
# Ensure argument looks like a request.
|
||||
if not hasattr(request, "META"):
|
||||
raise TypeError(
|
||||
"never_cache didn't receive an HttpRequest. If you are "
|
||||
"decorating a classmethod, be sure to use @method_decorator."
|
||||
)
|
||||
response = view_func(request, *args, **kwargs)
|
||||
add_never_cache_headers(response)
|
||||
return response
|
||||
|
||||
return _wrapper_view_func
|
@ -0,0 +1,62 @@
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def xframe_options_deny(view_func):
|
||||
"""
|
||||
Modify a view function so its response has the X-Frame-Options HTTP
|
||||
header set to 'DENY' as long as the response doesn't already have that
|
||||
header set. Usage:
|
||||
|
||||
@xframe_options_deny
|
||||
def some_view(request):
|
||||
...
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def wrapper_view(*args, **kwargs):
|
||||
resp = view_func(*args, **kwargs)
|
||||
if resp.get("X-Frame-Options") is None:
|
||||
resp["X-Frame-Options"] = "DENY"
|
||||
return resp
|
||||
|
||||
return wrapper_view
|
||||
|
||||
|
||||
def xframe_options_sameorigin(view_func):
|
||||
"""
|
||||
Modify a view function so its response has the X-Frame-Options HTTP
|
||||
header set to 'SAMEORIGIN' as long as the response doesn't already have
|
||||
that header set. Usage:
|
||||
|
||||
@xframe_options_sameorigin
|
||||
def some_view(request):
|
||||
...
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def wrapper_view(*args, **kwargs):
|
||||
resp = view_func(*args, **kwargs)
|
||||
if resp.get("X-Frame-Options") is None:
|
||||
resp["X-Frame-Options"] = "SAMEORIGIN"
|
||||
return resp
|
||||
|
||||
return wrapper_view
|
||||
|
||||
|
||||
def xframe_options_exempt(view_func):
|
||||
"""
|
||||
Modify a view function by setting a response variable that instructs
|
||||
XFrameOptionsMiddleware to NOT set the X-Frame-Options HTTP header. Usage:
|
||||
|
||||
@xframe_options_exempt
|
||||
def some_view(request):
|
||||
...
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def wrapper_view(*args, **kwargs):
|
||||
resp = view_func(*args, **kwargs)
|
||||
resp.xframe_options_exempt = True
|
||||
return resp
|
||||
|
||||
return wrapper_view
|
@ -0,0 +1,17 @@
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def no_append_slash(view_func):
|
||||
"""
|
||||
Mark a view function as excluded from CommonMiddleware's APPEND_SLASH
|
||||
redirection.
|
||||
"""
|
||||
|
||||
# view_func.should_append_slash = False would also work, but decorators are
|
||||
# nicer if they don't have side effects, so return a new function.
|
||||
@wraps(view_func)
|
||||
def wrapper_view(*args, **kwargs):
|
||||
return view_func(*args, **kwargs)
|
||||
|
||||
wrapper_view.should_append_slash = False
|
||||
return wrapper_view
|
@ -0,0 +1,59 @@
|
||||
from functools import wraps
|
||||
|
||||
from django.middleware.csrf import CsrfViewMiddleware, get_token
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
|
||||
csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
|
||||
csrf_protect.__name__ = "csrf_protect"
|
||||
csrf_protect.__doc__ = """
|
||||
This decorator adds CSRF protection in exactly the same way as
|
||||
CsrfViewMiddleware, but it can be used on a per view basis. Using both, or
|
||||
using the decorator multiple times, is harmless and efficient.
|
||||
"""
|
||||
|
||||
|
||||
class _EnsureCsrfToken(CsrfViewMiddleware):
|
||||
# Behave like CsrfViewMiddleware but don't reject requests or log warnings.
|
||||
def _reject(self, request, reason):
|
||||
return None
|
||||
|
||||
|
||||
requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken)
|
||||
requires_csrf_token.__name__ = "requires_csrf_token"
|
||||
requires_csrf_token.__doc__ = """
|
||||
Use this decorator on views that need a correct csrf_token available to
|
||||
RequestContext, but without the CSRF protection that csrf_protect
|
||||
enforces.
|
||||
"""
|
||||
|
||||
|
||||
class _EnsureCsrfCookie(CsrfViewMiddleware):
|
||||
def _reject(self, request, reason):
|
||||
return None
|
||||
|
||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||
retval = super().process_view(request, callback, callback_args, callback_kwargs)
|
||||
# Force process_response to send the cookie
|
||||
get_token(request)
|
||||
return retval
|
||||
|
||||
|
||||
ensure_csrf_cookie = decorator_from_middleware(_EnsureCsrfCookie)
|
||||
ensure_csrf_cookie.__name__ = "ensure_csrf_cookie"
|
||||
ensure_csrf_cookie.__doc__ = """
|
||||
Use this decorator to ensure that a view sets a CSRF cookie, whether or not it
|
||||
uses the csrf_token template tag, or the CsrfViewMiddleware is used.
|
||||
"""
|
||||
|
||||
|
||||
def csrf_exempt(view_func):
|
||||
"""Mark a view function as being exempt from the CSRF view protection."""
|
||||
|
||||
# view_func.csrf_exempt = True would also work, but decorators are nicer
|
||||
# if they don't have side effects, so return a new function.
|
||||
@wraps(view_func)
|
||||
def wrapper_view(*args, **kwargs):
|
||||
return view_func(*args, **kwargs)
|
||||
|
||||
wrapper_view.csrf_exempt = True
|
||||
return wrapper_view
|
@ -0,0 +1,96 @@
|
||||
from functools import wraps
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
def sensitive_variables(*variables):
|
||||
"""
|
||||
Indicate which variables used in the decorated function are sensitive so
|
||||
that those variables can later be treated in a special way, for example
|
||||
by hiding them when logging unhandled exceptions.
|
||||
|
||||
Accept two forms:
|
||||
|
||||
* with specified variable names:
|
||||
|
||||
@sensitive_variables('user', 'password', 'credit_card')
|
||||
def my_function(user):
|
||||
password = user.pass_word
|
||||
credit_card = user.credit_card_number
|
||||
...
|
||||
|
||||
* without any specified variable names, in which case consider all
|
||||
variables are sensitive:
|
||||
|
||||
@sensitive_variables()
|
||||
def my_function()
|
||||
...
|
||||
"""
|
||||
if len(variables) == 1 and callable(variables[0]):
|
||||
raise TypeError(
|
||||
"sensitive_variables() must be called to use it as a decorator, "
|
||||
"e.g., use @sensitive_variables(), not @sensitive_variables."
|
||||
)
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def sensitive_variables_wrapper(*func_args, **func_kwargs):
|
||||
if variables:
|
||||
sensitive_variables_wrapper.sensitive_variables = variables
|
||||
else:
|
||||
sensitive_variables_wrapper.sensitive_variables = "__ALL__"
|
||||
return func(*func_args, **func_kwargs)
|
||||
|
||||
return sensitive_variables_wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def sensitive_post_parameters(*parameters):
|
||||
"""
|
||||
Indicate which POST parameters used in the decorated view are sensitive,
|
||||
so that those parameters can later be treated in a special way, for example
|
||||
by hiding them when logging unhandled exceptions.
|
||||
|
||||
Accept two forms:
|
||||
|
||||
* with specified parameters:
|
||||
|
||||
@sensitive_post_parameters('password', 'credit_card')
|
||||
def my_view(request):
|
||||
pw = request.POST['password']
|
||||
cc = request.POST['credit_card']
|
||||
...
|
||||
|
||||
* without any specified parameters, in which case consider all
|
||||
variables are sensitive:
|
||||
|
||||
@sensitive_post_parameters()
|
||||
def my_view(request)
|
||||
...
|
||||
"""
|
||||
if len(parameters) == 1 and callable(parameters[0]):
|
||||
raise TypeError(
|
||||
"sensitive_post_parameters() must be called to use it as a "
|
||||
"decorator, e.g., use @sensitive_post_parameters(), not "
|
||||
"@sensitive_post_parameters."
|
||||
)
|
||||
|
||||
def decorator(view):
|
||||
@wraps(view)
|
||||
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
|
||||
if not isinstance(request, HttpRequest):
|
||||
raise TypeError(
|
||||
"sensitive_post_parameters didn't receive an HttpRequest "
|
||||
"object. If you are decorating a classmethod, make sure "
|
||||
"to use @method_decorator."
|
||||
)
|
||||
if parameters:
|
||||
request.sensitive_post_parameters = parameters
|
||||
else:
|
||||
request.sensitive_post_parameters = "__ALL__"
|
||||
return view(request, *args, **kwargs)
|
||||
|
||||
return sensitive_post_parameters_wrapper
|
||||
|
||||
return decorator
|
@ -0,0 +1,5 @@
|
||||
from django.middleware.gzip import GZipMiddleware
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
|
||||
gzip_page = decorator_from_middleware(GZipMiddleware)
|
||||
gzip_page.__doc__ = "Decorator for views that gzips pages if the client supports it."
|
@ -0,0 +1,132 @@
|
||||
"""
|
||||
Decorators for views based on HTTP headers.
|
||||
"""
|
||||
import datetime
|
||||
from functools import wraps
|
||||
|
||||
from django.http import HttpResponseNotAllowed
|
||||
from django.middleware.http import ConditionalGetMiddleware
|
||||
from django.utils import timezone
|
||||
from django.utils.cache import get_conditional_response
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
from django.utils.http import http_date, quote_etag
|
||||
from django.utils.log import log_response
|
||||
|
||||
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
|
||||
|
||||
|
||||
def require_http_methods(request_method_list):
|
||||
"""
|
||||
Decorator to make a view only accept particular request methods. Usage::
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def my_view(request):
|
||||
# I can assume now that only GET or POST requests make it this far
|
||||
# ...
|
||||
|
||||
Note that request methods should be in uppercase.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def inner(request, *args, **kwargs):
|
||||
if request.method not in request_method_list:
|
||||
response = HttpResponseNotAllowed(request_method_list)
|
||||
log_response(
|
||||
"Method Not Allowed (%s): %s",
|
||||
request.method,
|
||||
request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
)
|
||||
return response
|
||||
return func(request, *args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
require_GET = require_http_methods(["GET"])
|
||||
require_GET.__doc__ = "Decorator to require that a view only accepts the GET method."
|
||||
|
||||
require_POST = require_http_methods(["POST"])
|
||||
require_POST.__doc__ = "Decorator to require that a view only accepts the POST method."
|
||||
|
||||
require_safe = require_http_methods(["GET", "HEAD"])
|
||||
require_safe.__doc__ = (
|
||||
"Decorator to require that a view only accepts safe methods: GET and HEAD."
|
||||
)
|
||||
|
||||
|
||||
def condition(etag_func=None, last_modified_func=None):
|
||||
"""
|
||||
Decorator to support conditional retrieval (or change) for a view
|
||||
function.
|
||||
|
||||
The parameters are callables to compute the ETag and last modified time for
|
||||
the requested resource, respectively. The callables are passed the same
|
||||
parameters as the view itself. The ETag function should return a string (or
|
||||
None if the resource doesn't exist), while the last_modified function
|
||||
should return a datetime object (or None if the resource doesn't exist).
|
||||
|
||||
The ETag function should return a complete ETag, including quotes (e.g.
|
||||
'"etag"'), since that's the only way to distinguish between weak and strong
|
||||
ETags. If an unquoted ETag is returned (e.g. 'etag'), it will be converted
|
||||
to a strong ETag by adding quotes.
|
||||
|
||||
This decorator will either pass control to the wrapped view function or
|
||||
return an HTTP 304 response (unmodified) or 412 response (precondition
|
||||
failed), depending upon the request method. In either case, the decorator
|
||||
will add the generated ETag and Last-Modified headers to the response if
|
||||
the headers aren't already set and if the request's method is safe.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def inner(request, *args, **kwargs):
|
||||
# Compute values (if any) for the requested resource.
|
||||
def get_last_modified():
|
||||
if last_modified_func:
|
||||
dt = last_modified_func(request, *args, **kwargs)
|
||||
if dt:
|
||||
if not timezone.is_aware(dt):
|
||||
dt = timezone.make_aware(dt, datetime.timezone.utc)
|
||||
return int(dt.timestamp())
|
||||
|
||||
# The value from etag_func() could be quoted or unquoted.
|
||||
res_etag = etag_func(request, *args, **kwargs) if etag_func else None
|
||||
res_etag = quote_etag(res_etag) if res_etag is not None else None
|
||||
res_last_modified = get_last_modified()
|
||||
|
||||
response = get_conditional_response(
|
||||
request,
|
||||
etag=res_etag,
|
||||
last_modified=res_last_modified,
|
||||
)
|
||||
|
||||
if response is None:
|
||||
response = func(request, *args, **kwargs)
|
||||
|
||||
# Set relevant headers on the response if they don't already exist
|
||||
# and if the request method is safe.
|
||||
if request.method in ("GET", "HEAD"):
|
||||
if res_last_modified and not response.has_header("Last-Modified"):
|
||||
response.headers["Last-Modified"] = http_date(res_last_modified)
|
||||
if res_etag:
|
||||
response.headers.setdefault("ETag", res_etag)
|
||||
|
||||
return response
|
||||
|
||||
return inner
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# Shortcut decorators for common cases based on ETag or Last-Modified only
|
||||
def etag(etag_func):
|
||||
return condition(etag_func=etag_func)
|
||||
|
||||
|
||||
def last_modified(last_modified_func):
|
||||
return condition(last_modified_func=last_modified_func)
|
@ -0,0 +1,46 @@
|
||||
from functools import wraps
|
||||
|
||||
from django.utils.cache import patch_vary_headers
|
||||
|
||||
|
||||
def vary_on_headers(*headers):
|
||||
"""
|
||||
A view decorator that adds the specified headers to the Vary header of the
|
||||
response. Usage:
|
||||
|
||||
@vary_on_headers('Cookie', 'Accept-language')
|
||||
def index(request):
|
||||
...
|
||||
|
||||
Note that the header names are not case-sensitive.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def inner_func(*args, **kwargs):
|
||||
response = func(*args, **kwargs)
|
||||
patch_vary_headers(response, headers)
|
||||
return response
|
||||
|
||||
return inner_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def vary_on_cookie(func):
|
||||
"""
|
||||
A view decorator that adds "Cookie" to the Vary header of a response. This
|
||||
indicates that a page's contents depends on cookies. Usage:
|
||||
|
||||
@vary_on_cookie
|
||||
def index(request):
|
||||
...
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def inner_func(*args, **kwargs):
|
||||
response = func(*args, **kwargs)
|
||||
patch_vary_headers(response, ("Cookie",))
|
||||
return response
|
||||
|
||||
return inner_func
|
149
srcs/.venv/lib/python3.11/site-packages/django/views/defaults.py
Normal file
149
srcs/.venv/lib/python3.11/site-packages/django/views/defaults.py
Normal file
@ -0,0 +1,149 @@
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.http import (
|
||||
HttpResponseBadRequest,
|
||||
HttpResponseForbidden,
|
||||
HttpResponseNotFound,
|
||||
HttpResponseServerError,
|
||||
)
|
||||
from django.template import Context, Engine, TemplateDoesNotExist, loader
|
||||
from django.views.decorators.csrf import requires_csrf_token
|
||||
|
||||
ERROR_404_TEMPLATE_NAME = "404.html"
|
||||
ERROR_403_TEMPLATE_NAME = "403.html"
|
||||
ERROR_400_TEMPLATE_NAME = "400.html"
|
||||
ERROR_500_TEMPLATE_NAME = "500.html"
|
||||
ERROR_PAGE_TEMPLATE = """
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>%(title)s</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>%(title)s</h1><p>%(details)s</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
# These views can be called when CsrfViewMiddleware.process_view() not run,
|
||||
# therefore need @requires_csrf_token in case the template needs
|
||||
# {% csrf_token %}.
|
||||
|
||||
|
||||
@requires_csrf_token
|
||||
def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
|
||||
"""
|
||||
Default 404 handler.
|
||||
|
||||
Templates: :template:`404.html`
|
||||
Context:
|
||||
request_path
|
||||
The path of the requested URL (e.g., '/app/pages/bad_page/'). It's
|
||||
quoted to prevent a content injection attack.
|
||||
exception
|
||||
The message from the exception which triggered the 404 (if one was
|
||||
supplied), or the exception class name
|
||||
"""
|
||||
exception_repr = exception.__class__.__name__
|
||||
# Try to get an "interesting" exception message, if any (and not the ugly
|
||||
# Resolver404 dictionary)
|
||||
try:
|
||||
message = exception.args[0]
|
||||
except (AttributeError, IndexError):
|
||||
pass
|
||||
else:
|
||||
if isinstance(message, str):
|
||||
exception_repr = message
|
||||
context = {
|
||||
"request_path": quote(request.path),
|
||||
"exception": exception_repr,
|
||||
}
|
||||
try:
|
||||
template = loader.get_template(template_name)
|
||||
body = template.render(context, request)
|
||||
except TemplateDoesNotExist:
|
||||
if template_name != ERROR_404_TEMPLATE_NAME:
|
||||
# Reraise if it's a missing custom template.
|
||||
raise
|
||||
# Render template (even though there are no substitutions) to allow
|
||||
# inspecting the context in tests.
|
||||
template = Engine().from_string(
|
||||
ERROR_PAGE_TEMPLATE
|
||||
% {
|
||||
"title": "Not Found",
|
||||
"details": "The requested resource was not found on this server.",
|
||||
},
|
||||
)
|
||||
body = template.render(Context(context))
|
||||
return HttpResponseNotFound(body)
|
||||
|
||||
|
||||
@requires_csrf_token
|
||||
def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
|
||||
"""
|
||||
500 error handler.
|
||||
|
||||
Templates: :template:`500.html`
|
||||
Context: None
|
||||
"""
|
||||
try:
|
||||
template = loader.get_template(template_name)
|
||||
except TemplateDoesNotExist:
|
||||
if template_name != ERROR_500_TEMPLATE_NAME:
|
||||
# Reraise if it's a missing custom template.
|
||||
raise
|
||||
return HttpResponseServerError(
|
||||
ERROR_PAGE_TEMPLATE % {"title": "Server Error (500)", "details": ""},
|
||||
)
|
||||
return HttpResponseServerError(template.render())
|
||||
|
||||
|
||||
@requires_csrf_token
|
||||
def bad_request(request, exception, template_name=ERROR_400_TEMPLATE_NAME):
|
||||
"""
|
||||
400 error handler.
|
||||
|
||||
Templates: :template:`400.html`
|
||||
Context: None
|
||||
"""
|
||||
try:
|
||||
template = loader.get_template(template_name)
|
||||
except TemplateDoesNotExist:
|
||||
if template_name != ERROR_400_TEMPLATE_NAME:
|
||||
# Reraise if it's a missing custom template.
|
||||
raise
|
||||
return HttpResponseBadRequest(
|
||||
ERROR_PAGE_TEMPLATE % {"title": "Bad Request (400)", "details": ""},
|
||||
)
|
||||
# No exception content is passed to the template, to not disclose any
|
||||
# sensitive information.
|
||||
return HttpResponseBadRequest(template.render())
|
||||
|
||||
|
||||
@requires_csrf_token
|
||||
def permission_denied(request, exception, template_name=ERROR_403_TEMPLATE_NAME):
|
||||
"""
|
||||
Permission denied (403) handler.
|
||||
|
||||
Templates: :template:`403.html`
|
||||
Context:
|
||||
exception
|
||||
The message from the exception which triggered the 403 (if one was
|
||||
supplied).
|
||||
|
||||
If the template does not exist, an Http403 response containing the text
|
||||
"403 Forbidden" (as per RFC 9110 Section 15.5.4) will be returned.
|
||||
"""
|
||||
try:
|
||||
template = loader.get_template(template_name)
|
||||
except TemplateDoesNotExist:
|
||||
if template_name != ERROR_403_TEMPLATE_NAME:
|
||||
# Reraise if it's a missing custom template.
|
||||
raise
|
||||
return HttpResponseForbidden(
|
||||
ERROR_PAGE_TEMPLATE % {"title": "403 Forbidden", "details": ""},
|
||||
)
|
||||
return HttpResponseForbidden(
|
||||
template.render(request=request, context={"exception": str(exception)})
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
from django.views.generic.base import RedirectView, TemplateView, View
|
||||
from django.views.generic.dates import (
|
||||
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.list import ListView
|
||||
|
||||
__all__ = [
|
||||
"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
|
@ -0,0 +1,285 @@
|
||||
import logging
|
||||
|
||||
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import (
|
||||
HttpResponse,
|
||||
HttpResponseGone,
|
||||
HttpResponseNotAllowed,
|
||||
HttpResponsePermanentRedirect,
|
||||
HttpResponseRedirect,
|
||||
)
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import classonlymethod
|
||||
from django.utils.functional import classproperty
|
||||
|
||||
logger = logging.getLogger("django.request")
|
||||
|
||||
|
||||
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)
|
||||
if self.extra_context is not None:
|
||||
kwargs.update(self.extra_context)
|
||||
return kwargs
|
||||
|
||||
|
||||
class View:
|
||||
"""
|
||||
Intentionally simple parent class for all views. Only implements
|
||||
dispatch-by-method and simple sanity checking.
|
||||
"""
|
||||
|
||||
http_method_names = [
|
||||
"get",
|
||||
"post",
|
||||
"put",
|
||||
"patch",
|
||||
"delete",
|
||||
"head",
|
||||
"options",
|
||||
"trace",
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Constructor. Called in the URLconf; can contain helpful extra
|
||||
keyword arguments, and other things.
|
||||
"""
|
||||
# Go through keyword arguments, and either save their values to our
|
||||
# instance, or raise an error.
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
@classproperty
|
||||
def view_is_async(cls):
|
||||
handlers = [
|
||||
getattr(cls, method)
|
||||
for method in cls.http_method_names
|
||||
if (method != "options" and hasattr(cls, method))
|
||||
]
|
||||
if not handlers:
|
||||
return False
|
||||
is_async = iscoroutinefunction(handlers[0])
|
||||
if not all(iscoroutinefunction(h) == is_async for h in handlers[1:]):
|
||||
raise ImproperlyConfigured(
|
||||
f"{cls.__qualname__} HTTP handlers must either be all sync or all "
|
||||
"async."
|
||||
)
|
||||
return is_async
|
||||
|
||||
@classonlymethod
|
||||
def as_view(cls, **initkwargs):
|
||||
"""Main entry point for a request-response process."""
|
||||
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__)
|
||||
)
|
||||
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)
|
||||
)
|
||||
|
||||
def view(request, *args, **kwargs):
|
||||
self = cls(**initkwargs)
|
||||
self.setup(request, *args, **kwargs)
|
||||
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__)
|
||||
|
||||
# Mark the callback if the view class is async.
|
||||
if cls.view_is_async:
|
||||
markcoroutinefunction(view)
|
||||
|
||||
return view
|
||||
|
||||
def setup(self, request, *args, **kwargs):
|
||||
"""Initialize attributes shared by all view methods."""
|
||||
if hasattr(self, "get") and not hasattr(self, "head"):
|
||||
self.head = self.get
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# Try to dispatch to the right method; if a method doesn't exist,
|
||||
# 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
|
||||
)
|
||||
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},
|
||||
)
|
||||
response = HttpResponseNotAllowed(self._allowed_methods())
|
||||
|
||||
if self.view_is_async:
|
||||
|
||||
async def func():
|
||||
return response
|
||||
|
||||
return func()
|
||||
else:
|
||||
return response
|
||||
|
||||
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"
|
||||
|
||||
if self.view_is_async:
|
||||
|
||||
async def func():
|
||||
return response
|
||||
|
||||
return func()
|
||||
else:
|
||||
return response
|
||||
|
||||
def _allowed_methods(self):
|
||||
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
|
||||
|
||||
|
||||
class TemplateResponseMixin:
|
||||
"""A mixin that can be used to render a template."""
|
||||
|
||||
template_name = None
|
||||
template_engine = None
|
||||
response_class = TemplateResponse
|
||||
content_type = None
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
"""
|
||||
Return a response, using the `response_class` for this view, with a
|
||||
template rendered with the given context.
|
||||
|
||||
Pass response_kwargs to the constructor of the response class.
|
||||
"""
|
||||
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,
|
||||
)
|
||||
|
||||
def get_template_names(self):
|
||||
"""
|
||||
Return a list of template names to be used for the request. Must return
|
||||
a list. May not be called if render_to_response() is overridden.
|
||||
"""
|
||||
if self.template_name is None:
|
||||
raise ImproperlyConfigured(
|
||||
"TemplateResponseMixin requires either a definition of "
|
||||
"'template_name' or an implementation of 'get_template_names()'"
|
||||
)
|
||||
else:
|
||||
return [self.template_name]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class RedirectView(View):
|
||||
"""Provide a redirect on any GET request."""
|
||||
|
||||
permanent = False
|
||||
url = None
|
||||
pattern_name = None
|
||||
query_string = False
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
"""
|
||||
Return the URL redirect to. Keyword arguments from the URL pattern
|
||||
match generating the redirect request are provided as kwargs to this
|
||||
method.
|
||||
"""
|
||||
if self.url:
|
||||
url = self.url % kwargs
|
||||
elif self.pattern_name:
|
||||
url = reverse(self.pattern_name, args=args, kwargs=kwargs)
|
||||
else:
|
||||
return None
|
||||
|
||||
args = self.request.META.get("QUERY_STRING", "")
|
||||
if args and self.query_string:
|
||||
url = "%s?%s" % (url, args)
|
||||
return url
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
url = self.get_redirect_url(*args, **kwargs)
|
||||
if url:
|
||||
if self.permanent:
|
||||
return HttpResponsePermanentRedirect(url)
|
||||
else:
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
logger.warning(
|
||||
"Gone: %s", request.path, extra={"status_code": 410, "request": request}
|
||||
)
|
||||
return HttpResponseGone()
|
||||
|
||||
def head(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
@ -0,0 +1,795 @@
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
from django.http import Http404
|
||||
from django.utils import timezone
|
||||
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,
|
||||
)
|
||||
from django.views.generic.list import (
|
||||
MultipleObjectMixin,
|
||||
MultipleObjectTemplateResponseMixin,
|
||||
)
|
||||
|
||||
|
||||
class YearMixin:
|
||||
"""Mixin for views manipulating year-based data."""
|
||||
|
||||
year_format = "%Y"
|
||||
year = None
|
||||
|
||||
def get_year_format(self):
|
||||
"""
|
||||
Get a year format string in strptime syntax to be used to parse the
|
||||
year from url variables.
|
||||
"""
|
||||
return self.year_format
|
||||
|
||||
def get_year(self):
|
||||
"""Return the year for which this view should display data."""
|
||||
year = self.year
|
||||
if year is None:
|
||||
try:
|
||||
year = self.kwargs["year"]
|
||||
except KeyError:
|
||||
try:
|
||||
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")
|
||||
|
||||
def get_previous_year(self, date):
|
||||
"""Get the previous valid year."""
|
||||
return _get_next_prev(self, date, is_previous=True, period="year")
|
||||
|
||||
def _get_next_year(self, date):
|
||||
"""
|
||||
Return the start date of the next interval.
|
||||
|
||||
The interval is defined by start date <= item date < next start date.
|
||||
"""
|
||||
try:
|
||||
return date.replace(year=date.year + 1, month=1, day=1)
|
||||
except ValueError:
|
||||
raise Http404(_("Date out of range"))
|
||||
|
||||
def _get_current_year(self, date):
|
||||
"""Return the start date of the current interval."""
|
||||
return date.replace(month=1, day=1)
|
||||
|
||||
|
||||
class MonthMixin:
|
||||
"""Mixin for views manipulating month-based data."""
|
||||
|
||||
month_format = "%b"
|
||||
month = None
|
||||
|
||||
def get_month_format(self):
|
||||
"""
|
||||
Get a month format string in strptime syntax to be used to parse the
|
||||
month from url variables.
|
||||
"""
|
||||
return self.month_format
|
||||
|
||||
def get_month(self):
|
||||
"""Return the month for which this view should display data."""
|
||||
month = self.month
|
||||
if month is None:
|
||||
try:
|
||||
month = self.kwargs["month"]
|
||||
except KeyError:
|
||||
try:
|
||||
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")
|
||||
|
||||
def get_previous_month(self, date):
|
||||
"""Get the previous valid month."""
|
||||
return _get_next_prev(self, date, is_previous=True, period="month")
|
||||
|
||||
def _get_next_month(self, date):
|
||||
"""
|
||||
Return the start date of the next interval.
|
||||
|
||||
The interval is defined by start date <= item date < next start date.
|
||||
"""
|
||||
if date.month == 12:
|
||||
try:
|
||||
return date.replace(year=date.year + 1, month=1, day=1)
|
||||
except ValueError:
|
||||
raise Http404(_("Date out of range"))
|
||||
else:
|
||||
return date.replace(month=date.month + 1, day=1)
|
||||
|
||||
def _get_current_month(self, date):
|
||||
"""Return the start date of the previous interval."""
|
||||
return date.replace(day=1)
|
||||
|
||||
|
||||
class DayMixin:
|
||||
"""Mixin for views manipulating day-based data."""
|
||||
|
||||
day_format = "%d"
|
||||
day = None
|
||||
|
||||
def get_day_format(self):
|
||||
"""
|
||||
Get a day format string in strptime syntax to be used to parse the day
|
||||
from url variables.
|
||||
"""
|
||||
return self.day_format
|
||||
|
||||
def get_day(self):
|
||||
"""Return the day for which this view should display data."""
|
||||
day = self.day
|
||||
if day is None:
|
||||
try:
|
||||
day = self.kwargs["day"]
|
||||
except KeyError:
|
||||
try:
|
||||
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")
|
||||
|
||||
def get_previous_day(self, date):
|
||||
"""Get the previous valid day."""
|
||||
return _get_next_prev(self, date, is_previous=True, period="day")
|
||||
|
||||
def _get_next_day(self, date):
|
||||
"""
|
||||
Return the start date of the next interval.
|
||||
|
||||
The interval is defined by start date <= item date < next start date.
|
||||
"""
|
||||
return date + datetime.timedelta(days=1)
|
||||
|
||||
def _get_current_day(self, date):
|
||||
"""Return the start date of the current interval."""
|
||||
return date
|
||||
|
||||
|
||||
class WeekMixin:
|
||||
"""Mixin for views manipulating week-based data."""
|
||||
|
||||
week_format = "%U"
|
||||
week = None
|
||||
|
||||
def get_week_format(self):
|
||||
"""
|
||||
Get a week format string in strptime syntax to be used to parse the
|
||||
week from url variables.
|
||||
"""
|
||||
return self.week_format
|
||||
|
||||
def get_week(self):
|
||||
"""Return the week for which this view should display data."""
|
||||
week = self.week
|
||||
if week is None:
|
||||
try:
|
||||
week = self.kwargs["week"]
|
||||
except KeyError:
|
||||
try:
|
||||
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")
|
||||
|
||||
def get_previous_week(self, date):
|
||||
"""Get the previous valid week."""
|
||||
return _get_next_prev(self, date, is_previous=True, period="week")
|
||||
|
||||
def _get_next_week(self, date):
|
||||
"""
|
||||
Return the start date of the next interval.
|
||||
|
||||
The interval is defined by start date <= item date < next start date.
|
||||
"""
|
||||
try:
|
||||
return date + datetime.timedelta(days=7 - self._get_weekday(date))
|
||||
except OverflowError:
|
||||
raise Http404(_("Date out of range"))
|
||||
|
||||
def _get_current_week(self, date):
|
||||
"""Return the start date of the current interval."""
|
||||
return date - datetime.timedelta(self._get_weekday(date))
|
||||
|
||||
def _get_weekday(self, date):
|
||||
"""
|
||||
Return the weekday for a given date.
|
||||
|
||||
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
|
||||
return date.weekday()
|
||||
elif week_format == "%U": # week starts on Sunday
|
||||
return (date.weekday() + 1) % 7
|
||||
else:
|
||||
raise ValueError("unknown week format: %s" % week_format)
|
||||
|
||||
|
||||
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__
|
||||
)
|
||||
return self.date_field
|
||||
|
||||
def get_allow_future(self):
|
||||
"""
|
||||
Return `True` if the view should be allowed to display objects from
|
||||
the future.
|
||||
"""
|
||||
return self.allow_future
|
||||
|
||||
# Note: the following three methods only work in subclasses that also
|
||||
# inherit SingleObjectMixin or MultipleObjectMixin.
|
||||
|
||||
@cached_property
|
||||
def uses_datetime_field(self):
|
||||
"""
|
||||
Return `True` if the date field is a `DateTimeField` and `False`
|
||||
if it's a `DateField`.
|
||||
"""
|
||||
model = self.get_queryset().model if self.model is None else self.model
|
||||
field = model._meta.get_field(self.get_date_field())
|
||||
return isinstance(field, models.DateTimeField)
|
||||
|
||||
def _make_date_lookup_arg(self, value):
|
||||
"""
|
||||
Convert a date into a datetime when the date field is a DateTimeField.
|
||||
|
||||
When time zone support is enabled, `date` is assumed to be in the
|
||||
current time zone, so that displayed items are consistent with the URL.
|
||||
"""
|
||||
if self.uses_datetime_field:
|
||||
value = datetime.datetime.combine(value, datetime.time.min)
|
||||
if settings.USE_TZ:
|
||||
value = timezone.make_aware(value)
|
||||
return value
|
||||
|
||||
def _make_single_date_lookup(self, date):
|
||||
"""
|
||||
Get the lookup kwargs for filtering on a single date.
|
||||
|
||||
If the date field is a DateTimeField, we can't just filter on
|
||||
date_field=date because that doesn't take the time into account.
|
||||
"""
|
||||
date_field = self.get_date_field()
|
||||
if self.uses_datetime_field:
|
||||
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,
|
||||
}
|
||||
else:
|
||||
# Skip self._make_date_lookup_arg, it's a no-op in this branch.
|
||||
return {date_field: date}
|
||||
|
||||
|
||||
class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
||||
"""Abstract base class for date-based views displaying a list of objects."""
|
||||
|
||||
allow_empty = False
|
||||
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
|
||||
)
|
||||
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()"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
def get_dated_queryset(self, **lookup):
|
||||
"""
|
||||
Get a queryset properly filtered according to `allow_future` and any
|
||||
extra lookup kwargs.
|
||||
"""
|
||||
qs = self.get_queryset().filter(**lookup)
|
||||
date_field = self.get_date_field()
|
||||
allow_future = self.get_allow_future()
|
||||
allow_empty = self.get_allow_empty()
|
||||
paginate_by = self.get_paginate_by(qs)
|
||||
|
||||
if not allow_future:
|
||||
now = timezone.now() if self.uses_datetime_field else timezone_today()
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
return qs
|
||||
|
||||
def get_date_list_period(self):
|
||||
"""
|
||||
Get the aggregation period for the list of dates: 'year', 'month', or
|
||||
'day'.
|
||||
"""
|
||||
return self.date_list_period
|
||||
|
||||
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.
|
||||
"""
|
||||
date_field = self.get_date_field()
|
||||
allow_empty = self.get_allow_empty()
|
||||
if date_type is None:
|
||||
date_type = self.get_date_list_period()
|
||||
|
||||
if self.uses_datetime_field:
|
||||
date_list = queryset.datetimes(date_field, date_type, ordering)
|
||||
else:
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
return date_list
|
||||
|
||||
|
||||
class BaseArchiveIndexView(BaseDateListView):
|
||||
"""
|
||||
Base class for archives of date-based items. Requires a response mixin.
|
||||
"""
|
||||
|
||||
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")
|
||||
|
||||
if not date_list:
|
||||
qs = qs.none()
|
||||
|
||||
return (date_list, qs, {})
|
||||
|
||||
|
||||
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
|
||||
"""Top-level archive of date-based items."""
|
||||
|
||||
template_name_suffix = "_archive"
|
||||
|
||||
|
||||
class BaseYearArchiveView(YearMixin, BaseDateListView):
|
||||
"""List of objects published in a given year."""
|
||||
|
||||
date_list_period = "month"
|
||||
make_object_list = False
|
||||
|
||||
def get_dated_items(self):
|
||||
"""Return (date_list, items, extra_context) for this request."""
|
||||
year = self.get_year()
|
||||
|
||||
date_field = self.get_date_field()
|
||||
date = _date_from_string(year, self.get_year_format())
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
qs = self.get_dated_queryset(**lookup_kwargs)
|
||||
date_list = self.get_date_list(qs)
|
||||
|
||||
if not self.get_make_object_list():
|
||||
# We need this to be a queryset since parent classes introspect it
|
||||
# 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),
|
||||
},
|
||||
)
|
||||
|
||||
def get_make_object_list(self):
|
||||
"""
|
||||
Return `True` if this view should contain the full list of objects in
|
||||
the given year.
|
||||
"""
|
||||
return self.make_object_list
|
||||
|
||||
|
||||
class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView):
|
||||
"""List of objects published in a given year."""
|
||||
|
||||
template_name_suffix = "_archive_year"
|
||||
|
||||
|
||||
class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
|
||||
"""List of objects published in a given month."""
|
||||
|
||||
date_list_period = "day"
|
||||
|
||||
def get_dated_items(self):
|
||||
"""Return (date_list, items, extra_context) for this request."""
|
||||
year = self.get_year()
|
||||
month = self.get_month()
|
||||
|
||||
date_field = self.get_date_field()
|
||||
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,
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView):
|
||||
"""List of objects published in a given month."""
|
||||
|
||||
template_name_suffix = "_archive_month"
|
||||
|
||||
|
||||
class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
|
||||
"""List of objects published in a given week."""
|
||||
|
||||
def get_dated_items(self):
|
||||
"""Return (date_list, items, extra_context) for this request."""
|
||||
year = self.get_year()
|
||||
week = self.get_week()
|
||||
|
||||
date_field = self.get_date_field()
|
||||
week_format = self.get_week_format()
|
||||
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)),
|
||||
)
|
||||
)
|
||||
year_format = self.get_year_format()
|
||||
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,
|
||||
)
|
||||
)
|
||||
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,
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView):
|
||||
"""List of objects published in a given 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(),
|
||||
)
|
||||
|
||||
return self._get_dated_items(date)
|
||||
|
||||
def _get_dated_items(self, date):
|
||||
"""
|
||||
Do the actual heavy lifting of getting the dated items; this accepts a
|
||||
date object so that TodayArchiveView can be trivial.
|
||||
"""
|
||||
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),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView):
|
||||
"""List of objects published on a given day."""
|
||||
|
||||
template_name_suffix = "_archive_day"
|
||||
|
||||
|
||||
class BaseTodayArchiveView(BaseDayArchiveView):
|
||||
"""List of objects published today."""
|
||||
|
||||
def get_dated_items(self):
|
||||
"""Return (date_list, items, extra_context) for this request."""
|
||||
return self._get_dated_items(datetime.date.today())
|
||||
|
||||
|
||||
class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView):
|
||||
"""List of objects published today."""
|
||||
|
||||
template_name_suffix = "_archive_day"
|
||||
|
||||
|
||||
class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailView):
|
||||
"""
|
||||
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(),
|
||||
)
|
||||
|
||||
# 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__,
|
||||
}
|
||||
)
|
||||
|
||||
# Filter down a queryset from self.queryset using the date from the
|
||||
# URL. This'll get passed as the queryset to DetailView.get_object,
|
||||
# which'll handle the 404
|
||||
lookup_kwargs = self._make_single_date_lookup(date)
|
||||
qs = qs.filter(**lookup_kwargs)
|
||||
|
||||
return super().get_object(queryset=qs)
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
format = year_format + delim + month_format + delim + day_format
|
||||
datestr = str(year) + delim + str(month) + delim + str(day)
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _get_next_prev(generic_view, date, is_previous, period):
|
||||
"""
|
||||
Get the next or the previous valid date. The idea is to allow links on
|
||||
month/day views to never be 404s by never providing a date that'll be
|
||||
invalid for the given view.
|
||||
|
||||
This is a bit complicated since it handles different intervals of time,
|
||||
hence the coupling to generic_view.
|
||||
|
||||
However in essence the logic comes down to:
|
||||
|
||||
* If allow_empty and allow_future are both true, this is easy: just
|
||||
return the naive result (just the next/previous day/week/month,
|
||||
regardless of object existence.)
|
||||
|
||||
* If allow_empty is true, allow_future is false, and the naive result
|
||||
isn't in the future, then return it; otherwise return None.
|
||||
|
||||
* If allow_empty is false and allow_future is true, return the next
|
||||
date *that contains a valid object*, even if it's in the future. If
|
||||
there are no next objects, return None.
|
||||
|
||||
* If allow_empty is false and allow_future is false, return the next
|
||||
date that contains a valid object. If that date is in the future, or
|
||||
if there are no next objects, return None.
|
||||
"""
|
||||
date_field = generic_view.get_date_field()
|
||||
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)
|
||||
|
||||
# Bounds of the current interval
|
||||
start, end = get_current(date), get_next(date)
|
||||
|
||||
# If allow_empty is True, the naive result will be valid
|
||||
if allow_empty:
|
||||
if is_previous:
|
||||
result = get_current(start - datetime.timedelta(days=1))
|
||||
else:
|
||||
result = end
|
||||
|
||||
if allow_future or result <= timezone_today():
|
||||
return result
|
||||
else:
|
||||
return None
|
||||
|
||||
# Otherwise, we'll need to go to the database to look for an object
|
||||
# whose date_field is at least (greater than/less than) the given
|
||||
# naive result
|
||||
else:
|
||||
# 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
|
||||
else:
|
||||
lookup = {"%s__gte" % date_field: generic_view._make_date_lookup_arg(end)}
|
||||
ordering = date_field
|
||||
|
||||
# Filter out objects in the future if appropriate.
|
||||
if not allow_future:
|
||||
# Fortunately, to match the implementation of allow_future,
|
||||
# we need __lte, which doesn't conflict with __lt above.
|
||||
if generic_view.uses_datetime_field:
|
||||
now = timezone.now()
|
||||
else:
|
||||
now = timezone_today()
|
||||
lookup["%s__lte" % date_field] = now
|
||||
|
||||
qs = generic_view.get_queryset().filter(**lookup).order_by(ordering)
|
||||
|
||||
# Snag the first object from the queryset; if it doesn't exist that
|
||||
# means there's no next/previous link available.
|
||||
try:
|
||||
result = getattr(qs[0], date_field)
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
# Convert datetimes to dates in the current time zone.
|
||||
if generic_view.uses_datetime_field:
|
||||
if settings.USE_TZ:
|
||||
result = timezone.localtime(result)
|
||||
result = result.date()
|
||||
|
||||
# Return the first day of the period.
|
||||
return get_current(result)
|
||||
|
||||
|
||||
def timezone_today():
|
||||
"""Return the current date in the current time zone."""
|
||||
if settings.USE_TZ:
|
||||
return timezone.localdate()
|
||||
else:
|
||||
return datetime.date.today()
|
@ -0,0 +1,180 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
from django.http import Http404
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
|
||||
|
||||
|
||||
class SingleObjectMixin(ContextMixin):
|
||||
"""
|
||||
Provide the ability to retrieve a single object for further manipulation.
|
||||
"""
|
||||
|
||||
model = None
|
||||
queryset = None
|
||||
slug_field = "slug"
|
||||
context_object_name = None
|
||||
slug_url_kwarg = "slug"
|
||||
pk_url_kwarg = "pk"
|
||||
query_pk_and_slug = False
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
Return the object the view is displaying.
|
||||
|
||||
Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
|
||||
Subclasses can override this to return any object.
|
||||
"""
|
||||
# Use a custom queryset if provided; this is required for subclasses
|
||||
# like DateDetailView
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Next, try looking up by primary key.
|
||||
pk = self.kwargs.get(self.pk_url_kwarg)
|
||||
slug = self.kwargs.get(self.slug_url_kwarg)
|
||||
if pk is not None:
|
||||
queryset = queryset.filter(pk=pk)
|
||||
|
||||
# Next, try looking up by slug.
|
||||
if slug is not None and (pk is None or self.query_pk_and_slug):
|
||||
slug_field = self.get_slug_field()
|
||||
queryset = queryset.filter(**{slug_field: slug})
|
||||
|
||||
# If none of those are defined, it's an error.
|
||||
if pk is None and slug is None:
|
||||
raise AttributeError(
|
||||
"Generic detail view %s must be called with either an object "
|
||||
"pk or a slug in the URLconf." % self.__class__.__name__
|
||||
)
|
||||
|
||||
try:
|
||||
# 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}
|
||||
)
|
||||
return obj
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Return the `QuerySet` that will be used to look up the object.
|
||||
|
||||
This method is called by the default implementation of get_object() and
|
||||
may not be called if get_object() is overridden.
|
||||
"""
|
||||
if self.queryset is None:
|
||||
if self.model:
|
||||
return self.model._default_manager.all()
|
||||
else:
|
||||
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__}
|
||||
)
|
||||
return self.queryset.all()
|
||||
|
||||
def get_slug_field(self):
|
||||
"""Get the name of a slug field to be used to look up by slug."""
|
||||
return self.slug_field
|
||||
|
||||
def get_context_object_name(self, obj):
|
||||
"""Get the name to use for the object."""
|
||||
if self.context_object_name:
|
||||
return self.context_object_name
|
||||
elif isinstance(obj, models.Model):
|
||||
return obj._meta.model_name
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Insert the single object into the context dict."""
|
||||
context = {}
|
||||
if 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
|
||||
context.update(kwargs)
|
||||
return super().get_context_data(**context)
|
||||
|
||||
|
||||
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)
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
|
||||
template_name_field = None
|
||||
template_name_suffix = "_detail"
|
||||
|
||||
def get_template_names(self):
|
||||
"""
|
||||
Return a list of template names to be used for the request. May not be
|
||||
called if render_to_response() is overridden. Return the following list:
|
||||
|
||||
* the value of ``template_name`` on the view (if provided)
|
||||
* the contents of the ``template_name_field`` field on the
|
||||
object instance that the view is operating upon (if available)
|
||||
* ``<app_label>/<model_name><template_name_suffix>.html``
|
||||
"""
|
||||
try:
|
||||
names = super().get_template_names()
|
||||
except ImproperlyConfigured:
|
||||
# If template_name isn't specified, it's not a problem --
|
||||
# we just start with an empty list.
|
||||
names = []
|
||||
|
||||
# If self.template_name_field is set, grab the value of the field
|
||||
# of that name from the object; this is the most specific template
|
||||
# name, if given.
|
||||
if self.object and self.template_name_field:
|
||||
name = getattr(self.object, self.template_name_field, None)
|
||||
if name:
|
||||
names.insert(0, name)
|
||||
|
||||
# The least-specific option is the default <app>/<model>_detail.html;
|
||||
# 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,
|
||||
)
|
||||
)
|
||||
|
||||
# If we still haven't managed to find any template names, we should
|
||||
# re-raise the ImproperlyConfigured to alert the user.
|
||||
if not names:
|
||||
raise
|
||||
|
||||
return names
|
||||
|
||||
|
||||
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
|
||||
"""
|
||||
Render a "detail" view of an object.
|
||||
|
||||
By default this is a model instance looked up from `self.queryset`, but the
|
||||
view will support display of *any* object by overriding `self.get_object()`.
|
||||
"""
|
@ -0,0 +1,294 @@
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
class FormMixin(ContextMixin):
|
||||
"""Provide a way to show and handle a form in a request."""
|
||||
|
||||
initial = {}
|
||||
form_class = None
|
||||
success_url = None
|
||||
prefix = None
|
||||
|
||||
def get_initial(self):
|
||||
"""Return the initial data to use for forms on this view."""
|
||||
return self.initial.copy()
|
||||
|
||||
def get_prefix(self):
|
||||
"""Return the prefix to use for forms."""
|
||||
return self.prefix
|
||||
|
||||
def get_form_class(self):
|
||||
"""Return the form class to use."""
|
||||
return self.form_class
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
"""Return an instance of the form to be used in this view."""
|
||||
if form_class is None:
|
||||
form_class = self.get_form_class()
|
||||
return form_class(**self.get_form_kwargs())
|
||||
|
||||
def get_form_kwargs(self):
|
||||
"""Return the keyword arguments for instantiating the form."""
|
||||
kwargs = {
|
||||
"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,
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
"""Return the URL to redirect to after processing a valid form."""
|
||||
if not self.success_url:
|
||||
raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
|
||||
return str(self.success_url) # success_url may be lazy
|
||||
|
||||
def form_valid(self, form):
|
||||
"""If the form is valid, redirect to the supplied URL."""
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def form_invalid(self, form):
|
||||
"""If the form is invalid, render the invalid form."""
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Insert the form into the context dict."""
|
||||
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):
|
||||
"""Return the form class to use in this view."""
|
||||
if self.fields is not None and self.form_class:
|
||||
raise ImproperlyConfigured(
|
||||
"Specifying both 'fields' and 'form_class' is not permitted."
|
||||
)
|
||||
if self.form_class:
|
||||
return self.form_class
|
||||
else:
|
||||
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:
|
||||
# If this view is operating on a single object, use
|
||||
# the class of that object
|
||||
model = self.object.__class__
|
||||
else:
|
||||
# Try to get a queryset and extract the model class
|
||||
# from that
|
||||
model = self.get_queryset().model
|
||||
|
||||
if self.fields is None:
|
||||
raise ImproperlyConfigured(
|
||||
"Using ModelFormMixin (base class of %s) without "
|
||||
"the 'fields' attribute is prohibited." % self.__class__.__name__
|
||||
)
|
||||
|
||||
return model_forms.modelform_factory(model, fields=self.fields)
|
||||
|
||||
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})
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
"""Return the URL to redirect to after processing a valid form."""
|
||||
if self.success_url:
|
||||
url = self.success_url.format(**self.object.__dict__)
|
||||
else:
|
||||
try:
|
||||
url = self.object.get_absolute_url()
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
"No URL to redirect to. Either provide a url or define"
|
||||
" a get_absolute_url method on the Model."
|
||||
)
|
||||
return url
|
||||
|
||||
def form_valid(self, form):
|
||||
"""If the form is valid, save the associated model."""
|
||||
self.object = form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
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())
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Handle POST requests: instantiate a form instance with the passed
|
||||
POST variables and then check if it's valid.
|
||||
"""
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
|
||||
# object, note that browsers only support POST for now.
|
||||
def put(self, *args, **kwargs):
|
||||
return self.post(*args, **kwargs)
|
||||
|
||||
|
||||
class BaseFormView(FormMixin, ProcessFormView):
|
||||
"""A base view for displaying a form."""
|
||||
|
||||
|
||||
class FormView(TemplateResponseMixin, BaseFormView):
|
||||
"""A view for displaying a form and rendering a template response."""
|
||||
|
||||
|
||||
class BaseCreateView(ModelFormMixin, ProcessFormView):
|
||||
"""
|
||||
Base view for creating a new object instance.
|
||||
|
||||
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)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = None
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
|
||||
"""
|
||||
View for creating a new object, with a response rendered by a template.
|
||||
"""
|
||||
|
||||
template_name_suffix = "_form"
|
||||
|
||||
|
||||
class BaseUpdateView(ModelFormMixin, ProcessFormView):
|
||||
"""
|
||||
Base view for updating an existing object.
|
||||
|
||||
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)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
|
||||
"""View for updating an object, with a response rendered by a template."""
|
||||
|
||||
template_name_suffix = "_form"
|
||||
|
||||
|
||||
class DeletionMixin:
|
||||
"""Provide the ability to delete objects."""
|
||||
|
||||
success_url = None
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""
|
||||
Call the delete() method on the fetched object and then redirect to the
|
||||
success URL.
|
||||
"""
|
||||
self.object = self.get_object()
|
||||
success_url = self.get_success_url()
|
||||
self.object.delete()
|
||||
return HttpResponseRedirect(success_url)
|
||||
|
||||
# Add support for browsers which only accept GET and POST for now.
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.delete(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
if self.success_url:
|
||||
return self.success_url.format(**self.object.__dict__)
|
||||
else:
|
||||
raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
|
||||
|
||||
|
||||
# RemovedInDjango50Warning.
|
||||
class DeleteViewCustomDeleteWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class BaseDeleteView(DeletionMixin, FormMixin, 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"
|
@ -0,0 +1,220 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.paginator import InvalidPage, Paginator
|
||||
from django.db.models import QuerySet
|
||||
from django.http import Http404
|
||||
from django.utils.translation import gettext as _
|
||||
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
|
||||
paginate_by = None
|
||||
paginate_orphans = 0
|
||||
context_object_name = None
|
||||
paginator_class = Paginator
|
||||
page_kwarg = "page"
|
||||
ordering = None
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Return the list of items for this view.
|
||||
|
||||
The return value must be an iterable and may be an instance of
|
||||
`QuerySet` in which case `QuerySet` specific behavior will be enabled.
|
||||
"""
|
||||
if self.queryset is not None:
|
||||
queryset = self.queryset
|
||||
if isinstance(queryset, QuerySet):
|
||||
queryset = queryset.all()
|
||||
elif self.model is not None:
|
||||
queryset = self.model._default_manager.all()
|
||||
else:
|
||||
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__}
|
||||
)
|
||||
ordering = self.get_ordering()
|
||||
if ordering:
|
||||
if isinstance(ordering, str):
|
||||
ordering = (ordering,)
|
||||
queryset = queryset.order_by(*ordering)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_ordering(self):
|
||||
"""Return the field or fields to use for ordering the queryset."""
|
||||
return self.ordering
|
||||
|
||||
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(),
|
||||
)
|
||||
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":
|
||||
page_number = paginator.num_pages
|
||||
else:
|
||||
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)}
|
||||
)
|
||||
|
||||
def get_paginate_by(self, queryset):
|
||||
"""
|
||||
Get the number of items to paginate by, or ``None`` for no pagination.
|
||||
"""
|
||||
return self.paginate_by
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
def get_paginate_orphans(self):
|
||||
"""
|
||||
Return the maximum number of orphans extend the last page by when
|
||||
paginating.
|
||||
"""
|
||||
return self.paginate_orphans
|
||||
|
||||
def get_allow_empty(self):
|
||||
"""
|
||||
Return ``True`` if the view should display empty lists and ``False``
|
||||
if a 404 should be raised instead.
|
||||
"""
|
||||
return self.allow_empty
|
||||
|
||||
def get_context_object_name(self, object_list):
|
||||
"""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
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_context_data(self, *, object_list=None, **kwargs):
|
||||
"""Get the context for this view."""
|
||||
queryset = object_list if object_list is not None else self.object_list
|
||||
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
|
||||
)
|
||||
context = {
|
||||
"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,
|
||||
}
|
||||
if context_object_name is not None:
|
||||
context[context_object_name] = queryset
|
||||
context.update(kwargs)
|
||||
return super().get_context_data(**context)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
if not allow_empty:
|
||||
# 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"
|
||||
):
|
||||
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__,
|
||||
}
|
||||
)
|
||||
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"
|
||||
|
||||
def get_template_names(self):
|
||||
"""
|
||||
Return a list of template names to be used for the request. Must return
|
||||
a list. May not be called if render_to_response is overridden.
|
||||
"""
|
||||
try:
|
||||
names = super().get_template_names()
|
||||
except ImproperlyConfigured:
|
||||
# If template_name isn't specified, it's not a problem --
|
||||
# we just start with an empty list.
|
||||
names = []
|
||||
|
||||
# If the list is a queryset, we'll invent a template name based on the
|
||||
# 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"):
|
||||
opts = self.object_list.model._meta
|
||||
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__,
|
||||
}
|
||||
)
|
||||
return names
|
||||
|
||||
|
||||
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
|
||||
"""
|
||||
Render some list of objects, set by `self.model` or `self.queryset`.
|
||||
`self.queryset` can actually be any iterable of items, not just a queryset.
|
||||
"""
|
345
srcs/.venv/lib/python3.11/site-packages/django/views/i18n.py
Normal file
345
srcs/.venv/lib/python3.11/site-packages/django/views/i18n.py
Normal file
@ -0,0 +1,345 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
||||
from django.template import Context, Engine
|
||||
from django.urls import translate_url
|
||||
from django.utils.formats import get_format
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.translation import check_for_language, get_language
|
||||
from django.utils.translation.trans_real import DjangoTranslation
|
||||
from django.views.generic import View
|
||||
|
||||
LANGUAGE_QUERY_PARAMETER = "language"
|
||||
|
||||
|
||||
def set_language(request):
|
||||
"""
|
||||
Redirect to a given URL while setting the chosen language in the session
|
||||
(if enabled) and in a cookie. The URL and the language code need to be
|
||||
specified in the request parameters.
|
||||
|
||||
Since this view changes how the user will see the rest of the site, it must
|
||||
only be accessed as a POST request. If called as a GET request, it will
|
||||
redirect to the page in the request (the 'next' parameter) without changing
|
||||
any state.
|
||||
"""
|
||||
next_url = request.POST.get("next", request.GET.get("next"))
|
||||
if (
|
||||
next_url or request.accepts("text/html")
|
||||
) and not url_has_allowed_host_and_scheme(
|
||||
url=next_url,
|
||||
allowed_hosts={request.get_host()},
|
||||
require_https=request.is_secure(),
|
||||
):
|
||||
next_url = request.META.get("HTTP_REFERER")
|
||||
if not url_has_allowed_host_and_scheme(
|
||||
url=next_url,
|
||||
allowed_hosts={request.get_host()},
|
||||
require_https=request.is_secure(),
|
||||
):
|
||||
next_url = "/"
|
||||
response = HttpResponseRedirect(next_url) if next_url else HttpResponse(status=204)
|
||||
if request.method == "POST":
|
||||
lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER)
|
||||
if lang_code and check_for_language(lang_code):
|
||||
if next_url:
|
||||
next_trans = translate_url(next_url, lang_code)
|
||||
if next_trans != next_url:
|
||||
response = HttpResponseRedirect(next_trans)
|
||||
response.set_cookie(
|
||||
settings.LANGUAGE_COOKIE_NAME,
|
||||
lang_code,
|
||||
max_age=settings.LANGUAGE_COOKIE_AGE,
|
||||
path=settings.LANGUAGE_COOKIE_PATH,
|
||||
domain=settings.LANGUAGE_COOKIE_DOMAIN,
|
||||
secure=settings.LANGUAGE_COOKIE_SECURE,
|
||||
httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
|
||||
samesite=settings.LANGUAGE_COOKIE_SAMESITE,
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def get_formats():
|
||||
"""Return all formats strings required for i18n to work."""
|
||||
FORMAT_SETTINGS = (
|
||||
"DATE_FORMAT",
|
||||
"DATETIME_FORMAT",
|
||||
"TIME_FORMAT",
|
||||
"YEAR_MONTH_FORMAT",
|
||||
"MONTH_DAY_FORMAT",
|
||||
"SHORT_DATE_FORMAT",
|
||||
"SHORT_DATETIME_FORMAT",
|
||||
"FIRST_DAY_OF_WEEK",
|
||||
"DECIMAL_SEPARATOR",
|
||||
"THOUSAND_SEPARATOR",
|
||||
"NUMBER_GROUPING",
|
||||
"DATE_INPUT_FORMATS",
|
||||
"TIME_INPUT_FORMATS",
|
||||
"DATETIME_INPUT_FORMATS",
|
||||
)
|
||||
return {attr: get_format(attr) for attr in FORMAT_SETTINGS}
|
||||
|
||||
|
||||
js_catalog_template = r"""
|
||||
{% autoescape off %}
|
||||
'use strict';
|
||||
{
|
||||
const globals = this;
|
||||
const django = globals.django || (globals.django = {});
|
||||
|
||||
{% if plural %}
|
||||
django.pluralidx = function(n) {
|
||||
const v = {{ plural }};
|
||||
if (typeof v === 'boolean') {
|
||||
return v ? 1 : 0;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
};
|
||||
{% else %}
|
||||
django.pluralidx = function(count) { return (count == 1) ? 0 : 1; };
|
||||
{% endif %}
|
||||
|
||||
/* gettext library */
|
||||
|
||||
django.catalog = django.catalog || {};
|
||||
{% if catalog_str %}
|
||||
const newcatalog = {{ catalog_str }};
|
||||
for (const key in newcatalog) {
|
||||
django.catalog[key] = newcatalog[key];
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
if (!django.jsi18n_initialized) {
|
||||
django.gettext = function(msgid) {
|
||||
const value = django.catalog[msgid];
|
||||
if (typeof value === 'undefined') {
|
||||
return msgid;
|
||||
} else {
|
||||
return (typeof value === 'string') ? value : value[0];
|
||||
}
|
||||
};
|
||||
|
||||
django.ngettext = function(singular, plural, count) {
|
||||
const value = django.catalog[singular];
|
||||
if (typeof value === 'undefined') {
|
||||
return (count == 1) ? singular : plural;
|
||||
} else {
|
||||
return value.constructor === Array ? value[django.pluralidx(count)] : value;
|
||||
}
|
||||
};
|
||||
|
||||
django.gettext_noop = function(msgid) { return msgid; };
|
||||
|
||||
django.pgettext = function(context, msgid) {
|
||||
let value = django.gettext(context + '\x04' + msgid);
|
||||
if (value.includes('\x04')) {
|
||||
value = msgid;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
django.npgettext = function(context, singular, plural, count) {
|
||||
let value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
|
||||
if (value.includes('\x04')) {
|
||||
value = django.ngettext(singular, plural, count);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
django.interpolate = function(fmt, obj, named) {
|
||||
if (named) {
|
||||
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
|
||||
} else {
|
||||
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* formatting library */
|
||||
|
||||
django.formats = {{ formats_str }};
|
||||
|
||||
django.get_format = function(format_type) {
|
||||
const value = django.formats[format_type];
|
||||
if (typeof value === 'undefined') {
|
||||
return format_type;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/* add to global namespace */
|
||||
globals.pluralidx = django.pluralidx;
|
||||
globals.gettext = django.gettext;
|
||||
globals.ngettext = django.ngettext;
|
||||
globals.gettext_noop = django.gettext_noop;
|
||||
globals.pgettext = django.pgettext;
|
||||
globals.npgettext = django.npgettext;
|
||||
globals.interpolate = django.interpolate;
|
||||
globals.get_format = django.get_format;
|
||||
|
||||
django.jsi18n_initialized = true;
|
||||
}
|
||||
};
|
||||
{% endautoescape %}
|
||||
""" # NOQA
|
||||
|
||||
|
||||
class JavaScriptCatalog(View):
|
||||
"""
|
||||
Return the selected language catalog as a JavaScript library.
|
||||
|
||||
Receive the list of packages to check for translations in the `packages`
|
||||
kwarg either from the extra dictionary passed to the path() function or as
|
||||
a plus-sign delimited string from the request. Default is 'django.conf'.
|
||||
|
||||
You can override the gettext domain for this view, but usually you don't
|
||||
want to do that as JavaScript messages go to the djangojs domain. This
|
||||
might be needed if you deliver your JavaScript source from Django templates.
|
||||
"""
|
||||
|
||||
domain = "djangojs"
|
||||
packages = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
locale = get_language()
|
||||
domain = kwargs.get("domain", self.domain)
|
||||
# If packages are not provided, default to all installed packages, as
|
||||
# DjangoTranslation without localedirs harvests them all.
|
||||
packages = kwargs.get("packages", "")
|
||||
packages = packages.split("+") if packages else self.packages
|
||||
paths = self.get_paths(packages) if packages else None
|
||||
self.translation = DjangoTranslation(locale, domain=domain, localedirs=paths)
|
||||
context = self.get_context_data(**kwargs)
|
||||
return self.render_to_response(context)
|
||||
|
||||
def get_paths(self, packages):
|
||||
allowable_packages = {
|
||||
app_config.name: app_config for app_config in apps.get_app_configs()
|
||||
}
|
||||
app_configs = [
|
||||
allowable_packages[p] for p in packages if p in allowable_packages
|
||||
]
|
||||
if len(app_configs) < len(packages):
|
||||
excluded = [p for p in packages if p not in allowable_packages]
|
||||
raise ValueError(
|
||||
"Invalid package(s) provided to JavaScriptCatalog: %s"
|
||||
% ",".join(excluded)
|
||||
)
|
||||
# paths of requested packages
|
||||
return [os.path.join(app.path, "locale") for app in app_configs]
|
||||
|
||||
@property
|
||||
def _num_plurals(self):
|
||||
"""
|
||||
Return the number of plurals for this catalog language, or 2 if no
|
||||
plural string is available.
|
||||
"""
|
||||
match = re.search(r"nplurals=\s*(\d+)", self._plural_string or "")
|
||||
if match:
|
||||
return int(match[1])
|
||||
return 2
|
||||
|
||||
@property
|
||||
def _plural_string(self):
|
||||
"""
|
||||
Return the plural string (including nplurals) for this catalog language,
|
||||
or None if no plural string is available.
|
||||
"""
|
||||
if "" in self.translation._catalog:
|
||||
for line in self.translation._catalog[""].split("\n"):
|
||||
if line.startswith("Plural-Forms:"):
|
||||
return line.split(":", 1)[1].strip()
|
||||
return None
|
||||
|
||||
def get_plural(self):
|
||||
plural = self._plural_string
|
||||
if plural is not None:
|
||||
# This should be a compiled function of a typical plural-form:
|
||||
# Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 :
|
||||
# n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
|
||||
plural = [
|
||||
el.strip()
|
||||
for el in plural.split(";")
|
||||
if el.strip().startswith("plural=")
|
||||
][0].split("=", 1)[1]
|
||||
return plural
|
||||
|
||||
def get_catalog(self):
|
||||
pdict = {}
|
||||
catalog = {}
|
||||
translation = self.translation
|
||||
seen_keys = set()
|
||||
while True:
|
||||
for key, value in translation._catalog.items():
|
||||
if key == "" or key in seen_keys:
|
||||
continue
|
||||
if isinstance(key, str):
|
||||
catalog[key] = value
|
||||
elif isinstance(key, tuple):
|
||||
msgid, cnt = key
|
||||
pdict.setdefault(msgid, {})[cnt] = value
|
||||
else:
|
||||
raise TypeError(key)
|
||||
seen_keys.add(key)
|
||||
if translation._fallback:
|
||||
translation = translation._fallback
|
||||
else:
|
||||
break
|
||||
|
||||
num_plurals = self._num_plurals
|
||||
for k, v in pdict.items():
|
||||
catalog[k] = [v.get(i, "") for i in range(num_plurals)]
|
||||
return catalog
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {
|
||||
"catalog": self.get_catalog(),
|
||||
"formats": get_formats(),
|
||||
"plural": self.get_plural(),
|
||||
}
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
def indent(s):
|
||||
return s.replace("\n", "\n ")
|
||||
|
||||
template = Engine().from_string(js_catalog_template)
|
||||
context["catalog_str"] = (
|
||||
indent(json.dumps(context["catalog"], sort_keys=True, indent=2))
|
||||
if context["catalog"]
|
||||
else None
|
||||
)
|
||||
context["formats_str"] = indent(
|
||||
json.dumps(context["formats"], sort_keys=True, indent=2)
|
||||
)
|
||||
|
||||
return HttpResponse(
|
||||
template.render(Context(context)), 'text/javascript; charset="utf-8"'
|
||||
)
|
||||
|
||||
|
||||
class JSONCatalog(JavaScriptCatalog):
|
||||
"""
|
||||
Return the selected language catalog as a JSON object.
|
||||
|
||||
Receive the same parameters as JavaScriptCatalog and return a response
|
||||
with a JSON object of the following format:
|
||||
|
||||
{
|
||||
"catalog": {
|
||||
# Translations catalog
|
||||
},
|
||||
"formats": {
|
||||
# Language formats for date, time, etc.
|
||||
},
|
||||
"plural": '...' # Expression for plural forms, or null.
|
||||
}
|
||||
"""
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
return JsonResponse(context)
|
132
srcs/.venv/lib/python3.11/site-packages/django/views/static.py
Normal file
132
srcs/.venv/lib/python3.11/site-packages/django/views/static.py
Normal file
@ -0,0 +1,132 @@
|
||||
"""
|
||||
Views and functions for serving static files. These are only to be used
|
||||
during development, and SHOULD NOT be used in a production setting.
|
||||
"""
|
||||
import mimetypes
|
||||
import posixpath
|
||||
from pathlib import Path
|
||||
|
||||
from django.http import FileResponse, Http404, HttpResponse, HttpResponseNotModified
|
||||
from django.template import Context, Engine, TemplateDoesNotExist, loader
|
||||
from django.utils._os import safe_join
|
||||
from django.utils.http import http_date, parse_http_date
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
|
||||
def serve(request, path, document_root=None, show_indexes=False):
|
||||
"""
|
||||
Serve static files below a given point in the directory structure.
|
||||
|
||||
To use, put a URL pattern such as::
|
||||
|
||||
from django.views.static import serve
|
||||
|
||||
path('<path:path>', serve, {'document_root': '/path/to/my/files/'})
|
||||
|
||||
in your URLconf. You must provide the ``document_root`` param. You may
|
||||
also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
|
||||
of the directory. This index view will use the template hardcoded below,
|
||||
but if you'd like to override it, you can create a template called
|
||||
``static/directory_index.html``.
|
||||
"""
|
||||
path = posixpath.normpath(path).lstrip("/")
|
||||
fullpath = Path(safe_join(document_root, path))
|
||||
if fullpath.is_dir():
|
||||
if show_indexes:
|
||||
return directory_index(path, fullpath)
|
||||
raise Http404(_("Directory indexes are not allowed here."))
|
||||
if not fullpath.exists():
|
||||
raise Http404(_("“%(path)s” does not exist") % {"path": fullpath})
|
||||
# Respect the If-Modified-Since header.
|
||||
statobj = fullpath.stat()
|
||||
if not was_modified_since(
|
||||
request.META.get("HTTP_IF_MODIFIED_SINCE"), statobj.st_mtime
|
||||
):
|
||||
return HttpResponseNotModified()
|
||||
content_type, encoding = mimetypes.guess_type(str(fullpath))
|
||||
content_type = content_type or "application/octet-stream"
|
||||
response = FileResponse(fullpath.open("rb"), content_type=content_type)
|
||||
response.headers["Last-Modified"] = http_date(statobj.st_mtime)
|
||||
if encoding:
|
||||
response.headers["Content-Encoding"] = encoding
|
||||
return response
|
||||
|
||||
|
||||
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Language" content="en-us">
|
||||
<meta name="robots" content="NONE,NOARCHIVE">
|
||||
<title>{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{% blocktranslate %}Index of {{ directory }}{% endblocktranslate %}</h1>
|
||||
<ul>
|
||||
{% if directory != "/" %}
|
||||
<li><a href="../">../</a></li>
|
||||
{% endif %}
|
||||
{% for f in file_list %}
|
||||
<li><a href="{{ f|urlencode }}">{{ f }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
template_translatable = gettext_lazy("Index of %(directory)s")
|
||||
|
||||
|
||||
def directory_index(path, fullpath):
|
||||
try:
|
||||
t = loader.select_template(
|
||||
[
|
||||
"static/directory_index.html",
|
||||
"static/directory_index",
|
||||
]
|
||||
)
|
||||
except TemplateDoesNotExist:
|
||||
t = Engine(libraries={"i18n": "django.templatetags.i18n"}).from_string(
|
||||
DEFAULT_DIRECTORY_INDEX_TEMPLATE
|
||||
)
|
||||
c = Context()
|
||||
else:
|
||||
c = {}
|
||||
files = []
|
||||
for f in fullpath.iterdir():
|
||||
if not f.name.startswith("."):
|
||||
url = str(f.relative_to(fullpath))
|
||||
if f.is_dir():
|
||||
url += "/"
|
||||
files.append(url)
|
||||
c.update(
|
||||
{
|
||||
"directory": path + "/",
|
||||
"file_list": files,
|
||||
}
|
||||
)
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
|
||||
def was_modified_since(header=None, mtime=0):
|
||||
"""
|
||||
Was something modified since the user last downloaded it?
|
||||
|
||||
header
|
||||
This is the value of the If-Modified-Since header. If this is None,
|
||||
I'll just return True.
|
||||
|
||||
mtime
|
||||
This is the modification time of the item we're talking about.
|
||||
"""
|
||||
try:
|
||||
if header is None:
|
||||
raise ValueError
|
||||
header_mtime = parse_http_date(header)
|
||||
if int(mtime) > header_mtime:
|
||||
raise ValueError
|
||||
except (ValueError, OverflowError):
|
||||
return True
|
||||
return False
|
@ -0,0 +1,253 @@
|
||||
{% load i18n %}
|
||||
<!doctype html>
|
||||
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
|
||||
<html lang="{{ LANGUAGE_CODE|default:'en-us' }}" dir="{{ LANGUAGE_BIDI|yesno:'rtl,ltr,auto' }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{% translate "The install worked successfully! Congratulations!" %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
html {
|
||||
line-height: 1.15;
|
||||
}
|
||||
a {
|
||||
color: #19865C;
|
||||
}
|
||||
header {
|
||||
border-bottom: 1px solid #efefef;
|
||||
}
|
||||
body {
|
||||
max-width: 960px;
|
||||
color: #525252;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", ui-system, sans-serif;
|
||||
margin: 0 auto;
|
||||
}
|
||||
main {
|
||||
text-align: center;
|
||||
}
|
||||
h1, h2, h3, h4, h5, p, ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
header {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
align-items: self-end;
|
||||
justify-content: space-between;
|
||||
gap: 7px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.logo {
|
||||
font-weight: 700;
|
||||
font-size: 1.375rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.figure {
|
||||
margin-top: 19vh;
|
||||
max-width: 265px;
|
||||
position: relative;
|
||||
z-index: -9;
|
||||
overflow: visible;
|
||||
}
|
||||
.exhaust__line {
|
||||
animation: thrust 70ms 100 ease-in-out alternate;
|
||||
}
|
||||
.smoke {
|
||||
animation: smoke .1s 70 ease-in-out alternate;
|
||||
}
|
||||
@keyframes smoke {
|
||||
0% {
|
||||
transform: translate3d(-5px, 0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(5px, 0, 0);
|
||||
}
|
||||
}
|
||||
.flame {
|
||||
animation: burnInner2 .1s 70 ease-in-out alternate;
|
||||
}
|
||||
@keyframes burnInner2 {
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(0, 3px, 0);
|
||||
}
|
||||
}
|
||||
@keyframes thrust {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.exhaust__line,
|
||||
.smoke,
|
||||
.flame {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.375rem;
|
||||
max-width: 32rem;
|
||||
margin: 5px auto 0;
|
||||
}
|
||||
main p {
|
||||
line-height: 1.25;
|
||||
max-width: 26rem;
|
||||
margin: 15px auto 0;
|
||||
}
|
||||
footer {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 5px;
|
||||
padding: 25px 0;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
width: 960px;
|
||||
transform: translateX(-50%);
|
||||
transform-style: preserve-3d;
|
||||
border-top: 1px solid #efefef;
|
||||
}
|
||||
.option {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
}
|
||||
.option svg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: gray;
|
||||
border: 1px solid #d6d6d6;
|
||||
padding: 5px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.option p {
|
||||
font-weight: 300;
|
||||
line-height: 1.25;
|
||||
color: #525252;
|
||||
display: table;
|
||||
}
|
||||
.option .option__heading {
|
||||
color: #19865C;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
@media (max-width: 996px) {
|
||||
body, footer {
|
||||
max-width: 780px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
footer {
|
||||
height: 100%;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 60px;
|
||||
position: relative;
|
||||
padding: 25px;
|
||||
}
|
||||
.figure {
|
||||
margin-top: 10px;
|
||||
}
|
||||
main {
|
||||
padding: 0 25px;
|
||||
}
|
||||
main h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
header {
|
||||
grid-template-columns: 1fr;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
footer {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 801px) and (max-height: 730px) {
|
||||
.figure {
|
||||
margin-top: 80px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 801px) and (max-height: 600px) {
|
||||
footer {
|
||||
position: relative;
|
||||
margin: 135px auto 0;
|
||||
}
|
||||
.figure {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
.sr-only {
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a class="logo" href="https://www.djangoproject.com/" target="_blank" rel="noopener">
|
||||
django
|
||||
</a>
|
||||
<p>{% blocktranslate %}View <a href="https://docs.djangoproject.com/en/{{ version }}/releases/" target="_blank" rel="noopener">release notes</a> for Django {{ version }}{% endblocktranslate %}</p>
|
||||
</header>
|
||||
<main>
|
||||
<svg class="figure" viewBox="0 0 508 268" aria-hidden="true">
|
||||
<path d="M305.2 156.6c0 4.6-.5 9-1.6 13.2-2.5-4.4-5.6-8.4-9.2-12-4.6-4.6-10-8.4-16-11.2 2.8-11.2 4.5-22.9 5-34.6 1.8 1.4 3.5 2.9 5 4.5 10.5 10.3 16.8 24.5 16.8 40.1zm-75-10c-6 2.8-11.4 6.6-16 11.2-3.5 3.6-6.6 7.6-9.1 12-1-4.3-1.6-8.7-1.6-13.2 0-15.7 6.3-29.9 16.6-40.1 1.6-1.6 3.3-3.1 5.1-4.5.6 11.8 2.2 23.4 5 34.6z" fill="#2E3B39" fill-rule="nonzero"/>
|
||||
<path d="M282.981 152.6c16.125-48.1 6.375-104-29.25-142.6-35.625 38.5-45.25 94.5-29.25 142.6h58.5z" stroke="#FFF" stroke-width="3.396" fill="#6DDCBD"/>
|
||||
<path d="M271 29.7c-4.4-10.6-9.9-20.6-16.6-29.7-6.7 9-12.2 19-16.6 29.7H271z" stroke="#FFF" stroke-width="3" fill="#2E3B39"/>
|
||||
<circle fill="#FFF" cx="254.3" cy="76.8" r="15.5"/>
|
||||
<circle stroke="#FFF" stroke-width="7" fill="#6DDCBD" cx="254.3" cy="76.8" r="12.2"/>
|
||||
<path class="smoke" d="M507.812 234.24c0-2.16-.632-4.32-1.58-6.24-3.318-6.72-11.85-11.52-21.804-11.52-1.106 0-2.212.12-3.318.24-.474-11.52-12.956-20.76-28.282-20.76-3.318 0-6.636.48-9.638 1.32-4.74-6.72-14.062-11.28-24.806-11.28-.79 0-1.58 0-2.37.12-.79 0-1.58-.12-2.37-.12-10.744 0-20.066 4.56-24.806 11.28a35.326 35.326 0 00-9.638-1.32c-15.642 0-28.282 9.6-28.282 21.48 0 1.32.158 2.76.474 3.96a26.09 26.09 0 00-4.424-.36c-8.058 0-15.01 3.12-19.118 7.8-3.476-1.68-7.742-2.76-12.324-2.76-12.008 0-21.804 7.08-22.752 15.96h-.158c-9.322 0-17.38 4.32-20.856 10.44-4.108-3.6-10.27-6-17.222-6h-1.264c-6.794 0-12.956 2.28-17.222 6-3.476-6.12-11.534-10.44-20.856-10.44h-.158c-.948-9-10.744-15.96-22.752-15.96-4.582 0-8.69.96-12.324 2.76-4.108-4.68-11.06-7.8-19.118-7.8-1.422 0-3.002.12-4.424.36.316-1.32.474-2.64.474-3.96 0-11.88-12.64-21.48-28.282-21.48-3.318 0-6.636.48-9.638 1.32-4.74-6.72-14.062-11.28-24.806-11.28-.79 0-1.58 0-2.37.12-.79 0-1.58-.12-2.37-.12-10.744 0-20.066 4.56-24.806 11.28a35.326 35.326 0 00-9.638-1.32c-15.326 0-27.808 9.24-28.282 20.76-1.106-.12-2.212-.24-3.318-.24-9.954 0-18.486 4.8-21.804 11.52-.948 1.92-1.58 4.08-1.58 6.24 0 4.8 2.528 9.12 6.636 12.36-.79 1.44-1.264 3.12-1.264 4.8 0 7.2 7.742 13.08 17.222 13.08h462.15c9.48 0 17.222-5.88 17.222-13.08 0-1.68-.474-3.36-1.264-4.8 4.582-3.24 7.11-7.56 7.11-12.36z" fill="#E6E9EE"/>
|
||||
<path fill="#6DDCBD" d="M239 152h30v8h-30z"/>
|
||||
<path class="exhaust__line" fill="#E6E9EE" d="M250 172h7v90h-7z"/>
|
||||
<path class="flame" d="M250.27 178.834l-5.32-8.93s-2.47-5.7 3.458-6.118h10.26s6.232.266 3.306 6.194l-5.244 8.93s-3.23 4.37-6.46 0v-.076z" fill="#AA2247"/>
|
||||
</svg>
|
||||
<h1>{% translate "The install worked successfully! Congratulations!" %}</h1>
|
||||
<p>{% blocktranslate %}You are seeing this page because <a href="https://docs.djangoproject.com/en/{{ version }}/ref/settings/#debug" target="_blank" rel="noopener">DEBUG=True</a> is in your settings file and you have not configured any URLs.{% endblocktranslate %}</p>
|
||||
</main>
|
||||
<footer>
|
||||
<a class="option" href="https://docs.djangoproject.com/en/{{ version }}/" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z"></path>
|
||||
</svg>
|
||||
<p>
|
||||
<span class="option__heading">{% translate "Django Documentation" %}</span><span class="sr-only">.</span><br>
|
||||
{% translate 'Topics, references, & how-to’s' %}
|
||||
</p>
|
||||
</a>
|
||||
<a class="option" href="https://docs.djangoproject.com/en/{{ version }}/intro/tutorial01/" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"></path>
|
||||
</svg>
|
||||
<p>
|
||||
<span class="option__heading">{% translate "Tutorial: A Polling App" %}</span><span class="sr-only">.</span><br>
|
||||
{% translate "Get started with Django" %}
|
||||
</p>
|
||||
</a>
|
||||
<a class="option" href="https://www.djangoproject.com/community/" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M16.5 13c-1.2 0-3.07.34-4.5 1-1.43-.67-3.3-1-4.5-1C5.33 13 1 14.08 1 16.25V19h22v-2.75c0-2.17-4.33-3.25-6.5-3.25zm-4 4.5h-10v-1.25c0-.54 2.56-1.75 5-1.75s5 1.21 5 1.75v1.25zm9 0H14v-1.25c0-.46-.2-.86-.52-1.22.88-.3 1.96-.53 3.02-.53 2.44 0 5 1.21 5 1.75v1.25zM7.5 12c1.93 0 3.5-1.57 3.5-3.5S9.43 5 7.5 5 4 6.57 4 8.5 5.57 12 7.5 12zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 5.5c1.93 0 3.5-1.57 3.5-3.5S18.43 5 16.5 5 13 6.57 13 8.5s1.57 3.5 3.5 3.5zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2z"></path>
|
||||
</svg>
|
||||
<p>
|
||||
<span class="option__heading">{% translate "Django Community" %}</span><span class="sr-only">.</span><br>
|
||||
{% translate "Connect, get help, or contribute" %}
|
||||
</p>
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<title>Page not found at {{ request.path_info }}</title>
|
||||
<meta name="robots" content="NONE,NOARCHIVE">
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; background:#eee; color:#000; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; margin-bottom:.4em; }
|
||||
h1 span { font-size:60%; color:#666; font-weight:normal; }
|
||||
table { border:none; border-collapse: collapse; width:100%; }
|
||||
td, th { vertical-align:top; padding:2px 3px; }
|
||||
th { width:12em; text-align:right; color:#666; padding-right:.5em; }
|
||||
#info { background:#f6f6f6; }
|
||||
#info ol { margin: 0.5em 4em; }
|
||||
#info ol li { font-family: monospace; }
|
||||
#summary { background: #ffc; }
|
||||
#explanation { background:#eee; border-bottom: 0px none; }
|
||||
pre.exception_value { font-family: sans-serif; color: #575757; font-size: 1.5em; margin: 10px 0 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="summary">
|
||||
<h1>Page not found <span>(404)</span></h1>
|
||||
{% if reason and resolved %}<pre class="exception_value">{{ reason }}</pre>{% endif %}
|
||||
<table class="meta">
|
||||
<tr>
|
||||
<th>Request Method:</th>
|
||||
<td>{{ request.META.REQUEST_METHOD }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Request URL:</th>
|
||||
<td>{{ request.build_absolute_uri }}</td>
|
||||
</tr>
|
||||
{% if raising_view_name %}
|
||||
<tr>
|
||||
<th>Raised by:</th>
|
||||
<td>{{ raising_view_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div id="info">
|
||||
{% if urlpatterns %}
|
||||
<p>
|
||||
Using the URLconf defined in <code>{{ urlconf }}</code>,
|
||||
Django tried these URL patterns, in this order:
|
||||
</p>
|
||||
<ol>
|
||||
{% for pattern in urlpatterns %}
|
||||
<li>
|
||||
{% for pat in pattern %}
|
||||
{{ pat.pattern }}
|
||||
{% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
<p>
|
||||
{% if request_path %}
|
||||
The current path, <code>{{ request_path }}</code>,
|
||||
{% else %}
|
||||
The empty path
|
||||
{% endif %}
|
||||
{% if resolved %}matched the last one.{% else %}didn’t match any of these.{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="explanation">
|
||||
<p>
|
||||
You’re seeing this error because you have <code>DEBUG = True</code> in
|
||||
your Django settings file. Change that to <code>False</code>, and Django
|
||||
will display a standard 404 page.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,491 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="robots" content="NONE,NOARCHIVE">
|
||||
<title>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}
|
||||
{% if request %} at {{ request.path_info }}{% endif %}</title>
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; background-color:#fff; color:#000; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; }
|
||||
h2 { margin-bottom:.8em; }
|
||||
h3 { margin:1em 0 .5em 0; }
|
||||
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
||||
code, pre { font-size: 100%; white-space: pre-wrap; word-break: break-word; }
|
||||
summary { cursor: pointer; }
|
||||
table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
|
||||
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
||||
thead th {
|
||||
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
||||
font-weight:normal; font-size:11px; border:1px solid #ddd;
|
||||
}
|
||||
tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
|
||||
table.vars { margin:5px 10px 2px 40px; width: auto; }
|
||||
table.vars td, table.req td { font-family:monospace; }
|
||||
table td.code { width:100%; }
|
||||
table td.code pre { overflow:hidden; }
|
||||
table.source th { color:#666; }
|
||||
table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
||||
ul.traceback { list-style-type:none; color: #222; }
|
||||
ul.traceback li.cause { word-break: break-word; }
|
||||
ul.traceback li.frame { padding-bottom:1em; color:#4f4f4f; }
|
||||
ul.traceback li.user { background-color:#e0e0e0; color:#000 }
|
||||
div.context { padding:10px 0; overflow:hidden; }
|
||||
div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; }
|
||||
div.context ol li { font-family:monospace; white-space:pre; color:#777; cursor:pointer; padding-left: 2px; }
|
||||
div.context ol li pre { display:inline; }
|
||||
div.context ol.context-line li { color:#464646; background-color:#dfdfdf; padding: 3px 2px; }
|
||||
div.context ol.context-line li span { position:absolute; right:32px; }
|
||||
.user div.context ol.context-line li { background-color:#bbb; color:#000; }
|
||||
.user div.context ol li { color:#666; }
|
||||
div.commands, summary.commands { margin-left: 40px; }
|
||||
div.commands a, summary.commands { color:#555; text-decoration:none; }
|
||||
.user div.commands a { color: black; }
|
||||
#summary { background: #ffc; }
|
||||
#summary h2 { font-weight: normal; color: #666; }
|
||||
#explanation { background:#eee; }
|
||||
#template, #template-not-exist { background:#f6f6f6; }
|
||||
#template-not-exist ul { margin: 0 0 10px 20px; }
|
||||
#template-not-exist .postmortem-section { margin-bottom: 3px; }
|
||||
#unicode-hint { background:#eee; }
|
||||
#traceback { background:#eee; }
|
||||
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
||||
#summary table { border:none; background:transparent; }
|
||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
||||
#requestinfo h3 { margin-bottom:-1em; }
|
||||
.error { background: #ffc; }
|
||||
.specific { color:#cc3300; font-weight:bold; }
|
||||
h2 span.commands { font-size:.7em; font-weight:normal; }
|
||||
span.commands a:link {color:#5E5694;}
|
||||
pre.exception_value { font-family: sans-serif; color: #575757; font-size: 1.5em; margin: 10px 0 10px 0; }
|
||||
.append-bottom { margin-bottom: 10px; }
|
||||
.fname { user-select: all; }
|
||||
</style>
|
||||
{% if not is_email %}
|
||||
<script>
|
||||
function hideAll(elems) {
|
||||
for (var e = 0; e < elems.length; e++) {
|
||||
elems[e].style.display = 'none';
|
||||
}
|
||||
}
|
||||
window.onload = function() {
|
||||
hideAll(document.querySelectorAll('ol.pre-context'));
|
||||
hideAll(document.querySelectorAll('ol.post-context'));
|
||||
hideAll(document.querySelectorAll('div.pastebin'));
|
||||
}
|
||||
function toggle() {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var e = document.getElementById(arguments[i]);
|
||||
if (e) {
|
||||
e.style.display = e.style.display == 'none' ? 'block': 'none';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function switchPastebinFriendly(link) {
|
||||
s1 = "Switch to copy-and-paste view";
|
||||
s2 = "Switch back to interactive view";
|
||||
link.textContent = link.textContent.trim() == s1 ? s2: s1;
|
||||
toggle('browserTraceback', 'pastebinTraceback');
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="summary">
|
||||
<h1>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}
|
||||
{% if request %} at {{ request.path_info }}{% endif %}</h1>
|
||||
<pre class="exception_value">{% if exception_value %}{{ exception_value|force_escape }}{% if exception_notes %}{{ exception_notes }}{% endif %}{% else %}No exception message supplied{% endif %}</pre>
|
||||
<table class="meta">
|
||||
{% if request %}
|
||||
<tr>
|
||||
<th>Request Method:</th>
|
||||
<td>{{ request.META.REQUEST_METHOD }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Request URL:</th>
|
||||
<td>{{ request_insecure_uri }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Django Version:</th>
|
||||
<td>{{ django_version_info }}</td>
|
||||
</tr>
|
||||
{% if exception_type %}
|
||||
<tr>
|
||||
<th>Exception Type:</th>
|
||||
<td>{{ exception_type }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if exception_type and exception_value %}
|
||||
<tr>
|
||||
<th>Exception Value:</th>
|
||||
<td><pre>{{ exception_value|force_escape }}</pre></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if lastframe %}
|
||||
<tr>
|
||||
<th>Exception Location:</th>
|
||||
<td><span class="fname">{{ lastframe.filename }}</span>, line {{ lastframe.lineno }}, in {{ lastframe.function }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if raising_view_name %}
|
||||
<tr>
|
||||
<th>Raised during:</th>
|
||||
<td>{{ raising_view_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Python Executable:</th>
|
||||
<td>{{ sys_executable }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Python Version:</th>
|
||||
<td>{{ sys_version_info }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Python Path:</th>
|
||||
<td><pre>{{ sys_path|pprint }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Server time:</th>
|
||||
<td>{{server_time|date:"r"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% if unicode_hint %}
|
||||
<div id="unicode-hint">
|
||||
<h2>Unicode error hint</h2>
|
||||
<p>The string that could not be encoded/decoded was: <strong>{{ unicode_hint }}</strong></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if template_does_not_exist %}
|
||||
<div id="template-not-exist">
|
||||
<h2>Template-loader postmortem</h2>
|
||||
{% if postmortem %}
|
||||
<p class="append-bottom">Django tried loading these templates, in this order:</p>
|
||||
{% for entry in postmortem %}
|
||||
<p class="postmortem-section">Using engine <code>{{ entry.backend.name }}</code>:</p>
|
||||
<ul>
|
||||
{% if entry.tried %}
|
||||
{% for attempt in entry.tried %}
|
||||
<li><code>{{ attempt.0.loader_name }}</code>: {{ attempt.0.name }} ({{ attempt.1 }})</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<li>This engine did not provide a list of tried templates.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>No templates were found because your 'TEMPLATES' setting is not configured.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if template_info %}
|
||||
<div id="template">
|
||||
<h2>Error during template rendering</h2>
|
||||
<p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
|
||||
<h3>{{ template_info.message|force_escape }}</h3>
|
||||
<table class="source{% if template_info.top %} cut-top{% endif %}
|
||||
{% if template_info.bottom != template_info.total %} cut-bottom{% endif %}">
|
||||
{% for source_line in template_info.source_lines %}
|
||||
{% if source_line.0 == template_info.line %}
|
||||
<tr class="error"><th>{{ source_line.0 }}</th>
|
||||
<td>{{ template_info.before }}<span class="specific">{{ template_info.during }}</span>{{ template_info.after }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><th>{{ source_line.0 }}</th>
|
||||
<td>{{ source_line.1 }}</td></tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if frames %}
|
||||
<div id="traceback">
|
||||
<h2>Traceback{% if not is_email %} <span class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">
|
||||
Switch to copy-and-paste view</a></span>{% endif %}
|
||||
</h2>
|
||||
<div id="browserTraceback">
|
||||
<ul class="traceback">
|
||||
{% for frame in frames %}
|
||||
{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
|
||||
<li class="cause"><h3>
|
||||
{% if frame.exc_cause_explicit %}
|
||||
The above exception ({{ frame.exc_cause|force_escape }}) was the direct cause of the following exception:
|
||||
{% else %}
|
||||
During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred:
|
||||
{% endif %}
|
||||
</h3></li>
|
||||
{% endif %}{% endifchanged %}
|
||||
<li class="frame {{ frame.type }}">
|
||||
{% if frame.tb %}
|
||||
<code class="fname">{{ frame.filename }}</code>, line {{ frame.lineno }}, in {{ frame.function }}
|
||||
{% elif forloop.first %}
|
||||
None
|
||||
{% else %}
|
||||
Traceback: None
|
||||
{% endif %}
|
||||
|
||||
{% if frame.context_line %}
|
||||
<div class="context" id="c{{ frame.id }}">
|
||||
{% if frame.pre_context and not is_email %}
|
||||
<ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">
|
||||
{% for line in frame.pre_context %}
|
||||
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ line }}</pre></li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
<ol start="{{ frame.lineno }}" class="context-line">
|
||||
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ frame.context_line }}{{ frame.colno }}</pre>{% if not is_email %} <span>…</span>{% endif %}</li>
|
||||
</ol>
|
||||
{% if frame.post_context and not is_email %}
|
||||
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">
|
||||
{% for line in frame.post_context %}
|
||||
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ line }}</pre></li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if frame.vars %}
|
||||
{% if is_email %}
|
||||
<div class="commands">
|
||||
<h2>Local Vars</h2>
|
||||
</div>
|
||||
{% else %}
|
||||
<details>
|
||||
<summary class="commands">Local vars</summary>
|
||||
{% endif %}
|
||||
<table class="vars" id="v{{ frame.id }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for var in frame.vars|dictsort:0 %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><pre>{{ var.1 }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if not is_email %}</details>{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if not is_email %}
|
||||
<form action="https://dpaste.com/" name="pasteform" id="pasteform" method="post">
|
||||
<div id="pastebinTraceback" class="pastebin">
|
||||
<input type="hidden" name="language" value="PythonConsole">
|
||||
<input type="hidden" name="title"
|
||||
value="{{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}">
|
||||
<input type="hidden" name="source" value="Django Dpaste Agent">
|
||||
<input type="hidden" name="poster" value="Django">
|
||||
<textarea name="content" id="traceback_area" cols="140" rows="25">
|
||||
Environment:
|
||||
|
||||
{% if request %}
|
||||
Request Method: {{ request.META.REQUEST_METHOD }}
|
||||
Request URL: {{ request_insecure_uri }}
|
||||
{% endif %}
|
||||
Django Version: {{ django_version_info }}
|
||||
Python Version: {{ sys_version_info }}
|
||||
Installed Applications:
|
||||
{{ settings.INSTALLED_APPS|pprint }}
|
||||
Installed Middleware:
|
||||
{{ settings.MIDDLEWARE|pprint }}
|
||||
|
||||
{% if template_does_not_exist %}Template loader postmortem
|
||||
{% if postmortem %}Django tried loading these templates, in this order:
|
||||
{% for entry in postmortem %}
|
||||
Using engine {{ entry.backend.name }}:
|
||||
{% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
|
||||
{% endfor %}{% else %} This engine did not provide a list of tried templates.
|
||||
{% endif %}{% endfor %}
|
||||
{% else %}No templates were found because your 'TEMPLATES' setting is not configured.
|
||||
{% endif %}{% endif %}{% if template_info %}
|
||||
Template error:
|
||||
In template {{ template_info.name }}, error at line {{ template_info.line }}
|
||||
{{ template_info.message|force_escape }}
|
||||
{% for source_line in template_info.source_lines %}{% if source_line.0 == template_info.line %} {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }}{% else %} {{ source_line.0 }} : {{ source_line.1 }}{% endif %}{% endfor %}{% endif %}
|
||||
|
||||
Traceback (most recent call last):{% for frame in frames %}
|
||||
{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}{% if frame.exc_cause_explicit %}
|
||||
The above exception ({{ frame.exc_cause|force_escape }}) was the direct cause of the following exception:
|
||||
{% else %}
|
||||
During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred:
|
||||
{% endif %}{% endif %}{% endifchanged %} {% if frame.tb %}File "{{ frame.filename }}"{% if frame.context_line %}, line {{ frame.lineno }}{% endif %}, in {{ frame.function }}
|
||||
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{{ frame.tb_area_colno }}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}{% endfor %}
|
||||
|
||||
Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
|
||||
Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ exception_notes }}{% endif %}
|
||||
</textarea>
|
||||
<br><br>
|
||||
<input type="submit" value="Share this traceback on a public website">
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="requestinfo">
|
||||
<h2>Request information</h2>
|
||||
|
||||
{% if request %}
|
||||
{% if user_str %}
|
||||
<h3 id="user-info">USER</h3>
|
||||
<p>{{ user_str }}</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="get-info">GET</h3>
|
||||
{% if request.GET %}
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, v in request_GET_items %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td class="code"><pre>{{ v|pprint }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No GET data</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="post-info">POST</h3>
|
||||
{% if filtered_POST_items %}
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, v in filtered_POST_items %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td class="code"><pre>{{ v|pprint }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No POST data</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="files-info">FILES</h3>
|
||||
{% if request.FILES %}
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, v in request_FILES_items %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td class="code"><pre>{{ v|pprint }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No FILES data</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="cookie-info">COOKIES</h3>
|
||||
{% if request.COOKIES %}
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, v in request_COOKIES_items %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td class="code"><pre>{{ v|pprint }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No cookie data</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="meta-info">META</h3>
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, v in request_meta.items|dictsort:0 %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td class="code"><pre>{{ v|pprint }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>Request data not supplied</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 id="settings-info">Settings</h3>
|
||||
<h4>Using settings module <code>{{ settings.SETTINGS_MODULE }}</code></h4>
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, v in settings.items|dictsort:0 %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td class="code"><pre>{{ v|pprint }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
{% if not is_email %}
|
||||
<div id="explanation">
|
||||
<p>
|
||||
You’re seeing this error because you have <code>DEBUG = True</code> in your
|
||||
Django settings file. Change that to <code>False</code>, and Django will
|
||||
display a standard page generated by the handler for this status code.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,66 @@
|
||||
{% firstof exception_type 'Report' %}{% if request %} at {{ request.path_info }}{% endif %}
|
||||
{% firstof exception_value 'No exception message supplied' %}
|
||||
{% if request %}
|
||||
Request Method: {{ request.META.REQUEST_METHOD }}
|
||||
Request URL: {{ request_insecure_uri }}{% endif %}
|
||||
Django Version: {{ django_version_info }}
|
||||
Python Executable: {{ sys_executable }}
|
||||
Python Version: {{ sys_version_info }}
|
||||
Python Path: {{ sys_path }}
|
||||
Server time: {{server_time|date:"r"}}
|
||||
Installed Applications:
|
||||
{{ settings.INSTALLED_APPS|pprint }}
|
||||
Installed Middleware:
|
||||
{{ settings.MIDDLEWARE|pprint }}
|
||||
{% if template_does_not_exist %}Template loader postmortem
|
||||
{% if postmortem %}Django tried loading these templates, in this order:
|
||||
{% for entry in postmortem %}
|
||||
Using engine {{ entry.backend.name }}:
|
||||
{% if entry.tried %}{% for attempt in entry.tried %} * {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
|
||||
{% endfor %}{% else %} This engine did not provide a list of tried templates.
|
||||
{% endif %}{% endfor %}
|
||||
{% else %}No templates were found because your 'TEMPLATES' setting is not configured.
|
||||
{% endif %}
|
||||
{% endif %}{% if template_info %}
|
||||
Template error:
|
||||
In template {{ template_info.name }}, error at line {{ template_info.line }}
|
||||
{{ template_info.message }}
|
||||
{% for source_line in template_info.source_lines %}{% if source_line.0 == template_info.line %} {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }}{% else %} {{ source_line.0 }} : {{ source_line.1 }}{% endif %}{% endfor %}{% endif %}{% if frames %}
|
||||
|
||||
Traceback (most recent call last):
|
||||
{% for frame in frames %}{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
|
||||
{% if frame.exc_cause_explicit %}The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception:{% else %}During handling of the above exception ({{ frame.exc_cause }}), another exception occurred:{% endif %}
|
||||
{% endif %}{% endifchanged %} {% if frame.tb %}File "{{ frame.filename }}"{% if frame.context_line %}, line {{ frame.lineno }}{% endif %}, in {{ frame.function }}
|
||||
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{{ frame.tb_area_colno }}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}
|
||||
{% endfor %}
|
||||
{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
|
||||
{% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% if exception_notes %}{{ exception_notes }}{% endif %}{% endif %}{% endif %}
|
||||
{% if raising_view_name %}Raised during: {{ raising_view_name }}{% endif %}
|
||||
{% if request %}Request information:
|
||||
{% if user_str %}USER: {{ user_str }}{% endif %}
|
||||
|
||||
GET:{% for k, v in request_GET_items %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% empty %} No GET data{% endfor %}
|
||||
|
||||
POST:{% for k, v in filtered_POST_items %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% empty %} No POST data{% endfor %}
|
||||
|
||||
FILES:{% for k, v in request_FILES_items %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% empty %} No FILES data{% endfor %}
|
||||
|
||||
COOKIES:{% for k, v in request_COOKIES_items %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% empty %} No cookie data{% endfor %}
|
||||
|
||||
META:{% for k, v in request_meta.items|dictsort:0 %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% endfor %}
|
||||
{% else %}Request data not supplied
|
||||
{% endif %}
|
||||
Settings:
|
||||
Using settings module {{ settings.SETTINGS_MODULE }}{% for k, v in settings.items|dictsort:0 %}
|
||||
{{ k }} = {{ v|stringformat:"r" }}{% endfor %}
|
||||
|
||||
{% if not is_email %}
|
||||
You’re seeing this error because you have DEBUG = True in your
|
||||
Django settings file. Change that to False, and Django will
|
||||
display a standard page generated by the handler for this status code.
|
||||
{% endif %}
|
Reference in New Issue
Block a user