first comit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from django.conf import settings
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
|
||||
def default_storage(request):
|
||||
"""
|
||||
Callable with the same interface as the storage classes.
|
||||
|
||||
This isn't just default_storage = import_string(settings.MESSAGE_STORAGE)
|
||||
to avoid accessing the settings at the module level.
|
||||
"""
|
||||
return import_string(settings.MESSAGE_STORAGE)(request)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
183
venv/lib/python3.10/site-packages/django/contrib/messages/storage/base.py
Executable file
183
venv/lib/python3.10/site-packages/django/contrib/messages/storage/base.py
Executable file
@@ -0,0 +1,183 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.messages import constants, utils
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
|
||||
LEVEL_TAGS = SimpleLazyObject(utils.get_level_tags)
|
||||
|
||||
|
||||
class Message:
|
||||
"""
|
||||
Represent an actual message that can be stored in any of the supported
|
||||
storage classes (typically session- or cookie-based) and rendered in a view
|
||||
or template.
|
||||
"""
|
||||
|
||||
def __init__(self, level, message, extra_tags=None):
|
||||
self.level = int(level)
|
||||
self.message = message
|
||||
self.extra_tags = extra_tags
|
||||
|
||||
def _prepare(self):
|
||||
"""
|
||||
Prepare the message for serialization by forcing the ``message``
|
||||
and ``extra_tags`` to str in case they are lazy translations.
|
||||
"""
|
||||
self.message = str(self.message)
|
||||
self.extra_tags = str(self.extra_tags) if self.extra_tags is not None else None
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Message):
|
||||
return NotImplemented
|
||||
return self.level == other.level and self.message == other.message
|
||||
|
||||
def __str__(self):
|
||||
return str(self.message)
|
||||
|
||||
def __repr__(self):
|
||||
extra_tags = f", extra_tags={self.extra_tags!r}" if self.extra_tags else ""
|
||||
return f"Message(level={self.level}, message={self.message!r}{extra_tags})"
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return " ".join(tag for tag in [self.extra_tags, self.level_tag] if tag)
|
||||
|
||||
@property
|
||||
def level_tag(self):
|
||||
return LEVEL_TAGS.get(self.level, "")
|
||||
|
||||
|
||||
class BaseStorage:
|
||||
"""
|
||||
This is the base backend for temporary message storage.
|
||||
|
||||
This is not a complete class; to be a usable storage backend, it must be
|
||||
subclassed and the two methods ``_get`` and ``_store`` overridden.
|
||||
"""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
self._queued_messages = []
|
||||
self.used = False
|
||||
self.added_new = False
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._loaded_messages) + len(self._queued_messages)
|
||||
|
||||
def __iter__(self):
|
||||
self.used = True
|
||||
if self._queued_messages:
|
||||
self._loaded_messages.extend(self._queued_messages)
|
||||
self._queued_messages = []
|
||||
return iter(self._loaded_messages)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._loaded_messages or item in self._queued_messages
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}: request={self.request!r}>"
|
||||
|
||||
@property
|
||||
def _loaded_messages(self):
|
||||
"""
|
||||
Return a list of loaded messages, retrieving them first if they have
|
||||
not been loaded yet.
|
||||
"""
|
||||
if not hasattr(self, "_loaded_data"):
|
||||
messages, all_retrieved = self._get()
|
||||
self._loaded_data = messages or []
|
||||
return self._loaded_data
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
"""
|
||||
Retrieve a list of stored messages. Return a tuple of the messages
|
||||
and a flag indicating whether or not all the messages originally
|
||||
intended to be stored in this storage were, in fact, stored and
|
||||
retrieved; e.g., ``(messages, all_retrieved)``.
|
||||
|
||||
**This method must be implemented by a subclass.**
|
||||
|
||||
If it is possible to tell if the backend was not used (as opposed to
|
||||
just containing no messages) then ``None`` should be returned in
|
||||
place of ``messages``.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseStorage must provide a _get() method"
|
||||
)
|
||||
|
||||
def _store(self, messages, response, *args, **kwargs):
|
||||
"""
|
||||
Store a list of messages and return a list of any messages which could
|
||||
not be stored.
|
||||
|
||||
One type of object must be able to be stored, ``Message``.
|
||||
|
||||
**This method must be implemented by a subclass.**
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseStorage must provide a _store() method"
|
||||
)
|
||||
|
||||
def _prepare_messages(self, messages):
|
||||
"""
|
||||
Prepare a list of messages for storage.
|
||||
"""
|
||||
for message in messages:
|
||||
message._prepare()
|
||||
|
||||
def update(self, response):
|
||||
"""
|
||||
Store all unread messages.
|
||||
|
||||
If the backend has yet to be iterated, store previously stored messages
|
||||
again. Otherwise, only store messages added after the last iteration.
|
||||
"""
|
||||
self._prepare_messages(self._queued_messages)
|
||||
if self.used:
|
||||
return self._store(self._queued_messages, response)
|
||||
elif self.added_new:
|
||||
messages = self._loaded_messages + self._queued_messages
|
||||
return self._store(messages, response)
|
||||
|
||||
def add(self, level, message, extra_tags=""):
|
||||
"""
|
||||
Queue a message to be stored.
|
||||
|
||||
The message is only queued if it contained something and its level is
|
||||
not less than the recording level (``self.level``).
|
||||
"""
|
||||
if not message:
|
||||
return
|
||||
# Check that the message level is not less than the recording level.
|
||||
level = int(level)
|
||||
if level < self.level:
|
||||
return
|
||||
# Add the message.
|
||||
self.added_new = True
|
||||
message = Message(level, message, extra_tags=extra_tags)
|
||||
self._queued_messages.append(message)
|
||||
|
||||
def _get_level(self):
|
||||
"""
|
||||
Return the minimum recorded level.
|
||||
|
||||
The default level is the ``MESSAGE_LEVEL`` setting. If this is
|
||||
not found, the ``INFO`` level is used.
|
||||
"""
|
||||
if not hasattr(self, "_level"):
|
||||
self._level = getattr(settings, "MESSAGE_LEVEL", constants.INFO)
|
||||
return self._level
|
||||
|
||||
def _set_level(self, value=None):
|
||||
"""
|
||||
Set a custom minimum recorded level.
|
||||
|
||||
If set to ``None``, the default level will be used (see the
|
||||
``_get_level`` method).
|
||||
"""
|
||||
if value is None and hasattr(self, "_level"):
|
||||
del self._level
|
||||
else:
|
||||
self._level = int(value)
|
||||
|
||||
level = property(_get_level, _set_level, _set_level)
|
||||
248
venv/lib/python3.10/site-packages/django/contrib/messages/storage/cookie.py
Executable file
248
venv/lib/python3.10/site-packages/django/contrib/messages/storage/cookie.py
Executable file
@@ -0,0 +1,248 @@
|
||||
import binascii
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.messages.storage.base import BaseStorage, Message
|
||||
from django.core import signing
|
||||
from django.http import SimpleCookie
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
|
||||
|
||||
class MessageEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Compactly serialize instances of the ``Message`` class as JSON.
|
||||
"""
|
||||
|
||||
message_key = "__json_message"
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Message):
|
||||
# Using 0/1 here instead of False/True to produce more compact json
|
||||
is_safedata = 1 if isinstance(obj.message, SafeData) else 0
|
||||
message = [self.message_key, is_safedata, obj.level, obj.message]
|
||||
if obj.extra_tags is not None:
|
||||
message.append(obj.extra_tags)
|
||||
return message
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
class MessageDecoder(json.JSONDecoder):
|
||||
"""
|
||||
Decode JSON that includes serialized ``Message`` instances.
|
||||
"""
|
||||
|
||||
def process_messages(self, obj):
|
||||
if isinstance(obj, list) and obj:
|
||||
if obj[0] == MessageEncoder.message_key:
|
||||
if obj[1]:
|
||||
obj[3] = mark_safe(obj[3])
|
||||
return Message(*obj[2:])
|
||||
return [self.process_messages(item) for item in obj]
|
||||
if isinstance(obj, dict):
|
||||
return {key: self.process_messages(value) for key, value in obj.items()}
|
||||
return obj
|
||||
|
||||
def decode(self, s, **kwargs):
|
||||
decoded = super().decode(s, **kwargs)
|
||||
return self.process_messages(decoded)
|
||||
|
||||
|
||||
class MessagePartSerializer:
|
||||
def dumps(self, obj):
|
||||
return [
|
||||
json.dumps(
|
||||
o,
|
||||
separators=(",", ":"),
|
||||
cls=MessageEncoder,
|
||||
)
|
||||
for o in obj
|
||||
]
|
||||
|
||||
|
||||
class MessagePartGatherSerializer:
|
||||
def dumps(self, obj):
|
||||
"""
|
||||
The parameter is an already serialized list of Message objects. No need
|
||||
to serialize it again, only join the list together and encode it.
|
||||
"""
|
||||
return ("[" + ",".join(obj) + "]").encode("latin-1")
|
||||
|
||||
|
||||
class MessageSerializer:
|
||||
def loads(self, data):
|
||||
return json.loads(data.decode("latin-1"), cls=MessageDecoder)
|
||||
|
||||
|
||||
class CookieStorage(BaseStorage):
|
||||
"""
|
||||
Store messages in a cookie.
|
||||
"""
|
||||
|
||||
cookie_name = "messages"
|
||||
# uwsgi's default configuration enforces a maximum size of 4kb for all the
|
||||
# HTTP headers. In order to leave some room for other cookies and headers,
|
||||
# restrict the session cookie to 1/2 of 4kb. See #18781.
|
||||
max_cookie_size = 2048
|
||||
not_finished = "__messagesnotfinished__"
|
||||
not_finished_json = json.dumps("__messagesnotfinished__")
|
||||
key_salt = "django.contrib.messages"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.signer = signing.get_cookie_signer(salt=self.key_salt)
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
"""
|
||||
Retrieve a list of messages from the messages cookie. If the
|
||||
not_finished sentinel value is found at the end of the message list,
|
||||
remove it and return a result indicating that not all messages were
|
||||
retrieved by this storage.
|
||||
"""
|
||||
data = self.request.COOKIES.get(self.cookie_name)
|
||||
messages = self._decode(data)
|
||||
all_retrieved = not (messages and messages[-1] == self.not_finished)
|
||||
if messages and not all_retrieved:
|
||||
# remove the sentinel value
|
||||
messages.pop()
|
||||
return messages, all_retrieved
|
||||
|
||||
def _update_cookie(self, encoded_data, response):
|
||||
"""
|
||||
Either set the cookie with the encoded data if there is any data to
|
||||
store, or delete the cookie.
|
||||
"""
|
||||
if encoded_data:
|
||||
response.set_cookie(
|
||||
self.cookie_name,
|
||||
encoded_data,
|
||||
domain=settings.SESSION_COOKIE_DOMAIN,
|
||||
secure=settings.SESSION_COOKIE_SECURE or None,
|
||||
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
|
||||
samesite=settings.SESSION_COOKIE_SAMESITE,
|
||||
)
|
||||
else:
|
||||
response.delete_cookie(
|
||||
self.cookie_name,
|
||||
domain=settings.SESSION_COOKIE_DOMAIN,
|
||||
samesite=settings.SESSION_COOKIE_SAMESITE,
|
||||
)
|
||||
|
||||
def _store(self, messages, response, remove_oldest=True, *args, **kwargs):
|
||||
"""
|
||||
Store the messages to a cookie and return a list of any messages which
|
||||
could not be stored.
|
||||
|
||||
If the encoded data is larger than ``max_cookie_size``, remove
|
||||
messages until the data fits (these are the messages which are
|
||||
returned), and add the not_finished sentinel value to indicate as much.
|
||||
"""
|
||||
unstored_messages = []
|
||||
serialized_messages = MessagePartSerializer().dumps(messages)
|
||||
encoded_data = self._encode_parts(serialized_messages)
|
||||
if self.max_cookie_size:
|
||||
# data is going to be stored eventually by SimpleCookie, which
|
||||
# adds its own overhead, which we must account for.
|
||||
cookie = SimpleCookie() # create outside the loop
|
||||
|
||||
def is_too_large_for_cookie(data):
|
||||
return data and len(cookie.value_encode(data)[1]) > self.max_cookie_size
|
||||
|
||||
def compute_msg(some_serialized_msg):
|
||||
return self._encode_parts(
|
||||
some_serialized_msg + [self.not_finished_json],
|
||||
encode_empty=True,
|
||||
)
|
||||
|
||||
if is_too_large_for_cookie(encoded_data):
|
||||
if remove_oldest:
|
||||
idx = bisect_keep_right(
|
||||
serialized_messages,
|
||||
fn=lambda m: is_too_large_for_cookie(compute_msg(m)),
|
||||
)
|
||||
unstored_messages = messages[:idx]
|
||||
encoded_data = compute_msg(serialized_messages[idx:])
|
||||
else:
|
||||
idx = bisect_keep_left(
|
||||
serialized_messages,
|
||||
fn=lambda m: is_too_large_for_cookie(compute_msg(m)),
|
||||
)
|
||||
unstored_messages = messages[idx:]
|
||||
encoded_data = compute_msg(serialized_messages[:idx])
|
||||
|
||||
self._update_cookie(encoded_data, response)
|
||||
return unstored_messages
|
||||
|
||||
def _encode_parts(self, messages, encode_empty=False):
|
||||
"""
|
||||
Return an encoded version of the serialized messages list which can be
|
||||
stored as plain text.
|
||||
|
||||
Since the data will be retrieved from the client-side, the encoded data
|
||||
also contains a hash to ensure that the data was not tampered with.
|
||||
"""
|
||||
if messages or encode_empty:
|
||||
return self.signer.sign_object(
|
||||
messages, serializer=MessagePartGatherSerializer, compress=True
|
||||
)
|
||||
|
||||
def _encode(self, messages, encode_empty=False):
|
||||
"""
|
||||
Return an encoded version of the messages list which can be stored as
|
||||
plain text.
|
||||
|
||||
Proxies MessagePartSerializer.dumps and _encoded_parts.
|
||||
"""
|
||||
serialized_messages = MessagePartSerializer().dumps(messages)
|
||||
return self._encode_parts(serialized_messages, encode_empty=encode_empty)
|
||||
|
||||
def _decode(self, data):
|
||||
"""
|
||||
Safely decode an encoded text stream back into a list of messages.
|
||||
|
||||
If the encoded text stream contained an invalid hash or was in an
|
||||
invalid format, return None.
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
try:
|
||||
return self.signer.unsign_object(data, serializer=MessageSerializer)
|
||||
except (signing.BadSignature, binascii.Error, json.JSONDecodeError):
|
||||
pass
|
||||
# Mark the data as used (so it gets removed) since something was wrong
|
||||
# with the data.
|
||||
self.used = True
|
||||
return None
|
||||
|
||||
|
||||
def bisect_keep_left(a, fn):
|
||||
"""
|
||||
Find the index of the first element from the start of the array that
|
||||
verifies the given condition.
|
||||
The function is applied from the start of the array to the pivot.
|
||||
"""
|
||||
lo = 0
|
||||
hi = len(a)
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
if fn(a[: mid + 1]):
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid + 1
|
||||
return lo
|
||||
|
||||
|
||||
def bisect_keep_right(a, fn):
|
||||
"""
|
||||
Find the index of the first element from the end of the array that verifies
|
||||
the given condition.
|
||||
The function is applied from the pivot to the end of array.
|
||||
"""
|
||||
lo = 0
|
||||
hi = len(a)
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
if fn(a[mid:]):
|
||||
lo = mid + 1
|
||||
else:
|
||||
hi = mid
|
||||
return lo
|
||||
@@ -0,0 +1,56 @@
|
||||
from django.contrib.messages.storage.base import BaseStorage
|
||||
from django.contrib.messages.storage.cookie import CookieStorage
|
||||
from django.contrib.messages.storage.session import SessionStorage
|
||||
|
||||
|
||||
class FallbackStorage(BaseStorage):
|
||||
"""
|
||||
Try to store all messages in the first backend. Store any unstored
|
||||
messages in each subsequent backend.
|
||||
"""
|
||||
|
||||
storage_classes = (CookieStorage, SessionStorage)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.storages = [
|
||||
storage_class(*args, **kwargs) for storage_class in self.storage_classes
|
||||
]
|
||||
self._used_storages = set()
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
"""
|
||||
Get a single list of messages from all storage backends.
|
||||
"""
|
||||
all_messages = []
|
||||
for storage in self.storages:
|
||||
messages, all_retrieved = storage._get()
|
||||
# If the backend hasn't been used, no more retrieval is necessary.
|
||||
if messages is None:
|
||||
break
|
||||
if messages:
|
||||
self._used_storages.add(storage)
|
||||
all_messages.extend(messages)
|
||||
# If this storage class contained all the messages, no further
|
||||
# retrieval is necessary
|
||||
if all_retrieved:
|
||||
break
|
||||
return all_messages, all_retrieved
|
||||
|
||||
def _store(self, messages, response, *args, **kwargs):
|
||||
"""
|
||||
Store the messages and return any unstored messages after trying all
|
||||
backends.
|
||||
|
||||
For each storage backend, any messages not stored are passed on to the
|
||||
next backend.
|
||||
"""
|
||||
for storage in self.storages:
|
||||
if messages:
|
||||
messages = storage._store(messages, response, remove_oldest=False)
|
||||
# Even if there are no more messages, continue iterating to ensure
|
||||
# storages which contained messages are flushed.
|
||||
elif storage in self._used_storages:
|
||||
storage._store([], response)
|
||||
self._used_storages.remove(storage)
|
||||
return messages
|
||||
52
venv/lib/python3.10/site-packages/django/contrib/messages/storage/session.py
Executable file
52
venv/lib/python3.10/site-packages/django/contrib/messages/storage/session.py
Executable file
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
|
||||
from django.contrib.messages.storage.base import BaseStorage
|
||||
from django.contrib.messages.storage.cookie import MessageDecoder, MessageEncoder
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
class SessionStorage(BaseStorage):
|
||||
"""
|
||||
Store messages in the session (that is, django.contrib.sessions).
|
||||
"""
|
||||
|
||||
session_key = "_messages"
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
if not hasattr(request, "session"):
|
||||
raise ImproperlyConfigured(
|
||||
"The session-based temporary message storage requires session "
|
||||
"middleware to be installed, and come before the message "
|
||||
"middleware in the MIDDLEWARE list."
|
||||
)
|
||||
super().__init__(request, *args, **kwargs)
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
"""
|
||||
Retrieve a list of messages from the request's session. This storage
|
||||
always stores everything it is given, so return True for the
|
||||
all_retrieved flag.
|
||||
"""
|
||||
return (
|
||||
self.deserialize_messages(self.request.session.get(self.session_key)),
|
||||
True,
|
||||
)
|
||||
|
||||
def _store(self, messages, response, *args, **kwargs):
|
||||
"""
|
||||
Store a list of messages to the request's session.
|
||||
"""
|
||||
if messages:
|
||||
self.request.session[self.session_key] = self.serialize_messages(messages)
|
||||
else:
|
||||
self.request.session.pop(self.session_key, None)
|
||||
return []
|
||||
|
||||
def serialize_messages(self, messages):
|
||||
encoder = MessageEncoder()
|
||||
return encoder.encode(messages)
|
||||
|
||||
def deserialize_messages(self, data):
|
||||
if data and isinstance(data, str):
|
||||
return json.loads(data, cls=MessageDecoder)
|
||||
return data
|
||||
Reference in New Issue
Block a user