| 1 | import copy
|
|---|
| 2 | from django import http
|
|---|
| 3 | from django.core.exceptions import ImproperlyConfigured
|
|---|
| 4 | from django.template import RequestContext, loader
|
|---|
| 5 | from django.utils.translation import ugettext_lazy as _
|
|---|
| 6 | from django.utils.functional import update_wrapper
|
|---|
| 7 | from django.utils.log import getLogger
|
|---|
| 8 |
|
|---|
| 9 | logger = getLogger('django.request')
|
|---|
| 10 |
|
|---|
| 11 | class 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 |
|
|---|
| 17 | class 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 |
|
|---|
| 89 | class 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 |
|
|---|
| 148 | class 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 |
|
|---|
| 162 | class 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()
|
|---|