Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/views/generic/base.py: 35%

133 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

1import logging 

2 

3from asgiref.sync import iscoroutinefunction, markcoroutinefunction 

4 

5from django.core.exceptions import ImproperlyConfigured 

6from django.http import ( 

7 HttpResponse, 

8 HttpResponseGone, 

9 HttpResponseNotAllowed, 

10 HttpResponsePermanentRedirect, 

11 HttpResponseRedirect, 

12) 

13from django.template.response import TemplateResponse 

14from django.urls import reverse 

15from django.utils.decorators import classonlymethod 

16from django.utils.functional import classproperty 

17 

18logger = logging.getLogger("django.request") 

19 

20 

21class ContextMixin: 

22 """ 

23 A default context mixin that passes the keyword arguments received by 

24 get_context_data() as the template context. 

25 """ 

26 

27 extra_context = None 

28 

29 def get_context_data(self, **kwargs): 

30 kwargs.setdefault("view", self) 

31 if self.extra_context is not None: 

32 kwargs.update(self.extra_context) 

33 return kwargs 

34 

35 

36class View: 

37 """ 

38 Intentionally simple parent class for all views. Only implements 

39 dispatch-by-method and simple sanity checking. 

40 """ 

41 

42 http_method_names = [ 

43 "get", 

44 "post", 

45 "put", 

46 "patch", 

47 "delete", 

48 "head", 

49 "options", 

50 "trace", 

51 ] 

52 

53 def __init__(self, **kwargs): 

54 """ 

55 Constructor. Called in the URLconf; can contain helpful extra 

56 keyword arguments, and other things. 

57 """ 

58 # Go through keyword arguments, and either save their values to our 

59 # instance, or raise an error. 

60 for key, value in kwargs.items(): 

61 setattr(self, key, value) 

62 

63 @classproperty 

64 def view_is_async(cls): 

65 handlers = [ 

66 getattr(cls, method) 

67 for method in cls.http_method_names 

68 if (method != "options" and hasattr(cls, method)) 

69 ] 

70 if not handlers: 

71 return False 

72 is_async = iscoroutinefunction(handlers[0]) 

73 if not all(iscoroutinefunction(h) == is_async for h in handlers[1:]): 

74 raise ImproperlyConfigured( 

75 f"{cls.__qualname__} HTTP handlers must either be all sync or all " 

76 "async." 

77 ) 

78 return is_async 

79 

80 @classonlymethod 

81 def as_view(cls, **initkwargs): 

82 """Main entry point for a request-response process.""" 

83 for key in initkwargs: 

84 if key in cls.http_method_names: 

85 raise TypeError( 

86 "The method name %s is not accepted as a keyword argument " 

87 "to %s()." % (key, cls.__name__) 

88 ) 

89 if not hasattr(cls, key): 

90 raise TypeError( 

91 "%s() received an invalid keyword %r. as_view " 

92 "only accepts arguments that are already " 

93 "attributes of the class." % (cls.__name__, key) 

94 ) 

95 

96 def view(request, *args, **kwargs): 

97 self = cls(**initkwargs) 

98 self.setup(request, *args, **kwargs) 

99 if not hasattr(self, "request"): 

100 raise AttributeError( 

101 "%s instance has no 'request' attribute. Did you override " 

102 "setup() and forget to call super()?" % cls.__name__ 

103 ) 

104 return self.dispatch(request, *args, **kwargs) 

105 

106 view.view_class = cls 

107 view.view_initkwargs = initkwargs 

108 

109 # __name__ and __qualname__ are intentionally left unchanged as 

110 # view_class should be used to robustly determine the name of the view 

111 # instead. 

112 view.__doc__ = cls.__doc__ 

113 view.__module__ = cls.__module__ 

114 view.__annotations__ = cls.dispatch.__annotations__ 

115 # Copy possible attributes set by decorators, e.g. @csrf_exempt, from 

116 # the dispatch method. 

117 view.__dict__.update(cls.dispatch.__dict__) 

118 

119 # Mark the callback if the view class is async. 

120 if cls.view_is_async: 

121 markcoroutinefunction(view) 

122 

123 return view 

124 

125 def setup(self, request, *args, **kwargs): 

126 """Initialize attributes shared by all view methods.""" 

127 if hasattr(self, "get") and not hasattr(self, "head"): 

128 self.head = self.get 

129 self.request = request 

130 self.args = args 

131 self.kwargs = kwargs 

132 

133 def dispatch(self, request, *args, **kwargs): 

134 # Try to dispatch to the right method; if a method doesn't exist, 

135 # defer to the error handler. Also defer to the error handler if the 

136 # request method isn't on the approved list. 

137 if request.method.lower() in self.http_method_names: 

138 handler = getattr( 

139 self, request.method.lower(), self.http_method_not_allowed 

140 ) 

141 else: 

142 handler = self.http_method_not_allowed 

143 return handler(request, *args, **kwargs) 

144 

145 def http_method_not_allowed(self, request, *args, **kwargs): 

146 logger.warning( 

147 "Method Not Allowed (%s): %s", 

148 request.method, 

149 request.path, 

150 extra={"status_code": 405, "request": request}, 

151 ) 

152 response = HttpResponseNotAllowed(self._allowed_methods()) 

153 

154 if self.view_is_async: 

155 

156 async def func(): 

157 return response 

158 

159 return func() 

160 else: 

161 return response 

162 

163 def options(self, request, *args, **kwargs): 

164 """Handle responding to requests for the OPTIONS HTTP verb.""" 

165 response = HttpResponse() 

166 response.headers["Allow"] = ", ".join(self._allowed_methods()) 

167 response.headers["Content-Length"] = "0" 

168 

169 if self.view_is_async: 

170 

171 async def func(): 

172 return response 

173 

174 return func() 

175 else: 

176 return response 

177 

178 def _allowed_methods(self): 

179 return [m.upper() for m in self.http_method_names if hasattr(self, m)] 

180 

181 

182class TemplateResponseMixin: 

183 """A mixin that can be used to render a template.""" 

184 

185 template_name = None 

186 template_engine = None 

187 response_class = TemplateResponse 

188 content_type = None 

189 

190 def render_to_response(self, context, **response_kwargs): 

191 """ 

192 Return a response, using the `response_class` for this view, with a 

193 template rendered with the given context. 

194 

195 Pass response_kwargs to the constructor of the response class. 

196 """ 

197 response_kwargs.setdefault("content_type", self.content_type) 

198 return self.response_class( 

199 request=self.request, 

200 template=self.get_template_names(), 

201 context=context, 

202 using=self.template_engine, 

203 **response_kwargs, 

204 ) 

205 

206 def get_template_names(self): 

207 """ 

208 Return a list of template names to be used for the request. Must return 

209 a list. May not be called if render_to_response() is overridden. 

210 """ 

211 if self.template_name is None: 

212 raise ImproperlyConfigured( 

213 "TemplateResponseMixin requires either a definition of " 

214 "'template_name' or an implementation of 'get_template_names()'" 

215 ) 

216 else: 

217 return [self.template_name] 

218 

219 

220class TemplateView(TemplateResponseMixin, ContextMixin, View): 

221 """ 

222 Render a template. Pass keyword arguments from the URLconf to the context. 

223 """ 

224 

225 def get(self, request, *args, **kwargs): 

226 context = self.get_context_data(**kwargs) 

227 return self.render_to_response(context) 

228 

229 

230class RedirectView(View): 

231 """Provide a redirect on any GET request.""" 

232 

233 permanent = False 

234 url = None 

235 pattern_name = None 

236 query_string = False 

237 

238 def get_redirect_url(self, *args, **kwargs): 

239 """ 

240 Return the URL redirect to. Keyword arguments from the URL pattern 

241 match generating the redirect request are provided as kwargs to this 

242 method. 

243 """ 

244 if self.url: 

245 url = self.url % kwargs 

246 elif self.pattern_name: 

247 url = reverse(self.pattern_name, args=args, kwargs=kwargs) 

248 else: 

249 return None 

250 

251 args = self.request.META.get("QUERY_STRING", "") 

252 if args and self.query_string: 

253 url = "%s?%s" % (url, args) 

254 return url 

255 

256 def get(self, request, *args, **kwargs): 

257 url = self.get_redirect_url(*args, **kwargs) 

258 if url: 

259 if self.permanent: 

260 return HttpResponsePermanentRedirect(url) 

261 else: 

262 return HttpResponseRedirect(url) 

263 else: 

264 logger.warning( 

265 "Gone: %s", request.path, extra={"status_code": 410, "request": request} 

266 ) 

267 return HttpResponseGone() 

268 

269 def head(self, request, *args, **kwargs): 

270 return self.get(request, *args, **kwargs) 

271 

272 def post(self, request, *args, **kwargs): 

273 return self.get(request, *args, **kwargs) 

274 

275 def options(self, request, *args, **kwargs): 

276 return self.get(request, *args, **kwargs) 

277 

278 def delete(self, request, *args, **kwargs): 

279 return self.get(request, *args, **kwargs) 

280 

281 def put(self, request, *args, **kwargs): 

282 return self.get(request, *args, **kwargs) 

283 

284 def patch(self, request, *args, **kwargs): 

285 return self.get(request, *args, **kwargs)