base.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. from django.conf import settings
  2. from django.contrib.messages import constants, utils
  3. from django.utils.functional import SimpleLazyObject
  4. LEVEL_TAGS = SimpleLazyObject(utils.get_level_tags)
  5. class Message:
  6. """
  7. Represent an actual message that can be stored in any of the supported
  8. storage classes (typically session- or cookie-based) and rendered in a view
  9. or template.
  10. """
  11. def __init__(self, level, message, extra_tags=None):
  12. self.level = int(level)
  13. self.message = message
  14. self.extra_tags = extra_tags
  15. def _prepare(self):
  16. """
  17. Prepare the message for serialization by forcing the ``message``
  18. and ``extra_tags`` to str in case they are lazy translations.
  19. """
  20. self.message = str(self.message)
  21. self.extra_tags = str(self.extra_tags) if self.extra_tags is not None else None
  22. def __eq__(self, other):
  23. if not isinstance(other, Message):
  24. return NotImplemented
  25. return self.level == other.level and self.message == other.message
  26. def __str__(self):
  27. return str(self.message)
  28. def __repr__(self):
  29. extra_tags = f", extra_tags={self.extra_tags!r}" if self.extra_tags else ""
  30. return f"Message(level={self.level}, message={self.message!r}{extra_tags})"
  31. @property
  32. def tags(self):
  33. return " ".join(tag for tag in [self.extra_tags, self.level_tag] if tag)
  34. @property
  35. def level_tag(self):
  36. return LEVEL_TAGS.get(self.level, "")
  37. class BaseStorage:
  38. """
  39. This is the base backend for temporary message storage.
  40. This is not a complete class; to be a usable storage backend, it must be
  41. subclassed and the two methods ``_get`` and ``_store`` overridden.
  42. """
  43. def __init__(self, request, *args, **kwargs):
  44. self.request = request
  45. self._queued_messages = []
  46. self.used = False
  47. self.added_new = False
  48. super().__init__(*args, **kwargs)
  49. def __len__(self):
  50. return len(self._loaded_messages) + len(self._queued_messages)
  51. def __iter__(self):
  52. self.used = True
  53. if self._queued_messages:
  54. self._loaded_messages.extend(self._queued_messages)
  55. self._queued_messages = []
  56. return iter(self._loaded_messages)
  57. def __contains__(self, item):
  58. return item in self._loaded_messages or item in self._queued_messages
  59. def __repr__(self):
  60. return f"<{self.__class__.__qualname__}: request={self.request!r}>"
  61. @property
  62. def _loaded_messages(self):
  63. """
  64. Return a list of loaded messages, retrieving them first if they have
  65. not been loaded yet.
  66. """
  67. if not hasattr(self, "_loaded_data"):
  68. messages, all_retrieved = self._get()
  69. self._loaded_data = messages or []
  70. return self._loaded_data
  71. def _get(self, *args, **kwargs):
  72. """
  73. Retrieve a list of stored messages. Return a tuple of the messages
  74. and a flag indicating whether or not all the messages originally
  75. intended to be stored in this storage were, in fact, stored and
  76. retrieved; e.g., ``(messages, all_retrieved)``.
  77. **This method must be implemented by a subclass.**
  78. If it is possible to tell if the backend was not used (as opposed to
  79. just containing no messages) then ``None`` should be returned in
  80. place of ``messages``.
  81. """
  82. raise NotImplementedError(
  83. "subclasses of BaseStorage must provide a _get() method"
  84. )
  85. def _store(self, messages, response, *args, **kwargs):
  86. """
  87. Store a list of messages and return a list of any messages which could
  88. not be stored.
  89. One type of object must be able to be stored, ``Message``.
  90. **This method must be implemented by a subclass.**
  91. """
  92. raise NotImplementedError(
  93. "subclasses of BaseStorage must provide a _store() method"
  94. )
  95. def _prepare_messages(self, messages):
  96. """
  97. Prepare a list of messages for storage.
  98. """
  99. for message in messages:
  100. message._prepare()
  101. def update(self, response):
  102. """
  103. Store all unread messages.
  104. If the backend has yet to be iterated, store previously stored messages
  105. again. Otherwise, only store messages added after the last iteration.
  106. """
  107. self._prepare_messages(self._queued_messages)
  108. if self.used:
  109. return self._store(self._queued_messages, response)
  110. elif self.added_new:
  111. messages = self._loaded_messages + self._queued_messages
  112. return self._store(messages, response)
  113. def add(self, level, message, extra_tags=""):
  114. """
  115. Queue a message to be stored.
  116. The message is only queued if it contained something and its level is
  117. not less than the recording level (``self.level``).
  118. """
  119. if not message:
  120. return
  121. # Check that the message level is not less than the recording level.
  122. level = int(level)
  123. if level < self.level:
  124. return
  125. # Add the message.
  126. self.added_new = True
  127. message = Message(level, message, extra_tags=extra_tags)
  128. self._queued_messages.append(message)
  129. def _get_level(self):
  130. """
  131. Return the minimum recorded level.
  132. The default level is the ``MESSAGE_LEVEL`` setting. If this is
  133. not found, the ``INFO`` level is used.
  134. """
  135. if not hasattr(self, "_level"):
  136. self._level = getattr(settings, "MESSAGE_LEVEL", constants.INFO)
  137. return self._level
  138. def _set_level(self, value=None):
  139. """
  140. Set a custom minimum recorded level.
  141. If set to ``None``, the default level will be used (see the
  142. ``_get_level`` method).
  143. """
  144. if value is None and hasattr(self, "_level"):
  145. del self._level
  146. else:
  147. self._level = int(value)
  148. level = property(_get_level, _set_level, _set_level)