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.

response.py 5.5KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from django.http import HttpResponse
  2. from .loader import get_template, select_template
  3. class ContentNotRenderedError(Exception):
  4. pass
  5. class SimpleTemplateResponse(HttpResponse):
  6. rendering_attrs = ["template_name", "context_data", "_post_render_callbacks"]
  7. def __init__(
  8. self,
  9. template,
  10. context=None,
  11. content_type=None,
  12. status=None,
  13. charset=None,
  14. using=None,
  15. headers=None,
  16. ):
  17. # It would seem obvious to call these next two members 'template' and
  18. # 'context', but those names are reserved as part of the test Client
  19. # API. To avoid the name collision, we use different names.
  20. self.template_name = template
  21. self.context_data = context
  22. self.using = using
  23. self._post_render_callbacks = []
  24. # _request stores the current request object in subclasses that know
  25. # about requests, like TemplateResponse. It's defined in the base class
  26. # to minimize code duplication.
  27. # It's called self._request because self.request gets overwritten by
  28. # django.test.client.Client. Unlike template_name and context_data,
  29. # _request should not be considered part of the public API.
  30. self._request = None
  31. # content argument doesn't make sense here because it will be replaced
  32. # with rendered template so we always pass empty string in order to
  33. # prevent errors and provide shorter signature.
  34. super().__init__("", content_type, status, charset=charset, headers=headers)
  35. # _is_rendered tracks whether the template and context has been baked
  36. # into a final response.
  37. # Super __init__ doesn't know any better than to set self.content to
  38. # the empty string we just gave it, which wrongly sets _is_rendered
  39. # True, so we initialize it to False after the call to super __init__.
  40. self._is_rendered = False
  41. def __getstate__(self):
  42. """
  43. Raise an exception if trying to pickle an unrendered response. Pickle
  44. only rendered data, not the data used to construct the response.
  45. """
  46. obj_dict = self.__dict__.copy()
  47. if not self._is_rendered:
  48. raise ContentNotRenderedError(
  49. "The response content must be rendered before it can be pickled."
  50. )
  51. for attr in self.rendering_attrs:
  52. if attr in obj_dict:
  53. del obj_dict[attr]
  54. return obj_dict
  55. def resolve_template(self, template):
  56. """Accept a template object, path-to-template, or list of paths."""
  57. if isinstance(template, (list, tuple)):
  58. return select_template(template, using=self.using)
  59. elif isinstance(template, str):
  60. return get_template(template, using=self.using)
  61. else:
  62. return template
  63. def resolve_context(self, context):
  64. return context
  65. @property
  66. def rendered_content(self):
  67. """Return the freshly rendered content for the template and context
  68. described by the TemplateResponse.
  69. This *does not* set the final content of the response. To set the
  70. response content, you must either call render(), or set the
  71. content explicitly using the value of this property.
  72. """
  73. template = self.resolve_template(self.template_name)
  74. context = self.resolve_context(self.context_data)
  75. return template.render(context, self._request)
  76. def add_post_render_callback(self, callback):
  77. """Add a new post-rendering callback.
  78. If the response has already been rendered,
  79. invoke the callback immediately.
  80. """
  81. if self._is_rendered:
  82. callback(self)
  83. else:
  84. self._post_render_callbacks.append(callback)
  85. def render(self):
  86. """Render (thereby finalizing) the content of the response.
  87. If the content has already been rendered, this is a no-op.
  88. Return the baked response instance.
  89. """
  90. retval = self
  91. if not self._is_rendered:
  92. self.content = self.rendered_content
  93. for post_callback in self._post_render_callbacks:
  94. newretval = post_callback(retval)
  95. if newretval is not None:
  96. retval = newretval
  97. return retval
  98. @property
  99. def is_rendered(self):
  100. return self._is_rendered
  101. def __iter__(self):
  102. if not self._is_rendered:
  103. raise ContentNotRenderedError(
  104. "The response content must be rendered before it can be iterated over."
  105. )
  106. return super().__iter__()
  107. @property
  108. def content(self):
  109. if not self._is_rendered:
  110. raise ContentNotRenderedError(
  111. "The response content must be rendered before it can be accessed."
  112. )
  113. return super().content
  114. @content.setter
  115. def content(self, value):
  116. """Set the content for the response."""
  117. HttpResponse.content.fset(self, value)
  118. self._is_rendered = True
  119. class TemplateResponse(SimpleTemplateResponse):
  120. rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"]
  121. def __init__(
  122. self,
  123. request,
  124. template,
  125. context=None,
  126. content_type=None,
  127. status=None,
  128. charset=None,
  129. using=None,
  130. headers=None,
  131. ):
  132. super().__init__(
  133. template, context, content_type, status, charset, using, headers=headers
  134. )
  135. self._request = request