Code

Ticket #14557: base.py

File base.py, 6.6 KB (added by pyrou, 4 years ago)
Line 
1import copy
2from django import http
3from django.core.exceptions import ImproperlyConfigured
4from django.template import RequestContext, loader
5from django.utils.translation import ugettext_lazy as _
6from django.utils.functional import update_wrapper
7from django.utils.log import getLogger
8
9logger = getLogger('django.request')
10
11class classonlymethod(classmethod):
12    def __get__(self, instance, owner):
13        if instance is not None:
14            raise AttributeError("This method is available only on the view class.")
15        return super(classonlymethod, self).__get__(instance, owner)
16
17class View(object):
18    """
19    Intentionally simple parent class for all views. Only implements
20    dispatch-by-method and simple sanity checking.
21    """
22
23    http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
24
25    def __init__(self, **kwargs):
26        """
27        Constructor. Called in the URLconf; can contain helpful extra
28        keyword arguments, and other things.
29        """
30        # Go through keyword arguments, and either save their values to our
31        # instance, or raise an error.
32        for key, value in kwargs.iteritems():
33            # sanitize keyword arguments
34            if key in self.http_method_names:
35                raise TypeError(u"You tried to pass in the %s method name as a "
36                                u"keyword argument to %s(). Don't do that."
37                                % (key, self.__class__.__name__))
38            if not hasattr(self, key):
39                raise TypeError(u"%s() received an invalid keyword %r" % (
40                    self.__class__.__name__, key))
41           
42            setattr(self, key, value)
43
44           
45    def __call__(self, request, *args, **kwargs):
46        return self.dispatch(request, *args, **kwargs)
47   
48    @classonlymethod
49    def as_view(cls, **initkwargs):
50        """
51        Main entry point for a request-response process.
52        """
53        def view(request, *args, **kwargs):
54            self = cls(**initkwargs)
55            return self.dispatch(request, *args, **kwargs)
56
57        # take name and docstring from class
58        update_wrapper(view, cls, updated=())
59
60        # and possible attributes set by decorators
61        # like csrf_exempt from dispatch
62        update_wrapper(view, cls.dispatch, assigned=())
63        return view
64
65    def dispatch(self, request, *args, **kwargs):
66        # Try to dispatch to the right method; if a method doesn't exist,
67        # defer to the error handler. Also defer to the error handler if the
68        # request method isn't on the approved list.
69        if request.method.lower() in self.http_method_names:
70            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
71        else:
72            handler = self.http_method_not_allowed
73        self.request = request
74        self.args = args
75        self.kwargs = kwargs
76        return handler(request, *args, **kwargs)
77
78    def http_method_not_allowed(self, request, *args, **kwargs):
79        allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
80        logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
81            extra={
82                'status_code': 405,
83                'request': self.request
84            }
85        )
86        return http.HttpResponseNotAllowed(allowed_methods)
87
88
89class TemplateResponseMixin(object):
90    """
91    A mixin that can be used to render a template.
92    """
93    template_name = None
94
95    def render_to_response(self, context):
96        """
97        Returns a response with a template rendered with the given context.
98        """
99        return self.get_response(self.render_template(context))
100
101    def get_response(self, content, **httpresponse_kwargs):
102        """
103        Construct an `HttpResponse` object.
104        """
105        return http.HttpResponse(content, **httpresponse_kwargs)
106
107    def render_template(self, context):
108        """
109        Render the template with a given context.
110        """
111        context_instance = self.get_context_instance(context)
112        return self.get_template().render(context_instance)
113
114    def get_context_instance(self, context):
115        """
116        Get the template context instance. Must return a Context (or subclass)
117        instance.
118        """
119        return RequestContext(self.request, context)
120
121    def get_template(self):
122        """
123        Get a ``Template`` object for the given request.
124        """
125        names = self.get_template_names()
126        if not names:
127            raise ImproperlyConfigured(u"'%s' must provide template_name."
128                                       % self.__class__.__name__)
129        return self.load_template(names)
130
131    def get_template_names(self):
132        """
133        Return a list of template names to be used for the request. Must return
134        a list. May not be called if get_template is overridden.
135        """
136        if self.template_name is None:
137            return []
138        else:
139            return [self.template_name]
140
141    def load_template(self, names):
142        """
143        Load a list of templates using the default template loader.
144        """
145        return loader.select_template(names)
146
147
148class TemplateView(TemplateResponseMixin, View):
149    """
150    A view that renders a template.
151    """
152    def get_context_data(self, **kwargs):
153        return {
154            'params': kwargs
155        }
156
157    def get(self, request, *args, **kwargs):
158        context = self.get_context_data(**kwargs)
159        return self.render_to_response(context)
160
161
162class RedirectView(View):
163    """
164    A view that provides a redirect on any GET request.
165    """
166    permanent = True
167    url = None
168    query_string = False
169
170    def get_redirect_url(self, **kwargs):
171        """
172        Return the URL redirect to. Keyword arguments from the
173        URL pattern match generating the redirect request
174        are provided as kwargs to this method.
175        """
176        if self.url:
177            args = self.request.META["QUERY_STRING"]
178            if args and self.query_string:
179                url = "%s?%s" % (self.url, args)
180            else:
181                url = self.url
182            return url % kwargs
183        else:
184            return None
185
186    def get(self, request, *args, **kwargs):
187        url = self.get_redirect_url(**kwargs)
188        if url:
189            if self.permanent:
190                return http.HttpResponsePermanentRedirect(url)
191            else:
192                return http.HttpResponseRedirect(url)
193        else:
194            logger.warning('Gone: %s' % self.request.path,
195                        extra={
196                            'status_code': 410,
197                            'request': self.request
198                        })
199            return http.HttpResponseGone()