17.12
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
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)(
|
||||
cache_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):
|
||||
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 _wrapped_view_func(request, *args, **kwargs):
|
||||
response = view_func(request, *args, **kwargs)
|
||||
add_never_cache_headers(response)
|
||||
return response
|
||||
return _wrapped_view_func
|
||||
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Decorators for views based on HTTP headers.
|
||||
"""
|
||||
|
||||
from calendar import timegm
|
||||
from functools import wraps
|
||||
|
||||
from django.http import HttpResponseNotAllowed
|
||||
from django.middleware.http import ConditionalGetMiddleware
|
||||
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:
|
||||
return timegm(dt.utctimetuple())
|
||||
|
||||
# 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['Last-Modified'] = http_date(res_last_modified)
|
||||
if res_etag:
|
||||
response.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,22 @@
|
||||
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,170 @@
|
||||
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,241 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
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.")
|
||||
|
||||
|
||||
class BaseDeleteView(DeletionMixin, BaseDetailView):
|
||||
"""
|
||||
Base view for deleting an object.
|
||||
|
||||
Using this base class requires subclassing to provide a response mixin.
|
||||
"""
|
||||
|
||||
|
||||
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,308 @@
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from urllib.parse import unquote
|
||||
|
||||
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 is_safe_url
|
||||
from django.utils.translation import (
|
||||
LANGUAGE_SESSION_KEY, 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 = request.POST.get('next', request.GET.get('next'))
|
||||
if ((next or not request.is_ajax()) and
|
||||
not is_safe_url(url=next, allowed_hosts={request.get_host()}, require_https=request.is_secure())):
|
||||
next = request.META.get('HTTP_REFERER')
|
||||
next = next and unquote(next) # HTTP_REFERER may be encoded.
|
||||
if not is_safe_url(url=next, allowed_hosts={request.get_host()}, require_https=request.is_secure()):
|
||||
next = '/'
|
||||
response = HttpResponseRedirect(next) if next 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:
|
||||
next_trans = translate_url(next, lang_code)
|
||||
if next_trans != next:
|
||||
response = HttpResponseRedirect(next_trans)
|
||||
if hasattr(request, 'session'):
|
||||
request.session[LANGUAGE_SESSION_KEY] = lang_code
|
||||
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,
|
||||
)
|
||||
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 %}
|
||||
(function(globals) {
|
||||
|
||||
var django = globals.django || (globals.django = {});
|
||||
|
||||
{% if plural %}
|
||||
django.pluralidx = function(n) {
|
||||
var 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 %}
|
||||
var newcatalog = {{ catalog_str }};
|
||||
for (var key in newcatalog) {
|
||||
django.catalog[key] = newcatalog[key];
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
if (!django.jsi18n_initialized) {
|
||||
django.gettext = function(msgid) {
|
||||
var value = django.catalog[msgid];
|
||||
if (typeof(value) == 'undefined') {
|
||||
return msgid;
|
||||
} else {
|
||||
return (typeof(value) == 'string') ? value : value[0];
|
||||
}
|
||||
};
|
||||
|
||||
django.ngettext = function(singular, plural, count) {
|
||||
var 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) {
|
||||
var value = django.gettext(context + '\x04' + msgid);
|
||||
if (value.indexOf('\x04') != -1) {
|
||||
value = msgid;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
django.npgettext = function(context, singular, plural, count) {
|
||||
var value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
|
||||
if (value.indexOf('\x04') != -1) {
|
||||
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) {
|
||||
var 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;
|
||||
}
|
||||
|
||||
}(this));
|
||||
{% endautoescape %}
|
||||
"""
|
||||
|
||||
|
||||
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 url() 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.groups()[0])
|
||||
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 = {}
|
||||
num_plurals = self._num_plurals
|
||||
catalog = {}
|
||||
trans_cat = self.translation._catalog
|
||||
trans_fallback_cat = self.translation._fallback._catalog if self.translation._fallback else {}
|
||||
seen_keys = set()
|
||||
for key, value in itertools.chain(trans_cat.items(), trans_fallback_cat.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)
|
||||
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)
|
||||
@@ -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.get_raw_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:
|
||||
{% 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 %}
|
||||
File "{{ frame.filename }}" in {{ frame.function }}
|
||||
{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %}
|
||||
{% endfor %}
|
||||
{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
|
||||
{% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% endif %}{% 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