Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base.py 9.0KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import asyncio
  2. import logging
  3. from django.core.exceptions import ImproperlyConfigured
  4. from django.http import (
  5. HttpResponse,
  6. HttpResponseGone,
  7. HttpResponseNotAllowed,
  8. HttpResponsePermanentRedirect,
  9. HttpResponseRedirect,
  10. )
  11. from django.template.response import TemplateResponse
  12. from django.urls import reverse
  13. from django.utils.decorators import classonlymethod
  14. from django.utils.functional import classproperty
  15. logger = logging.getLogger("django.request")
  16. class ContextMixin:
  17. """
  18. A default context mixin that passes the keyword arguments received by
  19. get_context_data() as the template context.
  20. """
  21. extra_context = None
  22. def get_context_data(self, **kwargs):
  23. kwargs.setdefault("view", self)
  24. if self.extra_context is not None:
  25. kwargs.update(self.extra_context)
  26. return kwargs
  27. class View:
  28. """
  29. Intentionally simple parent class for all views. Only implements
  30. dispatch-by-method and simple sanity checking.
  31. """
  32. http_method_names = [
  33. "get",
  34. "post",
  35. "put",
  36. "patch",
  37. "delete",
  38. "head",
  39. "options",
  40. "trace",
  41. ]
  42. def __init__(self, **kwargs):
  43. """
  44. Constructor. Called in the URLconf; can contain helpful extra
  45. keyword arguments, and other things.
  46. """
  47. # Go through keyword arguments, and either save their values to our
  48. # instance, or raise an error.
  49. for key, value in kwargs.items():
  50. setattr(self, key, value)
  51. @classproperty
  52. def view_is_async(cls):
  53. handlers = [
  54. getattr(cls, method)
  55. for method in cls.http_method_names
  56. if (method != "options" and hasattr(cls, method))
  57. ]
  58. if not handlers:
  59. return False
  60. is_async = asyncio.iscoroutinefunction(handlers[0])
  61. if not all(asyncio.iscoroutinefunction(h) == is_async for h in handlers[1:]):
  62. raise ImproperlyConfigured(
  63. f"{cls.__qualname__} HTTP handlers must either be all sync or all "
  64. "async."
  65. )
  66. return is_async
  67. @classonlymethod
  68. def as_view(cls, **initkwargs):
  69. """Main entry point for a request-response process."""
  70. for key in initkwargs:
  71. if key in cls.http_method_names:
  72. raise TypeError(
  73. "The method name %s is not accepted as a keyword argument "
  74. "to %s()." % (key, cls.__name__)
  75. )
  76. if not hasattr(cls, key):
  77. raise TypeError(
  78. "%s() received an invalid keyword %r. as_view "
  79. "only accepts arguments that are already "
  80. "attributes of the class." % (cls.__name__, key)
  81. )
  82. def view(request, *args, **kwargs):
  83. self = cls(**initkwargs)
  84. self.setup(request, *args, **kwargs)
  85. if not hasattr(self, "request"):
  86. raise AttributeError(
  87. "%s instance has no 'request' attribute. Did you override "
  88. "setup() and forget to call super()?" % cls.__name__
  89. )
  90. return self.dispatch(request, *args, **kwargs)
  91. view.view_class = cls
  92. view.view_initkwargs = initkwargs
  93. # __name__ and __qualname__ are intentionally left unchanged as
  94. # view_class should be used to robustly determine the name of the view
  95. # instead.
  96. view.__doc__ = cls.__doc__
  97. view.__module__ = cls.__module__
  98. view.__annotations__ = cls.dispatch.__annotations__
  99. # Copy possible attributes set by decorators, e.g. @csrf_exempt, from
  100. # the dispatch method.
  101. view.__dict__.update(cls.dispatch.__dict__)
  102. # Mark the callback if the view class is async.
  103. if cls.view_is_async:
  104. view._is_coroutine = asyncio.coroutines._is_coroutine
  105. return view
  106. def setup(self, request, *args, **kwargs):
  107. """Initialize attributes shared by all view methods."""
  108. if hasattr(self, "get") and not hasattr(self, "head"):
  109. self.head = self.get
  110. self.request = request
  111. self.args = args
  112. self.kwargs = kwargs
  113. def dispatch(self, request, *args, **kwargs):
  114. # Try to dispatch to the right method; if a method doesn't exist,
  115. # defer to the error handler. Also defer to the error handler if the
  116. # request method isn't on the approved list.
  117. if request.method.lower() in self.http_method_names:
  118. handler = getattr(
  119. self, request.method.lower(), self.http_method_not_allowed
  120. )
  121. else:
  122. handler = self.http_method_not_allowed
  123. return handler(request, *args, **kwargs)
  124. def http_method_not_allowed(self, request, *args, **kwargs):
  125. logger.warning(
  126. "Method Not Allowed (%s): %s",
  127. request.method,
  128. request.path,
  129. extra={"status_code": 405, "request": request},
  130. )
  131. response = HttpResponseNotAllowed(self._allowed_methods())
  132. if self.view_is_async:
  133. async def func():
  134. return response
  135. return func()
  136. else:
  137. return response
  138. def options(self, request, *args, **kwargs):
  139. """Handle responding to requests for the OPTIONS HTTP verb."""
  140. response = HttpResponse()
  141. response.headers["Allow"] = ", ".join(self._allowed_methods())
  142. response.headers["Content-Length"] = "0"
  143. if self.view_is_async:
  144. async def func():
  145. return response
  146. return func()
  147. else:
  148. return response
  149. def _allowed_methods(self):
  150. return [m.upper() for m in self.http_method_names if hasattr(self, m)]
  151. class TemplateResponseMixin:
  152. """A mixin that can be used to render a template."""
  153. template_name = None
  154. template_engine = None
  155. response_class = TemplateResponse
  156. content_type = None
  157. def render_to_response(self, context, **response_kwargs):
  158. """
  159. Return a response, using the `response_class` for this view, with a
  160. template rendered with the given context.
  161. Pass response_kwargs to the constructor of the response class.
  162. """
  163. response_kwargs.setdefault("content_type", self.content_type)
  164. return self.response_class(
  165. request=self.request,
  166. template=self.get_template_names(),
  167. context=context,
  168. using=self.template_engine,
  169. **response_kwargs,
  170. )
  171. def get_template_names(self):
  172. """
  173. Return a list of template names to be used for the request. Must return
  174. a list. May not be called if render_to_response() is overridden.
  175. """
  176. if self.template_name is None:
  177. raise ImproperlyConfigured(
  178. "TemplateResponseMixin requires either a definition of "
  179. "'template_name' or an implementation of 'get_template_names()'"
  180. )
  181. else:
  182. return [self.template_name]
  183. class TemplateView(TemplateResponseMixin, ContextMixin, View):
  184. """
  185. Render a template. Pass keyword arguments from the URLconf to the context.
  186. """
  187. def get(self, request, *args, **kwargs):
  188. context = self.get_context_data(**kwargs)
  189. return self.render_to_response(context)
  190. class RedirectView(View):
  191. """Provide a redirect on any GET request."""
  192. permanent = False
  193. url = None
  194. pattern_name = None
  195. query_string = False
  196. def get_redirect_url(self, *args, **kwargs):
  197. """
  198. Return the URL redirect to. Keyword arguments from the URL pattern
  199. match generating the redirect request are provided as kwargs to this
  200. method.
  201. """
  202. if self.url:
  203. url = self.url % kwargs
  204. elif self.pattern_name:
  205. url = reverse(self.pattern_name, args=args, kwargs=kwargs)
  206. else:
  207. return None
  208. args = self.request.META.get("QUERY_STRING", "")
  209. if args and self.query_string:
  210. url = "%s?%s" % (url, args)
  211. return url
  212. def get(self, request, *args, **kwargs):
  213. url = self.get_redirect_url(*args, **kwargs)
  214. if url:
  215. if self.permanent:
  216. return HttpResponsePermanentRedirect(url)
  217. else:
  218. return HttpResponseRedirect(url)
  219. else:
  220. logger.warning(
  221. "Gone: %s", request.path, extra={"status_code": 410, "request": request}
  222. )
  223. return HttpResponseGone()
  224. def head(self, request, *args, **kwargs):
  225. return self.get(request, *args, **kwargs)
  226. def post(self, request, *args, **kwargs):
  227. return self.get(request, *args, **kwargs)
  228. def options(self, request, *args, **kwargs):
  229. return self.get(request, *args, **kwargs)
  230. def delete(self, request, *args, **kwargs):
  231. return self.get(request, *args, **kwargs)
  232. def put(self, request, *args, **kwargs):
  233. return self.get(request, *args, **kwargs)
  234. def patch(self, request, *args, **kwargs):
  235. return self.get(request, *args, **kwargs)