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.

utils.py 8.0KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import json
  2. import warnings
  3. from collections import UserList
  4. from django.conf import settings
  5. from django.core.exceptions import ValidationError
  6. from django.forms.renderers import get_default_renderer
  7. from django.utils import timezone
  8. from django.utils.deprecation import RemovedInDjango50Warning
  9. from django.utils.html import escape, format_html_join
  10. from django.utils.safestring import mark_safe
  11. from django.utils.translation import gettext_lazy as _
  12. from django.utils.version import get_docs_version
  13. def pretty_name(name):
  14. """Convert 'first_name' to 'First name'."""
  15. if not name:
  16. return ""
  17. return name.replace("_", " ").capitalize()
  18. def flatatt(attrs):
  19. """
  20. Convert a dictionary of attributes to a single string.
  21. The returned string will contain a leading space followed by key="value",
  22. XML-style pairs. In the case of a boolean value, the key will appear
  23. without a value. It is assumed that the keys do not need to be
  24. XML-escaped. If the passed dictionary is empty, then return an empty
  25. string.
  26. The result is passed through 'mark_safe' (by way of 'format_html_join').
  27. """
  28. key_value_attrs = []
  29. boolean_attrs = []
  30. for attr, value in attrs.items():
  31. if isinstance(value, bool):
  32. if value:
  33. boolean_attrs.append((attr,))
  34. elif value is not None:
  35. key_value_attrs.append((attr, value))
  36. return format_html_join("", ' {}="{}"', sorted(key_value_attrs)) + format_html_join(
  37. "", " {}", sorted(boolean_attrs)
  38. )
  39. DEFAULT_TEMPLATE_DEPRECATION_MSG = (
  40. 'The "default.html" templates for forms and formsets will be removed. These were '
  41. 'proxies to the equivalent "table.html" templates, but the new "div.html" '
  42. "templates will be the default from Django 5.0. Transitional renderers are "
  43. "provided to allow you to opt-in to the new output style now. See "
  44. "https://docs.djangoproject.com/en/%s/releases/4.1/ for more details"
  45. % get_docs_version()
  46. )
  47. class RenderableMixin:
  48. def get_context(self):
  49. raise NotImplementedError(
  50. "Subclasses of RenderableMixin must provide a get_context() method."
  51. )
  52. def render(self, template_name=None, context=None, renderer=None):
  53. renderer = renderer or self.renderer
  54. template = template_name or self.template_name
  55. context = context or self.get_context()
  56. if (
  57. template == "django/forms/default.html"
  58. or template == "django/forms/formsets/default.html"
  59. ):
  60. warnings.warn(
  61. DEFAULT_TEMPLATE_DEPRECATION_MSG, RemovedInDjango50Warning, stacklevel=2
  62. )
  63. return mark_safe(renderer.render(template, context))
  64. __str__ = render
  65. __html__ = render
  66. class RenderableFormMixin(RenderableMixin):
  67. def as_p(self):
  68. """Render as <p> elements."""
  69. return self.render(self.template_name_p)
  70. def as_table(self):
  71. """Render as <tr> elements excluding the surrounding <table> tag."""
  72. return self.render(self.template_name_table)
  73. def as_ul(self):
  74. """Render as <li> elements excluding the surrounding <ul> tag."""
  75. return self.render(self.template_name_ul)
  76. def as_div(self):
  77. """Render as <div> elements."""
  78. return self.render(self.template_name_div)
  79. class RenderableErrorMixin(RenderableMixin):
  80. def as_json(self, escape_html=False):
  81. return json.dumps(self.get_json_data(escape_html))
  82. def as_text(self):
  83. return self.render(self.template_name_text)
  84. def as_ul(self):
  85. return self.render(self.template_name_ul)
  86. class ErrorDict(dict, RenderableErrorMixin):
  87. """
  88. A collection of errors that knows how to display itself in various formats.
  89. The dictionary keys are the field names, and the values are the errors.
  90. """
  91. template_name = "django/forms/errors/dict/default.html"
  92. template_name_text = "django/forms/errors/dict/text.txt"
  93. template_name_ul = "django/forms/errors/dict/ul.html"
  94. def __init__(self, *args, renderer=None, **kwargs):
  95. super().__init__(*args, **kwargs)
  96. self.renderer = renderer or get_default_renderer()
  97. def as_data(self):
  98. return {f: e.as_data() for f, e in self.items()}
  99. def get_json_data(self, escape_html=False):
  100. return {f: e.get_json_data(escape_html) for f, e in self.items()}
  101. def get_context(self):
  102. return {
  103. "errors": self.items(),
  104. "error_class": "errorlist",
  105. }
  106. class ErrorList(UserList, list, RenderableErrorMixin):
  107. """
  108. A collection of errors that knows how to display itself in various formats.
  109. """
  110. template_name = "django/forms/errors/list/default.html"
  111. template_name_text = "django/forms/errors/list/text.txt"
  112. template_name_ul = "django/forms/errors/list/ul.html"
  113. def __init__(self, initlist=None, error_class=None, renderer=None):
  114. super().__init__(initlist)
  115. if error_class is None:
  116. self.error_class = "errorlist"
  117. else:
  118. self.error_class = "errorlist {}".format(error_class)
  119. self.renderer = renderer or get_default_renderer()
  120. def as_data(self):
  121. return ValidationError(self.data).error_list
  122. def copy(self):
  123. copy = super().copy()
  124. copy.error_class = self.error_class
  125. return copy
  126. def get_json_data(self, escape_html=False):
  127. errors = []
  128. for error in self.as_data():
  129. message = next(iter(error))
  130. errors.append(
  131. {
  132. "message": escape(message) if escape_html else message,
  133. "code": error.code or "",
  134. }
  135. )
  136. return errors
  137. def get_context(self):
  138. return {
  139. "errors": self,
  140. "error_class": self.error_class,
  141. }
  142. def __repr__(self):
  143. return repr(list(self))
  144. def __contains__(self, item):
  145. return item in list(self)
  146. def __eq__(self, other):
  147. return list(self) == other
  148. def __getitem__(self, i):
  149. error = self.data[i]
  150. if isinstance(error, ValidationError):
  151. return next(iter(error))
  152. return error
  153. def __reduce_ex__(self, *args, **kwargs):
  154. # The `list` reduce function returns an iterator as the fourth element
  155. # that is normally used for repopulating. Since we only inherit from
  156. # `list` for `isinstance` backward compatibility (Refs #17413) we
  157. # nullify this iterator as it would otherwise result in duplicate
  158. # entries. (Refs #23594)
  159. info = super(UserList, self).__reduce_ex__(*args, **kwargs)
  160. return info[:3] + (None, None)
  161. # Utilities for time zone support in DateTimeField et al.
  162. def from_current_timezone(value):
  163. """
  164. When time zone support is enabled, convert naive datetimes
  165. entered in the current time zone to aware datetimes.
  166. """
  167. if settings.USE_TZ and value is not None and timezone.is_naive(value):
  168. current_timezone = timezone.get_current_timezone()
  169. try:
  170. if not timezone._is_pytz_zone(
  171. current_timezone
  172. ) and timezone._datetime_ambiguous_or_imaginary(value, current_timezone):
  173. raise ValueError("Ambiguous or non-existent time.")
  174. return timezone.make_aware(value, current_timezone)
  175. except Exception as exc:
  176. raise ValidationError(
  177. _(
  178. "%(datetime)s couldn’t be interpreted "
  179. "in time zone %(current_timezone)s; it "
  180. "may be ambiguous or it may not exist."
  181. ),
  182. code="ambiguous_timezone",
  183. params={"datetime": value, "current_timezone": current_timezone},
  184. ) from exc
  185. return value
  186. def to_current_timezone(value):
  187. """
  188. When time zone support is enabled, convert aware datetimes
  189. to naive datetimes in the current time zone for display.
  190. """
  191. if settings.USE_TZ and value is not None and timezone.is_aware(value):
  192. return timezone.make_naive(value)
  193. return value