mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #34901 -- Added async-compatible interface to session engines.
Thanks Andrew-Chen-Wang for the initial implementation which was posted to the Django forum thread about asyncifying contrib modules.
This commit is contained in:
parent
33c06ca0da
commit
f5c340684b
@ -269,4 +269,6 @@ def update_session_auth_hash(request, user):
|
|||||||
|
|
||||||
async def aupdate_session_auth_hash(request, user):
|
async def aupdate_session_auth_hash(request, user):
|
||||||
"""See update_session_auth_hash()."""
|
"""See update_session_auth_hash()."""
|
||||||
return await sync_to_async(update_session_auth_hash)(request, user)
|
await request.session.acycle_key()
|
||||||
|
if hasattr(user, "get_session_auth_hash") and request.user == user:
|
||||||
|
await request.session.aset(HASH_SESSION_KEY, user.get_session_auth_hash())
|
||||||
|
@ -2,6 +2,8 @@ import logging
|
|||||||
import string
|
import string
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -56,6 +58,10 @@ class SessionBase:
|
|||||||
self._session[key] = value
|
self._session[key] = value
|
||||||
self.modified = True
|
self.modified = True
|
||||||
|
|
||||||
|
async def aset(self, key, value):
|
||||||
|
(await self._aget_session())[key] = value
|
||||||
|
self.modified = True
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
del self._session[key]
|
del self._session[key]
|
||||||
self.modified = True
|
self.modified = True
|
||||||
@ -67,11 +73,19 @@ class SessionBase:
|
|||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
return self._session.get(key, default)
|
return self._session.get(key, default)
|
||||||
|
|
||||||
|
async def aget(self, key, default=None):
|
||||||
|
return (await self._aget_session()).get(key, default)
|
||||||
|
|
||||||
def pop(self, key, default=__not_given):
|
def pop(self, key, default=__not_given):
|
||||||
self.modified = self.modified or key in self._session
|
self.modified = self.modified or key in self._session
|
||||||
args = () if default is self.__not_given else (default,)
|
args = () if default is self.__not_given else (default,)
|
||||||
return self._session.pop(key, *args)
|
return self._session.pop(key, *args)
|
||||||
|
|
||||||
|
async def apop(self, key, default=__not_given):
|
||||||
|
self.modified = self.modified or key in (await self._aget_session())
|
||||||
|
args = () if default is self.__not_given else (default,)
|
||||||
|
return (await self._aget_session()).pop(key, *args)
|
||||||
|
|
||||||
def setdefault(self, key, value):
|
def setdefault(self, key, value):
|
||||||
if key in self._session:
|
if key in self._session:
|
||||||
return self._session[key]
|
return self._session[key]
|
||||||
@ -79,15 +93,32 @@ class SessionBase:
|
|||||||
self[key] = value
|
self[key] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
async def asetdefault(self, key, value):
|
||||||
|
session = await self._aget_session()
|
||||||
|
if key in session:
|
||||||
|
return session[key]
|
||||||
|
else:
|
||||||
|
await self.aset(key, value)
|
||||||
|
return value
|
||||||
|
|
||||||
def set_test_cookie(self):
|
def set_test_cookie(self):
|
||||||
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
|
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
|
||||||
|
|
||||||
|
async def aset_test_cookie(self):
|
||||||
|
await self.aset(self.TEST_COOKIE_NAME, self.TEST_COOKIE_VALUE)
|
||||||
|
|
||||||
def test_cookie_worked(self):
|
def test_cookie_worked(self):
|
||||||
return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
|
return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
|
||||||
|
|
||||||
|
async def atest_cookie_worked(self):
|
||||||
|
return (await self.aget(self.TEST_COOKIE_NAME)) == self.TEST_COOKIE_VALUE
|
||||||
|
|
||||||
def delete_test_cookie(self):
|
def delete_test_cookie(self):
|
||||||
del self[self.TEST_COOKIE_NAME]
|
del self[self.TEST_COOKIE_NAME]
|
||||||
|
|
||||||
|
async def adelete_test_cookie(self):
|
||||||
|
del (await self._aget_session())[self.TEST_COOKIE_NAME]
|
||||||
|
|
||||||
def encode(self, session_dict):
|
def encode(self, session_dict):
|
||||||
"Return the given session dictionary serialized and encoded as a string."
|
"Return the given session dictionary serialized and encoded as a string."
|
||||||
return signing.dumps(
|
return signing.dumps(
|
||||||
@ -115,18 +146,34 @@ class SessionBase:
|
|||||||
self._session.update(dict_)
|
self._session.update(dict_)
|
||||||
self.modified = True
|
self.modified = True
|
||||||
|
|
||||||
|
async def aupdate(self, dict_):
|
||||||
|
(await self._aget_session()).update(dict_)
|
||||||
|
self.modified = True
|
||||||
|
|
||||||
def has_key(self, key):
|
def has_key(self, key):
|
||||||
return key in self._session
|
return key in self._session
|
||||||
|
|
||||||
|
async def ahas_key(self, key):
|
||||||
|
return key in (await self._aget_session())
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
return self._session.keys()
|
return self._session.keys()
|
||||||
|
|
||||||
|
async def akeys(self):
|
||||||
|
return (await self._aget_session()).keys()
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
return self._session.values()
|
return self._session.values()
|
||||||
|
|
||||||
|
async def avalues(self):
|
||||||
|
return (await self._aget_session()).values()
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return self._session.items()
|
return self._session.items()
|
||||||
|
|
||||||
|
async def aitems(self):
|
||||||
|
return (await self._aget_session()).items()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
# To avoid unnecessary persistent storage accesses, we set up the
|
# To avoid unnecessary persistent storage accesses, we set up the
|
||||||
# internals directly (loading data wastes time, since we are going to
|
# internals directly (loading data wastes time, since we are going to
|
||||||
@ -149,11 +196,22 @@ class SessionBase:
|
|||||||
if not self.exists(session_key):
|
if not self.exists(session_key):
|
||||||
return session_key
|
return session_key
|
||||||
|
|
||||||
|
async def _aget_new_session_key(self):
|
||||||
|
while True:
|
||||||
|
session_key = get_random_string(32, VALID_KEY_CHARS)
|
||||||
|
if not await self.aexists(session_key):
|
||||||
|
return session_key
|
||||||
|
|
||||||
def _get_or_create_session_key(self):
|
def _get_or_create_session_key(self):
|
||||||
if self._session_key is None:
|
if self._session_key is None:
|
||||||
self._session_key = self._get_new_session_key()
|
self._session_key = self._get_new_session_key()
|
||||||
return self._session_key
|
return self._session_key
|
||||||
|
|
||||||
|
async def _aget_or_create_session_key(self):
|
||||||
|
if self._session_key is None:
|
||||||
|
self._session_key = await self._aget_new_session_key()
|
||||||
|
return self._session_key
|
||||||
|
|
||||||
def _validate_session_key(self, key):
|
def _validate_session_key(self, key):
|
||||||
"""
|
"""
|
||||||
Key must be truthy and at least 8 characters long. 8 characters is an
|
Key must be truthy and at least 8 characters long. 8 characters is an
|
||||||
@ -191,6 +249,17 @@ class SessionBase:
|
|||||||
self._session_cache = self.load()
|
self._session_cache = self.load()
|
||||||
return self._session_cache
|
return self._session_cache
|
||||||
|
|
||||||
|
async def _aget_session(self, no_load=False):
|
||||||
|
self.accessed = True
|
||||||
|
try:
|
||||||
|
return self._session_cache
|
||||||
|
except AttributeError:
|
||||||
|
if self.session_key is None or no_load:
|
||||||
|
self._session_cache = {}
|
||||||
|
else:
|
||||||
|
self._session_cache = await self.aload()
|
||||||
|
return self._session_cache
|
||||||
|
|
||||||
_session = property(_get_session)
|
_session = property(_get_session)
|
||||||
|
|
||||||
def get_session_cookie_age(self):
|
def get_session_cookie_age(self):
|
||||||
@ -223,6 +292,25 @@ class SessionBase:
|
|||||||
delta = expiry - modification
|
delta = expiry - modification
|
||||||
return delta.days * 86400 + delta.seconds
|
return delta.days * 86400 + delta.seconds
|
||||||
|
|
||||||
|
async def aget_expiry_age(self, **kwargs):
|
||||||
|
try:
|
||||||
|
modification = kwargs["modification"]
|
||||||
|
except KeyError:
|
||||||
|
modification = timezone.now()
|
||||||
|
try:
|
||||||
|
expiry = kwargs["expiry"]
|
||||||
|
except KeyError:
|
||||||
|
expiry = await self.aget("_session_expiry")
|
||||||
|
|
||||||
|
if not expiry: # Checks both None and 0 cases
|
||||||
|
return self.get_session_cookie_age()
|
||||||
|
if not isinstance(expiry, (datetime, str)):
|
||||||
|
return expiry
|
||||||
|
if isinstance(expiry, str):
|
||||||
|
expiry = datetime.fromisoformat(expiry)
|
||||||
|
delta = expiry - modification
|
||||||
|
return delta.days * 86400 + delta.seconds
|
||||||
|
|
||||||
def get_expiry_date(self, **kwargs):
|
def get_expiry_date(self, **kwargs):
|
||||||
"""Get session the expiry date (as a datetime object).
|
"""Get session the expiry date (as a datetime object).
|
||||||
|
|
||||||
@ -246,6 +334,23 @@ class SessionBase:
|
|||||||
expiry = expiry or self.get_session_cookie_age()
|
expiry = expiry or self.get_session_cookie_age()
|
||||||
return modification + timedelta(seconds=expiry)
|
return modification + timedelta(seconds=expiry)
|
||||||
|
|
||||||
|
async def aget_expiry_date(self, **kwargs):
|
||||||
|
try:
|
||||||
|
modification = kwargs["modification"]
|
||||||
|
except KeyError:
|
||||||
|
modification = timezone.now()
|
||||||
|
try:
|
||||||
|
expiry = kwargs["expiry"]
|
||||||
|
except KeyError:
|
||||||
|
expiry = await self.aget("_session_expiry")
|
||||||
|
|
||||||
|
if isinstance(expiry, datetime):
|
||||||
|
return expiry
|
||||||
|
elif isinstance(expiry, str):
|
||||||
|
return datetime.fromisoformat(expiry)
|
||||||
|
expiry = expiry or self.get_session_cookie_age()
|
||||||
|
return modification + timedelta(seconds=expiry)
|
||||||
|
|
||||||
def set_expiry(self, value):
|
def set_expiry(self, value):
|
||||||
"""
|
"""
|
||||||
Set a custom expiration for the session. ``value`` can be an integer,
|
Set a custom expiration for the session. ``value`` can be an integer,
|
||||||
@ -274,6 +379,20 @@ class SessionBase:
|
|||||||
value = value.isoformat()
|
value = value.isoformat()
|
||||||
self["_session_expiry"] = value
|
self["_session_expiry"] = value
|
||||||
|
|
||||||
|
async def aset_expiry(self, value):
|
||||||
|
if value is None:
|
||||||
|
# Remove any custom expiration for this session.
|
||||||
|
try:
|
||||||
|
await self.apop("_session_expiry")
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
if isinstance(value, timedelta):
|
||||||
|
value = timezone.now() + value
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
value = value.isoformat()
|
||||||
|
await self.aset("_session_expiry", value)
|
||||||
|
|
||||||
def get_expire_at_browser_close(self):
|
def get_expire_at_browser_close(self):
|
||||||
"""
|
"""
|
||||||
Return ``True`` if the session is set to expire when the browser
|
Return ``True`` if the session is set to expire when the browser
|
||||||
@ -285,6 +404,11 @@ class SessionBase:
|
|||||||
return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
||||||
return expiry == 0
|
return expiry == 0
|
||||||
|
|
||||||
|
async def aget_expire_at_browser_close(self):
|
||||||
|
if (expiry := await self.aget("_session_expiry")) is None:
|
||||||
|
return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
||||||
|
return expiry == 0
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""
|
"""
|
||||||
Remove the current session data from the database and regenerate the
|
Remove the current session data from the database and regenerate the
|
||||||
@ -294,6 +418,11 @@ class SessionBase:
|
|||||||
self.delete()
|
self.delete()
|
||||||
self._session_key = None
|
self._session_key = None
|
||||||
|
|
||||||
|
async def aflush(self):
|
||||||
|
self.clear()
|
||||||
|
await self.adelete()
|
||||||
|
self._session_key = None
|
||||||
|
|
||||||
def cycle_key(self):
|
def cycle_key(self):
|
||||||
"""
|
"""
|
||||||
Create a new session key, while retaining the current session data.
|
Create a new session key, while retaining the current session data.
|
||||||
@ -305,6 +434,17 @@ class SessionBase:
|
|||||||
if key:
|
if key:
|
||||||
self.delete(key)
|
self.delete(key)
|
||||||
|
|
||||||
|
async def acycle_key(self):
|
||||||
|
"""
|
||||||
|
Create a new session key, while retaining the current session data.
|
||||||
|
"""
|
||||||
|
data = await self._aget_session()
|
||||||
|
key = self.session_key
|
||||||
|
await self.acreate()
|
||||||
|
self._session_cache = data
|
||||||
|
if key:
|
||||||
|
await self.adelete(key)
|
||||||
|
|
||||||
# Methods that child classes must implement.
|
# Methods that child classes must implement.
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
@ -315,6 +455,9 @@ class SessionBase:
|
|||||||
"subclasses of SessionBase must provide an exists() method"
|
"subclasses of SessionBase must provide an exists() method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def aexists(self, session_key):
|
||||||
|
return await sync_to_async(self.exists)(session_key)
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
"""
|
"""
|
||||||
Create a new session instance. Guaranteed to create a new object with
|
Create a new session instance. Guaranteed to create a new object with
|
||||||
@ -325,6 +468,9 @@ class SessionBase:
|
|||||||
"subclasses of SessionBase must provide a create() method"
|
"subclasses of SessionBase must provide a create() method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def acreate(self):
|
||||||
|
return await sync_to_async(self.create)()
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
"""
|
"""
|
||||||
Save the session data. If 'must_create' is True, create a new session
|
Save the session data. If 'must_create' is True, create a new session
|
||||||
@ -335,6 +481,9 @@ class SessionBase:
|
|||||||
"subclasses of SessionBase must provide a save() method"
|
"subclasses of SessionBase must provide a save() method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def asave(self, must_create=False):
|
||||||
|
return await sync_to_async(self.save)(must_create)
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
"""
|
"""
|
||||||
Delete the session data under this key. If the key is None, use the
|
Delete the session data under this key. If the key is None, use the
|
||||||
@ -344,6 +493,9 @@ class SessionBase:
|
|||||||
"subclasses of SessionBase must provide a delete() method"
|
"subclasses of SessionBase must provide a delete() method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def adelete(self, session_key=None):
|
||||||
|
return await sync_to_async(self.delete)(session_key)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
Load the session data and return a dictionary.
|
Load the session data and return a dictionary.
|
||||||
@ -352,6 +504,9 @@ class SessionBase:
|
|||||||
"subclasses of SessionBase must provide a load() method"
|
"subclasses of SessionBase must provide a load() method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def aload(self):
|
||||||
|
return await sync_to_async(self.load)()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_expired(cls):
|
def clear_expired(cls):
|
||||||
"""
|
"""
|
||||||
@ -362,3 +517,7 @@ class SessionBase:
|
|||||||
a built-in expiration mechanism, it should be a no-op.
|
a built-in expiration mechanism, it should be a no-op.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("This backend does not support clear_expired().")
|
raise NotImplementedError("This backend does not support clear_expired().")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def aclear_expired(cls):
|
||||||
|
return await sync_to_async(cls.clear_expired)()
|
||||||
|
@ -20,6 +20,9 @@ class SessionStore(SessionBase):
|
|||||||
def cache_key(self):
|
def cache_key(self):
|
||||||
return self.cache_key_prefix + self._get_or_create_session_key()
|
return self.cache_key_prefix + self._get_or_create_session_key()
|
||||||
|
|
||||||
|
async def acache_key(self):
|
||||||
|
return self.cache_key_prefix + await self._aget_or_create_session_key()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
try:
|
try:
|
||||||
session_data = self._cache.get(self.cache_key)
|
session_data = self._cache.get(self.cache_key)
|
||||||
@ -32,6 +35,16 @@ class SessionStore(SessionBase):
|
|||||||
self._session_key = None
|
self._session_key = None
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
async def aload(self):
|
||||||
|
try:
|
||||||
|
session_data = await self._cache.aget(await self.acache_key())
|
||||||
|
except Exception:
|
||||||
|
session_data = None
|
||||||
|
if session_data is not None:
|
||||||
|
return session_data
|
||||||
|
self._session_key = None
|
||||||
|
return {}
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
# Because a cache can fail silently (e.g. memcache), we don't know if
|
# Because a cache can fail silently (e.g. memcache), we don't know if
|
||||||
# we are failing to create a new session because of a key collision or
|
# we are failing to create a new session because of a key collision or
|
||||||
@ -51,6 +64,20 @@ class SessionStore(SessionBase):
|
|||||||
"It is likely that the cache is unavailable."
|
"It is likely that the cache is unavailable."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def acreate(self):
|
||||||
|
for i in range(10000):
|
||||||
|
self._session_key = await self._aget_new_session_key()
|
||||||
|
try:
|
||||||
|
await self.asave(must_create=True)
|
||||||
|
except CreateError:
|
||||||
|
continue
|
||||||
|
self.modified = True
|
||||||
|
return
|
||||||
|
raise RuntimeError(
|
||||||
|
"Unable to create a new session key. "
|
||||||
|
"It is likely that the cache is unavailable."
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
if self.session_key is None:
|
if self.session_key is None:
|
||||||
return self.create()
|
return self.create()
|
||||||
@ -68,11 +95,33 @@ class SessionStore(SessionBase):
|
|||||||
if must_create and not result:
|
if must_create and not result:
|
||||||
raise CreateError
|
raise CreateError
|
||||||
|
|
||||||
|
async def asave(self, must_create=False):
|
||||||
|
if self.session_key is None:
|
||||||
|
return await self.acreate()
|
||||||
|
if must_create:
|
||||||
|
func = self._cache.aadd
|
||||||
|
elif await self._cache.aget(await self.acache_key()) is not None:
|
||||||
|
func = self._cache.aset
|
||||||
|
else:
|
||||||
|
raise UpdateError
|
||||||
|
result = await func(
|
||||||
|
await self.acache_key(),
|
||||||
|
await self._aget_session(no_load=must_create),
|
||||||
|
await self.aget_expiry_age(),
|
||||||
|
)
|
||||||
|
if must_create and not result:
|
||||||
|
raise CreateError
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
return (
|
return (
|
||||||
bool(session_key) and (self.cache_key_prefix + session_key) in self._cache
|
bool(session_key) and (self.cache_key_prefix + session_key) in self._cache
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def aexists(self, session_key):
|
||||||
|
return bool(session_key) and await self._cache.ahas_key(
|
||||||
|
self.cache_key_prefix + session_key
|
||||||
|
)
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
if session_key is None:
|
if session_key is None:
|
||||||
if self.session_key is None:
|
if self.session_key is None:
|
||||||
@ -80,6 +129,17 @@ class SessionStore(SessionBase):
|
|||||||
session_key = self.session_key
|
session_key = self.session_key
|
||||||
self._cache.delete(self.cache_key_prefix + session_key)
|
self._cache.delete(self.cache_key_prefix + session_key)
|
||||||
|
|
||||||
|
async def adelete(self, session_key=None):
|
||||||
|
if session_key is None:
|
||||||
|
if self.session_key is None:
|
||||||
|
return
|
||||||
|
session_key = self.session_key
|
||||||
|
await self._cache.adelete(self.cache_key_prefix + session_key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_expired(cls):
|
def clear_expired(cls):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def aclear_expired(cls):
|
||||||
|
pass
|
||||||
|
@ -28,6 +28,9 @@ class SessionStore(DBStore):
|
|||||||
def cache_key(self):
|
def cache_key(self):
|
||||||
return self.cache_key_prefix + self._get_or_create_session_key()
|
return self.cache_key_prefix + self._get_or_create_session_key()
|
||||||
|
|
||||||
|
async def acache_key(self):
|
||||||
|
return self.cache_key_prefix + await self._aget_or_create_session_key()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
try:
|
try:
|
||||||
data = self._cache.get(self.cache_key)
|
data = self._cache.get(self.cache_key)
|
||||||
@ -47,6 +50,27 @@ class SessionStore(DBStore):
|
|||||||
data = {}
|
data = {}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def aload(self):
|
||||||
|
try:
|
||||||
|
data = await self._cache.aget(await self.acache_key())
|
||||||
|
except Exception:
|
||||||
|
# Some backends (e.g. memcache) raise an exception on invalid
|
||||||
|
# cache keys. If this happens, reset the session. See #17810.
|
||||||
|
data = None
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
s = await self._aget_session_from_db()
|
||||||
|
if s:
|
||||||
|
data = self.decode(s.session_data)
|
||||||
|
await self._cache.aset(
|
||||||
|
await self.acache_key(),
|
||||||
|
data,
|
||||||
|
await self.aget_expiry_age(expiry=s.expire_date),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = {}
|
||||||
|
return data
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
return (
|
return (
|
||||||
session_key
|
session_key
|
||||||
@ -54,6 +78,13 @@ class SessionStore(DBStore):
|
|||||||
or super().exists(session_key)
|
or super().exists(session_key)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def aexists(self, session_key):
|
||||||
|
return (
|
||||||
|
session_key
|
||||||
|
and (self.cache_key_prefix + session_key) in self._cache
|
||||||
|
or await super().aexists(session_key)
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
super().save(must_create)
|
super().save(must_create)
|
||||||
try:
|
try:
|
||||||
@ -61,6 +92,17 @@ class SessionStore(DBStore):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error saving to cache (%s)", self._cache)
|
logger.exception("Error saving to cache (%s)", self._cache)
|
||||||
|
|
||||||
|
async def asave(self, must_create=False):
|
||||||
|
await super().asave(must_create)
|
||||||
|
try:
|
||||||
|
await self._cache.aset(
|
||||||
|
await self.acache_key(),
|
||||||
|
self._session,
|
||||||
|
await self.aget_expiry_age(),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error saving to cache (%s)", self._cache)
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
super().delete(session_key)
|
super().delete(session_key)
|
||||||
if session_key is None:
|
if session_key is None:
|
||||||
@ -69,6 +111,14 @@ class SessionStore(DBStore):
|
|||||||
session_key = self.session_key
|
session_key = self.session_key
|
||||||
self._cache.delete(self.cache_key_prefix + session_key)
|
self._cache.delete(self.cache_key_prefix + session_key)
|
||||||
|
|
||||||
|
async def adelete(self, session_key=None):
|
||||||
|
await super().adelete(session_key)
|
||||||
|
if session_key is None:
|
||||||
|
if self.session_key is None:
|
||||||
|
return
|
||||||
|
session_key = self.session_key
|
||||||
|
await self._cache.adelete(self.cache_key_prefix + session_key)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""
|
"""
|
||||||
Remove the current session data from the database and regenerate the
|
Remove the current session data from the database and regenerate the
|
||||||
@ -77,3 +127,9 @@ class SessionStore(DBStore):
|
|||||||
self.clear()
|
self.clear()
|
||||||
self.delete(self.session_key)
|
self.delete(self.session_key)
|
||||||
self._session_key = None
|
self._session_key = None
|
||||||
|
|
||||||
|
async def aflush(self):
|
||||||
|
"""See flush()."""
|
||||||
|
self.clear()
|
||||||
|
await self.adelete(self.session_key)
|
||||||
|
self._session_key = None
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
from django.contrib.sessions.backends.base import CreateError, SessionBase, UpdateError
|
from django.contrib.sessions.backends.base import CreateError, SessionBase, UpdateError
|
||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
from django.db import DatabaseError, IntegrityError, router, transaction
|
from django.db import DatabaseError, IntegrityError, router, transaction
|
||||||
@ -38,13 +40,31 @@ class SessionStore(SessionBase):
|
|||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
self._session_key = None
|
self._session_key = None
|
||||||
|
|
||||||
|
async def _aget_session_from_db(self):
|
||||||
|
try:
|
||||||
|
return await self.model.objects.aget(
|
||||||
|
session_key=self.session_key, expire_date__gt=timezone.now()
|
||||||
|
)
|
||||||
|
except (self.model.DoesNotExist, SuspiciousOperation) as e:
|
||||||
|
if isinstance(e, SuspiciousOperation):
|
||||||
|
logger = logging.getLogger("django.security.%s" % e.__class__.__name__)
|
||||||
|
logger.warning(str(e))
|
||||||
|
self._session_key = None
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
s = self._get_session_from_db()
|
s = self._get_session_from_db()
|
||||||
return self.decode(s.session_data) if s else {}
|
return self.decode(s.session_data) if s else {}
|
||||||
|
|
||||||
|
async def aload(self):
|
||||||
|
s = await self._aget_session_from_db()
|
||||||
|
return self.decode(s.session_data) if s else {}
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
return self.model.objects.filter(session_key=session_key).exists()
|
return self.model.objects.filter(session_key=session_key).exists()
|
||||||
|
|
||||||
|
async def aexists(self, session_key):
|
||||||
|
return await self.model.objects.filter(session_key=session_key).aexists()
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
while True:
|
while True:
|
||||||
self._session_key = self._get_new_session_key()
|
self._session_key = self._get_new_session_key()
|
||||||
@ -58,6 +78,19 @@ class SessionStore(SessionBase):
|
|||||||
self.modified = True
|
self.modified = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
async def acreate(self):
|
||||||
|
while True:
|
||||||
|
self._session_key = await self._aget_new_session_key()
|
||||||
|
try:
|
||||||
|
# Save immediately to ensure we have a unique entry in the
|
||||||
|
# database.
|
||||||
|
await self.asave(must_create=True)
|
||||||
|
except CreateError:
|
||||||
|
# Key wasn't unique. Try again.
|
||||||
|
continue
|
||||||
|
self.modified = True
|
||||||
|
return
|
||||||
|
|
||||||
def create_model_instance(self, data):
|
def create_model_instance(self, data):
|
||||||
"""
|
"""
|
||||||
Return a new instance of the session model object, which represents the
|
Return a new instance of the session model object, which represents the
|
||||||
@ -70,6 +103,14 @@ class SessionStore(SessionBase):
|
|||||||
expire_date=self.get_expiry_date(),
|
expire_date=self.get_expiry_date(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def acreate_model_instance(self, data):
|
||||||
|
"""See create_model_instance()."""
|
||||||
|
return self.model(
|
||||||
|
session_key=await self._aget_or_create_session_key(),
|
||||||
|
session_data=self.encode(data),
|
||||||
|
expire_date=await self.aget_expiry_date(),
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
"""
|
"""
|
||||||
Save the current session data to the database. If 'must_create' is
|
Save the current session data to the database. If 'must_create' is
|
||||||
@ -95,6 +136,36 @@ class SessionStore(SessionBase):
|
|||||||
raise UpdateError
|
raise UpdateError
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
async def asave(self, must_create=False):
|
||||||
|
"""See save()."""
|
||||||
|
if self.session_key is None:
|
||||||
|
return await self.acreate()
|
||||||
|
data = await self._aget_session(no_load=must_create)
|
||||||
|
obj = await self.acreate_model_instance(data)
|
||||||
|
using = router.db_for_write(self.model, instance=obj)
|
||||||
|
try:
|
||||||
|
# This code MOST run in a transaction, so it requires
|
||||||
|
# @sync_to_async wrapping until transaction.atomic() supports
|
||||||
|
# async.
|
||||||
|
@sync_to_async
|
||||||
|
def sync_transaction():
|
||||||
|
with transaction.atomic(using=using):
|
||||||
|
obj.save(
|
||||||
|
force_insert=must_create,
|
||||||
|
force_update=not must_create,
|
||||||
|
using=using,
|
||||||
|
)
|
||||||
|
|
||||||
|
await sync_transaction()
|
||||||
|
except IntegrityError:
|
||||||
|
if must_create:
|
||||||
|
raise CreateError
|
||||||
|
raise
|
||||||
|
except DatabaseError:
|
||||||
|
if not must_create:
|
||||||
|
raise UpdateError
|
||||||
|
raise
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
if session_key is None:
|
if session_key is None:
|
||||||
if self.session_key is None:
|
if self.session_key is None:
|
||||||
@ -105,6 +176,23 @@ class SessionStore(SessionBase):
|
|||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def adelete(self, session_key=None):
|
||||||
|
if session_key is None:
|
||||||
|
if self.session_key is None:
|
||||||
|
return
|
||||||
|
session_key = self.session_key
|
||||||
|
try:
|
||||||
|
obj = await self.model.objects.aget(session_key=session_key)
|
||||||
|
await obj.adelete()
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_expired(cls):
|
def clear_expired(cls):
|
||||||
cls.get_model_class().objects.filter(expire_date__lt=timezone.now()).delete()
|
cls.get_model_class().objects.filter(expire_date__lt=timezone.now()).delete()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def aclear_expired(cls):
|
||||||
|
await cls.get_model_class().objects.filter(
|
||||||
|
expire_date__lt=timezone.now()
|
||||||
|
).adelete()
|
||||||
|
@ -104,6 +104,9 @@ class SessionStore(SessionBase):
|
|||||||
self._session_key = None
|
self._session_key = None
|
||||||
return session_data
|
return session_data
|
||||||
|
|
||||||
|
async def aload(self):
|
||||||
|
return self.load()
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
while True:
|
while True:
|
||||||
self._session_key = self._get_new_session_key()
|
self._session_key = self._get_new_session_key()
|
||||||
@ -114,6 +117,9 @@ class SessionStore(SessionBase):
|
|||||||
self.modified = True
|
self.modified = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
async def acreate(self):
|
||||||
|
return self.create()
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
if self.session_key is None:
|
if self.session_key is None:
|
||||||
return self.create()
|
return self.create()
|
||||||
@ -177,9 +183,15 @@ class SessionStore(SessionBase):
|
|||||||
except (EOFError, OSError):
|
except (EOFError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def asave(self, must_create=False):
|
||||||
|
return self.save(must_create=must_create)
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
return os.path.exists(self._key_to_file(session_key))
|
return os.path.exists(self._key_to_file(session_key))
|
||||||
|
|
||||||
|
async def aexists(self, session_key):
|
||||||
|
return self.exists(session_key)
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
if session_key is None:
|
if session_key is None:
|
||||||
if self.session_key is None:
|
if self.session_key is None:
|
||||||
@ -190,6 +202,9 @@ class SessionStore(SessionBase):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def adelete(self, session_key=None):
|
||||||
|
return self.delete(session_key=session_key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_expired(cls):
|
def clear_expired(cls):
|
||||||
storage_path = cls._get_storage_path()
|
storage_path = cls._get_storage_path()
|
||||||
@ -205,3 +220,7 @@ class SessionStore(SessionBase):
|
|||||||
# the create() method.
|
# the create() method.
|
||||||
session.create = lambda: None
|
session.create = lambda: None
|
||||||
session.load()
|
session.load()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def aclear_expired(cls):
|
||||||
|
cls.clear_expired()
|
||||||
|
@ -23,6 +23,9 @@ class SessionStore(SessionBase):
|
|||||||
self.create()
|
self.create()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
async def aload(self):
|
||||||
|
return self.load()
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
"""
|
"""
|
||||||
To create a new key, set the modified flag so that the cookie is set
|
To create a new key, set the modified flag so that the cookie is set
|
||||||
@ -30,6 +33,9 @@ class SessionStore(SessionBase):
|
|||||||
"""
|
"""
|
||||||
self.modified = True
|
self.modified = True
|
||||||
|
|
||||||
|
async def acreate(self):
|
||||||
|
return self.create()
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
"""
|
"""
|
||||||
To save, get the session key as a securely signed string and then set
|
To save, get the session key as a securely signed string and then set
|
||||||
@ -39,6 +45,9 @@ class SessionStore(SessionBase):
|
|||||||
self._session_key = self._get_session_key()
|
self._session_key = self._get_session_key()
|
||||||
self.modified = True
|
self.modified = True
|
||||||
|
|
||||||
|
async def asave(self, must_create=False):
|
||||||
|
return self.save(must_create=must_create)
|
||||||
|
|
||||||
def exists(self, session_key=None):
|
def exists(self, session_key=None):
|
||||||
"""
|
"""
|
||||||
This method makes sense when you're talking to a shared resource, but
|
This method makes sense when you're talking to a shared resource, but
|
||||||
@ -47,6 +56,9 @@ class SessionStore(SessionBase):
|
|||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def aexists(self, session_key=None):
|
||||||
|
return self.exists(session_key=session_key)
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
"""
|
"""
|
||||||
To delete, clear the session key and the underlying data structure
|
To delete, clear the session key and the underlying data structure
|
||||||
@ -57,6 +69,9 @@ class SessionStore(SessionBase):
|
|||||||
self._session_cache = {}
|
self._session_cache = {}
|
||||||
self.modified = True
|
self.modified = True
|
||||||
|
|
||||||
|
async def adelete(self, session_key=None):
|
||||||
|
return self.delete(session_key=session_key)
|
||||||
|
|
||||||
def cycle_key(self):
|
def cycle_key(self):
|
||||||
"""
|
"""
|
||||||
Keep the same data but with a new key. Call save() and it will
|
Keep the same data but with a new key. Call save() and it will
|
||||||
@ -64,6 +79,9 @@ class SessionStore(SessionBase):
|
|||||||
"""
|
"""
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
async def acycle_key(self):
|
||||||
|
return self.cycle_key()
|
||||||
|
|
||||||
def _get_session_key(self):
|
def _get_session_key(self):
|
||||||
"""
|
"""
|
||||||
Instead of generating a random string, generate a secure url-safe
|
Instead of generating a random string, generate a secure url-safe
|
||||||
@ -79,3 +97,7 @@ class SessionStore(SessionBase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def clear_expired(cls):
|
def clear_expired(cls):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def aclear_expired(cls):
|
||||||
|
pass
|
||||||
|
@ -817,7 +817,14 @@ class ClientMixin:
|
|||||||
return session
|
return session
|
||||||
|
|
||||||
async def asession(self):
|
async def asession(self):
|
||||||
return await sync_to_async(lambda: self.session)()
|
engine = import_module(settings.SESSION_ENGINE)
|
||||||
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME)
|
||||||
|
if cookie:
|
||||||
|
return engine.SessionStore(cookie.value)
|
||||||
|
session = engine.SessionStore()
|
||||||
|
await session.asave()
|
||||||
|
self.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
|
||||||
|
return session
|
||||||
|
|
||||||
def login(self, **credentials):
|
def login(self, **credentials):
|
||||||
"""
|
"""
|
||||||
@ -893,7 +900,7 @@ class ClientMixin:
|
|||||||
|
|
||||||
await alogin(request, user, backend)
|
await alogin(request, user, backend)
|
||||||
# Save the session values.
|
# Save the session values.
|
||||||
await sync_to_async(request.session.save)()
|
await request.session.asave()
|
||||||
self._set_login_cookies(request)
|
self._set_login_cookies(request)
|
||||||
|
|
||||||
def _set_login_cookies(self, request):
|
def _set_login_cookies(self, request):
|
||||||
|
@ -125,6 +125,10 @@ Minor features
|
|||||||
error messages with their traceback via the newly added
|
error messages with their traceback via the newly added
|
||||||
:ref:`sessions logger <django-contrib-sessions-logger>`.
|
:ref:`sessions logger <django-contrib-sessions-logger>`.
|
||||||
|
|
||||||
|
* :class:`django.contrib.sessions.backends.base.SessionBase` and all built-in
|
||||||
|
session engines now provide async API. The new asynchronous methods all have
|
||||||
|
``a`` prefixed names, e.g. ``aget()``, ``akeys()``, or ``acycle_key()``.
|
||||||
|
|
||||||
:mod:`django.contrib.sitemaps`
|
:mod:`django.contrib.sitemaps`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -196,54 +196,156 @@ You can edit it multiple times.
|
|||||||
Example: ``'fav_color' in request.session``
|
Example: ``'fav_color' in request.session``
|
||||||
|
|
||||||
.. method:: get(key, default=None)
|
.. method:: get(key, default=None)
|
||||||
|
.. method:: aget(key, default=None)
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aget()``
|
||||||
|
|
||||||
Example: ``fav_color = request.session.get('fav_color', 'red')``
|
Example: ``fav_color = request.session.get('fav_color', 'red')``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aget()`` function was added.
|
||||||
|
|
||||||
|
.. method:: aset(key, value)
|
||||||
|
|
||||||
|
.. versionadded:: 5.1
|
||||||
|
|
||||||
|
Example: ``await request.session.aset('fav_color', 'red')``
|
||||||
|
|
||||||
|
.. method:: update(dict)
|
||||||
|
.. method:: aupdate(dict)
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aupdate()``
|
||||||
|
|
||||||
|
Example: ``request.session.update({'fav_color': 'red'})``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aupdate()`` function was added.
|
||||||
|
|
||||||
.. method:: pop(key, default=__not_given)
|
.. method:: pop(key, default=__not_given)
|
||||||
|
.. method:: apop(key, default=__not_given)
|
||||||
|
|
||||||
|
*Asynchronous version*: ``apop()``
|
||||||
|
|
||||||
Example: ``fav_color = request.session.pop('fav_color', 'blue')``
|
Example: ``fav_color = request.session.pop('fav_color', 'blue')``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``apop()`` function was added.
|
||||||
|
|
||||||
.. method:: keys()
|
.. method:: keys()
|
||||||
|
.. method:: akeys()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``akeys()``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``akeys()`` function was added.
|
||||||
|
|
||||||
|
.. method:: values()
|
||||||
|
.. method:: avalues()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``avalues()``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``avalues()`` function was added.
|
||||||
|
|
||||||
|
.. method:: has_key(key)
|
||||||
|
.. method:: ahas_key(key)
|
||||||
|
|
||||||
|
*Asynchronous version*: ``ahas_key()``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``ahas_key()`` function was added.
|
||||||
|
|
||||||
.. method:: items()
|
.. method:: items()
|
||||||
|
.. method:: aitems()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aitems()``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aitems()`` function was added.
|
||||||
|
|
||||||
.. method:: setdefault()
|
.. method:: setdefault()
|
||||||
|
.. method:: asetdefault()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``asetdefault()``
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``asetdefault()`` function was added.
|
||||||
|
|
||||||
.. method:: clear()
|
.. method:: clear()
|
||||||
|
|
||||||
It also has these methods:
|
It also has these methods:
|
||||||
|
|
||||||
.. method:: flush()
|
.. method:: flush()
|
||||||
|
.. method:: aflush()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aflush()``
|
||||||
|
|
||||||
Deletes the current session data from the session and deletes the session
|
Deletes the current session data from the session and deletes the session
|
||||||
cookie. This is used if you want to ensure that the previous session data
|
cookie. This is used if you want to ensure that the previous session data
|
||||||
can't be accessed again from the user's browser (for example, the
|
can't be accessed again from the user's browser (for example, the
|
||||||
:func:`django.contrib.auth.logout()` function calls it).
|
:func:`django.contrib.auth.logout()` function calls it).
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aflush()`` function was added.
|
||||||
|
|
||||||
.. method:: set_test_cookie()
|
.. method:: set_test_cookie()
|
||||||
|
.. method:: aset_test_cookie()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aset_test_cookie()``
|
||||||
|
|
||||||
Sets a test cookie to determine whether the user's browser supports
|
Sets a test cookie to determine whether the user's browser supports
|
||||||
cookies. Due to the way cookies work, you won't be able to test this
|
cookies. Due to the way cookies work, you won't be able to test this
|
||||||
until the user's next page request. See `Setting test cookies`_ below for
|
until the user's next page request. See `Setting test cookies`_ below for
|
||||||
more information.
|
more information.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aset_test_cookie()`` function was added.
|
||||||
|
|
||||||
.. method:: test_cookie_worked()
|
.. method:: test_cookie_worked()
|
||||||
|
.. method:: atest_cookie_worked()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``atest_cookie_worked()``
|
||||||
|
|
||||||
Returns either ``True`` or ``False``, depending on whether the user's
|
Returns either ``True`` or ``False``, depending on whether the user's
|
||||||
browser accepted the test cookie. Due to the way cookies work, you'll
|
browser accepted the test cookie. Due to the way cookies work, you'll
|
||||||
have to call ``set_test_cookie()`` on a previous, separate page request.
|
have to call ``set_test_cookie()`` or ``aset_test_cookie()`` on a
|
||||||
|
previous, separate page request.
|
||||||
See `Setting test cookies`_ below for more information.
|
See `Setting test cookies`_ below for more information.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``atest_cookie_worked()`` function was added.
|
||||||
|
|
||||||
.. method:: delete_test_cookie()
|
.. method:: delete_test_cookie()
|
||||||
|
.. method:: adelete_test_cookie()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``adelete_test_cookie()``
|
||||||
|
|
||||||
Deletes the test cookie. Use this to clean up after yourself.
|
Deletes the test cookie. Use this to clean up after yourself.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``adelete_test_cookie()`` function was added.
|
||||||
|
|
||||||
.. method:: get_session_cookie_age()
|
.. method:: get_session_cookie_age()
|
||||||
|
|
||||||
Returns the value of the setting :setting:`SESSION_COOKIE_AGE`. This can
|
Returns the value of the setting :setting:`SESSION_COOKIE_AGE`. This can
|
||||||
be overridden in a custom session backend.
|
be overridden in a custom session backend.
|
||||||
|
|
||||||
.. method:: set_expiry(value)
|
.. method:: set_expiry(value)
|
||||||
|
.. method:: aset_expiry(value)
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aset_expiry()``
|
||||||
|
|
||||||
Sets the expiration time for the session. You can pass a number of
|
Sets the expiration time for the session. You can pass a number of
|
||||||
different values:
|
different values:
|
||||||
@ -266,7 +368,14 @@ You can edit it multiple times.
|
|||||||
purposes. Session expiration is computed from the last time the
|
purposes. Session expiration is computed from the last time the
|
||||||
session was *modified*.
|
session was *modified*.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aset_expiry()`` function was added.
|
||||||
|
|
||||||
.. method:: get_expiry_age()
|
.. method:: get_expiry_age()
|
||||||
|
.. method:: aget_expiry_age()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aget_expiry_age()``
|
||||||
|
|
||||||
Returns the number of seconds until this session expires. For sessions
|
Returns the number of seconds until this session expires. For sessions
|
||||||
with no custom expiration (or those set to expire at browser close), this
|
with no custom expiration (or those set to expire at browser close), this
|
||||||
@ -279,7 +388,7 @@ You can edit it multiple times.
|
|||||||
- ``expiry``: expiry information for the session, as a
|
- ``expiry``: expiry information for the session, as a
|
||||||
:class:`~datetime.datetime` object, an :class:`int` (in seconds), or
|
:class:`~datetime.datetime` object, an :class:`int` (in seconds), or
|
||||||
``None``. Defaults to the value stored in the session by
|
``None``. Defaults to the value stored in the session by
|
||||||
:meth:`set_expiry`, if there is one, or ``None``.
|
:meth:`set_expiry`/:meth:`aset_expiry`, if there is one, or ``None``.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -295,7 +404,14 @@ You can edit it multiple times.
|
|||||||
|
|
||||||
expires_at = modification + timedelta(seconds=settings.SESSION_COOKIE_AGE)
|
expires_at = modification + timedelta(seconds=settings.SESSION_COOKIE_AGE)
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aget_expiry_age()`` function was added.
|
||||||
|
|
||||||
.. method:: get_expiry_date()
|
.. method:: get_expiry_date()
|
||||||
|
.. method:: aget_expiry_date()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aget_expiry_date()``
|
||||||
|
|
||||||
Returns the date this session will expire. For sessions with no custom
|
Returns the date this session will expire. For sessions with no custom
|
||||||
expiration (or those set to expire at browser close), this will equal the
|
expiration (or those set to expire at browser close), this will equal the
|
||||||
@ -304,22 +420,47 @@ You can edit it multiple times.
|
|||||||
This function accepts the same keyword arguments as
|
This function accepts the same keyword arguments as
|
||||||
:meth:`get_expiry_age`, and similar notes on usage apply.
|
:meth:`get_expiry_age`, and similar notes on usage apply.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aget_expiry_date()`` function was added.
|
||||||
|
|
||||||
.. method:: get_expire_at_browser_close()
|
.. method:: get_expire_at_browser_close()
|
||||||
|
.. method:: aget_expire_at_browser_close()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aget_expire_at_browser_close()``
|
||||||
|
|
||||||
Returns either ``True`` or ``False``, depending on whether the user's
|
Returns either ``True`` or ``False``, depending on whether the user's
|
||||||
session cookie will expire when the user's web browser is closed.
|
session cookie will expire when the user's web browser is closed.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aget_expire_at_browser_close()`` function was added.
|
||||||
|
|
||||||
.. method:: clear_expired()
|
.. method:: clear_expired()
|
||||||
|
.. method:: aclear_expired()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``aclear_expired()``
|
||||||
|
|
||||||
Removes expired sessions from the session store. This class method is
|
Removes expired sessions from the session store. This class method is
|
||||||
called by :djadmin:`clearsessions`.
|
called by :djadmin:`clearsessions`.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aclear_expired()`` function was added.
|
||||||
|
|
||||||
.. method:: cycle_key()
|
.. method:: cycle_key()
|
||||||
|
.. method:: acycle_key()
|
||||||
|
|
||||||
|
*Asynchronous version*: ``acycle_key()``
|
||||||
|
|
||||||
Creates a new session key while retaining the current session data.
|
Creates a new session key while retaining the current session data.
|
||||||
:func:`django.contrib.auth.login()` calls this method to mitigate against
|
:func:`django.contrib.auth.login()` calls this method to mitigate against
|
||||||
session fixation.
|
session fixation.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``acycle_key()`` function was added.
|
||||||
|
|
||||||
.. _session_serialization:
|
.. _session_serialization:
|
||||||
|
|
||||||
Session serialization
|
Session serialization
|
||||||
@ -475,6 +616,10 @@ Here's a typical usage example::
|
|||||||
request.session.set_test_cookie()
|
request.session.set_test_cookie()
|
||||||
return render(request, "foo/login_form.html")
|
return render(request, "foo/login_form.html")
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
Support for setting test cookies in asynchronous view functions was added.
|
||||||
|
|
||||||
Using sessions out of views
|
Using sessions out of views
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
@ -694,16 +839,26 @@ the corresponding session engine. By convention, the session store object class
|
|||||||
is named ``SessionStore`` and is located in the module designated by
|
is named ``SessionStore`` and is located in the module designated by
|
||||||
:setting:`SESSION_ENGINE`.
|
:setting:`SESSION_ENGINE`.
|
||||||
|
|
||||||
All ``SessionStore`` classes available in Django inherit from
|
All ``SessionStore`` subclasses available in Django implement the following
|
||||||
:class:`~backends.base.SessionBase` and implement data manipulation methods,
|
data manipulation methods:
|
||||||
namely:
|
|
||||||
|
|
||||||
* ``exists()``
|
* ``exists()``
|
||||||
* ``create()``
|
* ``create()``
|
||||||
* ``save()``
|
* ``save()``
|
||||||
* ``delete()``
|
* ``delete()``
|
||||||
* ``load()``
|
* ``load()``
|
||||||
* :meth:`~backends.base.SessionBase.clear_expired`
|
* :meth:`~.SessionBase.clear_expired`
|
||||||
|
|
||||||
|
An asynchronous interface for these methods is provided by wrapping them with
|
||||||
|
``sync_to_async()``. They can be implemented directly if an async-native
|
||||||
|
implementation is available:
|
||||||
|
|
||||||
|
* ``aexists()``
|
||||||
|
* ``acreate()``
|
||||||
|
* ``asave()``
|
||||||
|
* ``adelete()``
|
||||||
|
* ``aload()``
|
||||||
|
* :meth:`~.SessionBase.aclear_expired`
|
||||||
|
|
||||||
In order to build a custom session engine or to customize an existing one, you
|
In order to build a custom session engine or to customize an existing one, you
|
||||||
may create a new class inheriting from :class:`~backends.base.SessionBase` or
|
may create a new class inheriting from :class:`~backends.base.SessionBase` or
|
||||||
@ -713,6 +868,11 @@ You can extend the session engines, but doing so with database-backed session
|
|||||||
engines generally requires some extra effort (see the next section for
|
engines generally requires some extra effort (see the next section for
|
||||||
details).
|
details).
|
||||||
|
|
||||||
|
.. versionchanged:: 5.1
|
||||||
|
|
||||||
|
``aexists()``, ``acreate()``, ``asave()``, ``adelete()``, ``aload()``, and
|
||||||
|
``aclear_expired()`` methods were added.
|
||||||
|
|
||||||
.. _extending-database-backed-session-engines:
|
.. _extending-database-backed-session-engines:
|
||||||
|
|
||||||
Extending database-backed session engines
|
Extending database-backed session engines
|
||||||
|
3
tests/cache/failing_cache.py
vendored
3
tests/cache/failing_cache.py
vendored
@ -5,3 +5,6 @@ class CacheClass(LocMemCache):
|
|||||||
|
|
||||||
def set(self, *args, **kwargs):
|
def set(self, *args, **kwargs):
|
||||||
raise Exception("Faked exception saving to cache")
|
raise Exception("Faked exception saving to cache")
|
||||||
|
|
||||||
|
async def aset(self, *args, **kwargs):
|
||||||
|
raise Exception("Faked exception saving to cache")
|
||||||
|
@ -61,11 +61,19 @@ class SessionTestsMixin:
|
|||||||
def test_get_empty(self):
|
def test_get_empty(self):
|
||||||
self.assertIsNone(self.session.get("cat"))
|
self.assertIsNone(self.session.get("cat"))
|
||||||
|
|
||||||
|
async def test_get_empty_async(self):
|
||||||
|
self.assertIsNone(await self.session.aget("cat"))
|
||||||
|
|
||||||
def test_store(self):
|
def test_store(self):
|
||||||
self.session["cat"] = "dog"
|
self.session["cat"] = "dog"
|
||||||
self.assertIs(self.session.modified, True)
|
self.assertIs(self.session.modified, True)
|
||||||
self.assertEqual(self.session.pop("cat"), "dog")
|
self.assertEqual(self.session.pop("cat"), "dog")
|
||||||
|
|
||||||
|
async def test_store_async(self):
|
||||||
|
await self.session.aset("cat", "dog")
|
||||||
|
self.assertIs(self.session.modified, True)
|
||||||
|
self.assertEqual(await self.session.apop("cat"), "dog")
|
||||||
|
|
||||||
def test_pop(self):
|
def test_pop(self):
|
||||||
self.session["some key"] = "exists"
|
self.session["some key"] = "exists"
|
||||||
# Need to reset these to pretend we haven't accessed it:
|
# Need to reset these to pretend we haven't accessed it:
|
||||||
@ -77,6 +85,17 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.modified, True)
|
self.assertIs(self.session.modified, True)
|
||||||
self.assertIsNone(self.session.get("some key"))
|
self.assertIsNone(self.session.get("some key"))
|
||||||
|
|
||||||
|
async def test_pop_async(self):
|
||||||
|
await self.session.aset("some key", "exists")
|
||||||
|
# Need to reset these to pretend we haven't accessed it:
|
||||||
|
self.accessed = False
|
||||||
|
self.modified = False
|
||||||
|
|
||||||
|
self.assertEqual(await self.session.apop("some key"), "exists")
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, True)
|
||||||
|
self.assertIsNone(await self.session.aget("some key"))
|
||||||
|
|
||||||
def test_pop_default(self):
|
def test_pop_default(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.session.pop("some key", "does not exist"), "does not exist"
|
self.session.pop("some key", "does not exist"), "does not exist"
|
||||||
@ -84,6 +103,13 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, False)
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
|
async def test_pop_default_async(self):
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.apop("some key", "does not exist"), "does not exist"
|
||||||
|
)
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
def test_pop_default_named_argument(self):
|
def test_pop_default_named_argument(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.session.pop("some key", default="does not exist"), "does not exist"
|
self.session.pop("some key", default="does not exist"), "does not exist"
|
||||||
@ -91,22 +117,46 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, False)
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
|
async def test_pop_default_named_argument_async(self):
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.apop("some key", default="does not exist"),
|
||||||
|
"does not exist",
|
||||||
|
)
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
def test_pop_no_default_keyerror_raised(self):
|
def test_pop_no_default_keyerror_raised(self):
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
self.session.pop("some key")
|
self.session.pop("some key")
|
||||||
|
|
||||||
|
async def test_pop_no_default_keyerror_raised_async(self):
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
await self.session.apop("some key")
|
||||||
|
|
||||||
def test_setdefault(self):
|
def test_setdefault(self):
|
||||||
self.assertEqual(self.session.setdefault("foo", "bar"), "bar")
|
self.assertEqual(self.session.setdefault("foo", "bar"), "bar")
|
||||||
self.assertEqual(self.session.setdefault("foo", "baz"), "bar")
|
self.assertEqual(self.session.setdefault("foo", "baz"), "bar")
|
||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, True)
|
self.assertIs(self.session.modified, True)
|
||||||
|
|
||||||
|
async def test_setdefault_async(self):
|
||||||
|
self.assertEqual(await self.session.asetdefault("foo", "bar"), "bar")
|
||||||
|
self.assertEqual(await self.session.asetdefault("foo", "baz"), "bar")
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, True)
|
||||||
|
|
||||||
def test_update(self):
|
def test_update(self):
|
||||||
self.session.update({"update key": 1})
|
self.session.update({"update key": 1})
|
||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, True)
|
self.assertIs(self.session.modified, True)
|
||||||
self.assertEqual(self.session.get("update key", None), 1)
|
self.assertEqual(self.session.get("update key", None), 1)
|
||||||
|
|
||||||
|
async def test_update_async(self):
|
||||||
|
await self.session.aupdate({"update key": 1})
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, True)
|
||||||
|
self.assertEqual(await self.session.aget("update key", None), 1)
|
||||||
|
|
||||||
def test_has_key(self):
|
def test_has_key(self):
|
||||||
self.session["some key"] = 1
|
self.session["some key"] = 1
|
||||||
self.session.modified = False
|
self.session.modified = False
|
||||||
@ -115,6 +165,14 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, False)
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
|
async def test_has_key_async(self):
|
||||||
|
await self.session.aset("some key", 1)
|
||||||
|
self.session.modified = False
|
||||||
|
self.session.accessed = False
|
||||||
|
self.assertIs(await self.session.ahas_key("some key"), True)
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
def test_values(self):
|
def test_values(self):
|
||||||
self.assertEqual(list(self.session.values()), [])
|
self.assertEqual(list(self.session.values()), [])
|
||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
@ -125,6 +183,16 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, False)
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
|
async def test_values_async(self):
|
||||||
|
self.assertEqual(list(await self.session.avalues()), [])
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
await self.session.aset("some key", 1)
|
||||||
|
self.session.modified = False
|
||||||
|
self.session.accessed = False
|
||||||
|
self.assertEqual(list(await self.session.avalues()), [1])
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
def test_keys(self):
|
def test_keys(self):
|
||||||
self.session["x"] = 1
|
self.session["x"] = 1
|
||||||
self.session.modified = False
|
self.session.modified = False
|
||||||
@ -133,6 +201,14 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, False)
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
|
async def test_keys_async(self):
|
||||||
|
await self.session.aset("x", 1)
|
||||||
|
self.session.modified = False
|
||||||
|
self.session.accessed = False
|
||||||
|
self.assertEqual(list(await self.session.akeys()), ["x"])
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
def test_items(self):
|
def test_items(self):
|
||||||
self.session["x"] = 1
|
self.session["x"] = 1
|
||||||
self.session.modified = False
|
self.session.modified = False
|
||||||
@ -141,6 +217,14 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
self.assertIs(self.session.modified, False)
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
|
async def test_items_async(self):
|
||||||
|
await self.session.aset("x", 1)
|
||||||
|
self.session.modified = False
|
||||||
|
self.session.accessed = False
|
||||||
|
self.assertEqual(list(await self.session.aitems()), [("x", 1)])
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
self.assertIs(self.session.modified, False)
|
||||||
|
|
||||||
def test_clear(self):
|
def test_clear(self):
|
||||||
self.session["x"] = 1
|
self.session["x"] = 1
|
||||||
self.session.modified = False
|
self.session.modified = False
|
||||||
@ -155,11 +239,20 @@ class SessionTestsMixin:
|
|||||||
self.session.save()
|
self.session.save()
|
||||||
self.assertIs(self.session.exists(self.session.session_key), True)
|
self.assertIs(self.session.exists(self.session.session_key), True)
|
||||||
|
|
||||||
|
async def test_save_async(self):
|
||||||
|
await self.session.asave()
|
||||||
|
self.assertIs(await self.session.aexists(self.session.session_key), True)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
self.session.save()
|
self.session.save()
|
||||||
self.session.delete(self.session.session_key)
|
self.session.delete(self.session.session_key)
|
||||||
self.assertIs(self.session.exists(self.session.session_key), False)
|
self.assertIs(self.session.exists(self.session.session_key), False)
|
||||||
|
|
||||||
|
async def test_delete_async(self):
|
||||||
|
await self.session.asave()
|
||||||
|
await self.session.adelete(self.session.session_key)
|
||||||
|
self.assertIs(await self.session.aexists(self.session.session_key), False)
|
||||||
|
|
||||||
def test_flush(self):
|
def test_flush(self):
|
||||||
self.session["foo"] = "bar"
|
self.session["foo"] = "bar"
|
||||||
self.session.save()
|
self.session.save()
|
||||||
@ -171,6 +264,17 @@ class SessionTestsMixin:
|
|||||||
self.assertIs(self.session.modified, True)
|
self.assertIs(self.session.modified, True)
|
||||||
self.assertIs(self.session.accessed, True)
|
self.assertIs(self.session.accessed, True)
|
||||||
|
|
||||||
|
async def test_flush_async(self):
|
||||||
|
await self.session.aset("foo", "bar")
|
||||||
|
await self.session.asave()
|
||||||
|
prev_key = self.session.session_key
|
||||||
|
await self.session.aflush()
|
||||||
|
self.assertIs(await self.session.aexists(prev_key), False)
|
||||||
|
self.assertNotEqual(self.session.session_key, prev_key)
|
||||||
|
self.assertIsNone(self.session.session_key)
|
||||||
|
self.assertIs(self.session.modified, True)
|
||||||
|
self.assertIs(self.session.accessed, True)
|
||||||
|
|
||||||
def test_cycle(self):
|
def test_cycle(self):
|
||||||
self.session["a"], self.session["b"] = "c", "d"
|
self.session["a"], self.session["b"] = "c", "d"
|
||||||
self.session.save()
|
self.session.save()
|
||||||
@ -181,6 +285,17 @@ class SessionTestsMixin:
|
|||||||
self.assertNotEqual(self.session.session_key, prev_key)
|
self.assertNotEqual(self.session.session_key, prev_key)
|
||||||
self.assertEqual(list(self.session.items()), prev_data)
|
self.assertEqual(list(self.session.items()), prev_data)
|
||||||
|
|
||||||
|
async def test_cycle_async(self):
|
||||||
|
await self.session.aset("a", "c")
|
||||||
|
await self.session.aset("b", "d")
|
||||||
|
await self.session.asave()
|
||||||
|
prev_key = self.session.session_key
|
||||||
|
prev_data = list(await self.session.aitems())
|
||||||
|
await self.session.acycle_key()
|
||||||
|
self.assertIs(await self.session.aexists(prev_key), False)
|
||||||
|
self.assertNotEqual(self.session.session_key, prev_key)
|
||||||
|
self.assertEqual(list(await self.session.aitems()), prev_data)
|
||||||
|
|
||||||
def test_cycle_with_no_session_cache(self):
|
def test_cycle_with_no_session_cache(self):
|
||||||
self.session["a"], self.session["b"] = "c", "d"
|
self.session["a"], self.session["b"] = "c", "d"
|
||||||
self.session.save()
|
self.session.save()
|
||||||
@ -190,11 +305,26 @@ class SessionTestsMixin:
|
|||||||
self.session.cycle_key()
|
self.session.cycle_key()
|
||||||
self.assertCountEqual(self.session.items(), prev_data)
|
self.assertCountEqual(self.session.items(), prev_data)
|
||||||
|
|
||||||
|
async def test_cycle_with_no_session_cache_async(self):
|
||||||
|
await self.session.aset("a", "c")
|
||||||
|
await self.session.aset("b", "d")
|
||||||
|
await self.session.asave()
|
||||||
|
prev_data = await self.session.aitems()
|
||||||
|
self.session = self.backend(self.session.session_key)
|
||||||
|
self.assertIs(hasattr(self.session, "_session_cache"), False)
|
||||||
|
await self.session.acycle_key()
|
||||||
|
self.assertCountEqual(await self.session.aitems(), prev_data)
|
||||||
|
|
||||||
def test_save_doesnt_clear_data(self):
|
def test_save_doesnt_clear_data(self):
|
||||||
self.session["a"] = "b"
|
self.session["a"] = "b"
|
||||||
self.session.save()
|
self.session.save()
|
||||||
self.assertEqual(self.session["a"], "b")
|
self.assertEqual(self.session["a"], "b")
|
||||||
|
|
||||||
|
async def test_save_doesnt_clear_data_async(self):
|
||||||
|
await self.session.aset("a", "b")
|
||||||
|
await self.session.asave()
|
||||||
|
self.assertEqual(await self.session.aget("a"), "b")
|
||||||
|
|
||||||
def test_invalid_key(self):
|
def test_invalid_key(self):
|
||||||
# Submitting an invalid session key (either by guessing, or if the db has
|
# Submitting an invalid session key (either by guessing, or if the db has
|
||||||
# removed the key) results in a new key being generated.
|
# removed the key) results in a new key being generated.
|
||||||
@ -209,6 +339,20 @@ class SessionTestsMixin:
|
|||||||
# session key; make sure that entry is manually deleted
|
# session key; make sure that entry is manually deleted
|
||||||
session.delete("1")
|
session.delete("1")
|
||||||
|
|
||||||
|
async def test_invalid_key_async(self):
|
||||||
|
# Submitting an invalid session key (either by guessing, or if the db has
|
||||||
|
# removed the key) results in a new key being generated.
|
||||||
|
try:
|
||||||
|
session = self.backend("1")
|
||||||
|
await session.asave()
|
||||||
|
self.assertNotEqual(session.session_key, "1")
|
||||||
|
self.assertIsNone(await session.aget("cat"))
|
||||||
|
await session.adelete()
|
||||||
|
finally:
|
||||||
|
# Some backends leave a stale cache entry for the invalid
|
||||||
|
# session key; make sure that entry is manually deleted
|
||||||
|
await session.adelete("1")
|
||||||
|
|
||||||
def test_session_key_empty_string_invalid(self):
|
def test_session_key_empty_string_invalid(self):
|
||||||
"""Falsey values (Such as an empty string) are rejected."""
|
"""Falsey values (Such as an empty string) are rejected."""
|
||||||
self.session._session_key = ""
|
self.session._session_key = ""
|
||||||
@ -241,6 +385,18 @@ class SessionTestsMixin:
|
|||||||
self.session.set_expiry(0)
|
self.session.set_expiry(0)
|
||||||
self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
|
self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
|
||||||
|
|
||||||
|
async def test_default_expiry_async(self):
|
||||||
|
# A normal session has a max age equal to settings.
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.aget_expiry_age(), settings.SESSION_COOKIE_AGE
|
||||||
|
)
|
||||||
|
# So does a custom session with an idle expiration time of 0 (but it'll
|
||||||
|
# expire at browser close).
|
||||||
|
await self.session.aset_expiry(0)
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.aget_expiry_age(), settings.SESSION_COOKIE_AGE
|
||||||
|
)
|
||||||
|
|
||||||
def test_custom_expiry_seconds(self):
|
def test_custom_expiry_seconds(self):
|
||||||
modification = timezone.now()
|
modification = timezone.now()
|
||||||
|
|
||||||
@ -252,6 +408,17 @@ class SessionTestsMixin:
|
|||||||
age = self.session.get_expiry_age(modification=modification)
|
age = self.session.get_expiry_age(modification=modification)
|
||||||
self.assertEqual(age, 10)
|
self.assertEqual(age, 10)
|
||||||
|
|
||||||
|
async def test_custom_expiry_seconds_async(self):
|
||||||
|
modification = timezone.now()
|
||||||
|
|
||||||
|
await self.session.aset_expiry(10)
|
||||||
|
|
||||||
|
date = await self.session.aget_expiry_date(modification=modification)
|
||||||
|
self.assertEqual(date, modification + timedelta(seconds=10))
|
||||||
|
|
||||||
|
age = await self.session.aget_expiry_age(modification=modification)
|
||||||
|
self.assertEqual(age, 10)
|
||||||
|
|
||||||
def test_custom_expiry_timedelta(self):
|
def test_custom_expiry_timedelta(self):
|
||||||
modification = timezone.now()
|
modification = timezone.now()
|
||||||
|
|
||||||
@ -269,6 +436,23 @@ class SessionTestsMixin:
|
|||||||
age = self.session.get_expiry_age(modification=modification)
|
age = self.session.get_expiry_age(modification=modification)
|
||||||
self.assertEqual(age, 10)
|
self.assertEqual(age, 10)
|
||||||
|
|
||||||
|
async def test_custom_expiry_timedelta_async(self):
|
||||||
|
modification = timezone.now()
|
||||||
|
|
||||||
|
# Mock timezone.now, because set_expiry calls it on this code path.
|
||||||
|
original_now = timezone.now
|
||||||
|
try:
|
||||||
|
timezone.now = lambda: modification
|
||||||
|
await self.session.aset_expiry(timedelta(seconds=10))
|
||||||
|
finally:
|
||||||
|
timezone.now = original_now
|
||||||
|
|
||||||
|
date = await self.session.aget_expiry_date(modification=modification)
|
||||||
|
self.assertEqual(date, modification + timedelta(seconds=10))
|
||||||
|
|
||||||
|
age = await self.session.aget_expiry_age(modification=modification)
|
||||||
|
self.assertEqual(age, 10)
|
||||||
|
|
||||||
def test_custom_expiry_datetime(self):
|
def test_custom_expiry_datetime(self):
|
||||||
modification = timezone.now()
|
modification = timezone.now()
|
||||||
|
|
||||||
@ -280,12 +464,31 @@ class SessionTestsMixin:
|
|||||||
age = self.session.get_expiry_age(modification=modification)
|
age = self.session.get_expiry_age(modification=modification)
|
||||||
self.assertEqual(age, 10)
|
self.assertEqual(age, 10)
|
||||||
|
|
||||||
|
async def test_custom_expiry_datetime_async(self):
|
||||||
|
modification = timezone.now()
|
||||||
|
|
||||||
|
await self.session.aset_expiry(modification + timedelta(seconds=10))
|
||||||
|
|
||||||
|
date = await self.session.aget_expiry_date(modification=modification)
|
||||||
|
self.assertEqual(date, modification + timedelta(seconds=10))
|
||||||
|
|
||||||
|
age = await self.session.aget_expiry_age(modification=modification)
|
||||||
|
self.assertEqual(age, 10)
|
||||||
|
|
||||||
def test_custom_expiry_reset(self):
|
def test_custom_expiry_reset(self):
|
||||||
self.session.set_expiry(None)
|
self.session.set_expiry(None)
|
||||||
self.session.set_expiry(10)
|
self.session.set_expiry(10)
|
||||||
self.session.set_expiry(None)
|
self.session.set_expiry(None)
|
||||||
self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
|
self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
|
||||||
|
|
||||||
|
async def test_custom_expiry_reset_async(self):
|
||||||
|
await self.session.aset_expiry(None)
|
||||||
|
await self.session.aset_expiry(10)
|
||||||
|
await self.session.aset_expiry(None)
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.aget_expiry_age(), settings.SESSION_COOKIE_AGE
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_expire_at_browser_close(self):
|
def test_get_expire_at_browser_close(self):
|
||||||
# Tests get_expire_at_browser_close with different settings and different
|
# Tests get_expire_at_browser_close with different settings and different
|
||||||
# set_expiry calls
|
# set_expiry calls
|
||||||
@ -309,6 +512,29 @@ class SessionTestsMixin:
|
|||||||
self.session.set_expiry(None)
|
self.session.set_expiry(None)
|
||||||
self.assertIs(self.session.get_expire_at_browser_close(), True)
|
self.assertIs(self.session.get_expire_at_browser_close(), True)
|
||||||
|
|
||||||
|
async def test_get_expire_at_browser_close_async(self):
|
||||||
|
# Tests get_expire_at_browser_close with different settings and different
|
||||||
|
# set_expiry calls
|
||||||
|
with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=False):
|
||||||
|
await self.session.aset_expiry(10)
|
||||||
|
self.assertIs(await self.session.aget_expire_at_browser_close(), False)
|
||||||
|
|
||||||
|
await self.session.aset_expiry(0)
|
||||||
|
self.assertIs(await self.session.aget_expire_at_browser_close(), True)
|
||||||
|
|
||||||
|
await self.session.aset_expiry(None)
|
||||||
|
self.assertIs(await self.session.aget_expire_at_browser_close(), False)
|
||||||
|
|
||||||
|
with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=True):
|
||||||
|
await self.session.aset_expiry(10)
|
||||||
|
self.assertIs(await self.session.aget_expire_at_browser_close(), False)
|
||||||
|
|
||||||
|
await self.session.aset_expiry(0)
|
||||||
|
self.assertIs(await self.session.aget_expire_at_browser_close(), True)
|
||||||
|
|
||||||
|
await self.session.aset_expiry(None)
|
||||||
|
self.assertIs(await self.session.aget_expire_at_browser_close(), True)
|
||||||
|
|
||||||
def test_decode(self):
|
def test_decode(self):
|
||||||
# Ensure we can decode what we encode
|
# Ensure we can decode what we encode
|
||||||
data = {"a test key": "a test value"}
|
data = {"a test key": "a test value"}
|
||||||
@ -350,6 +576,22 @@ class SessionTestsMixin:
|
|||||||
self.session.delete(old_session_key)
|
self.session.delete(old_session_key)
|
||||||
self.session.delete(new_session_key)
|
self.session.delete(new_session_key)
|
||||||
|
|
||||||
|
async def test_actual_expiry_async(self):
|
||||||
|
old_session_key = None
|
||||||
|
new_session_key = None
|
||||||
|
try:
|
||||||
|
await self.session.aset("foo", "bar")
|
||||||
|
await self.session.aset_expiry(-timedelta(seconds=10))
|
||||||
|
await self.session.asave()
|
||||||
|
old_session_key = self.session.session_key
|
||||||
|
# With an expiry date in the past, the session expires instantly.
|
||||||
|
new_session = self.backend(self.session.session_key)
|
||||||
|
new_session_key = new_session.session_key
|
||||||
|
self.assertIs(await new_session.ahas_key("foo"), False)
|
||||||
|
finally:
|
||||||
|
await self.session.adelete(old_session_key)
|
||||||
|
await self.session.adelete(new_session_key)
|
||||||
|
|
||||||
def test_session_load_does_not_create_record(self):
|
def test_session_load_does_not_create_record(self):
|
||||||
"""
|
"""
|
||||||
Loading an unknown session key does not create a session record.
|
Loading an unknown session key does not create a session record.
|
||||||
@ -364,6 +606,15 @@ class SessionTestsMixin:
|
|||||||
# provided unknown key was cycled, not reused
|
# provided unknown key was cycled, not reused
|
||||||
self.assertNotEqual(session.session_key, "someunknownkey")
|
self.assertNotEqual(session.session_key, "someunknownkey")
|
||||||
|
|
||||||
|
async def test_session_load_does_not_create_record_async(self):
|
||||||
|
session = self.backend("someunknownkey")
|
||||||
|
await session.aload()
|
||||||
|
|
||||||
|
self.assertIsNone(session.session_key)
|
||||||
|
self.assertIs(await session.aexists(session.session_key), False)
|
||||||
|
# Provided unknown key was cycled, not reused.
|
||||||
|
self.assertNotEqual(session.session_key, "someunknownkey")
|
||||||
|
|
||||||
def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self):
|
def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self):
|
||||||
"""
|
"""
|
||||||
Sessions shouldn't be resurrected by a concurrent request.
|
Sessions shouldn't be resurrected by a concurrent request.
|
||||||
@ -386,6 +637,28 @@ class SessionTestsMixin:
|
|||||||
|
|
||||||
self.assertEqual(s1.load(), {})
|
self.assertEqual(s1.load(), {})
|
||||||
|
|
||||||
|
async def test_session_asave_does_not_resurrect_session_logged_out_in_other_context(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
"""Sessions shouldn't be resurrected by a concurrent request."""
|
||||||
|
# Create new session.
|
||||||
|
s1 = self.backend()
|
||||||
|
await s1.aset("test_data", "value1")
|
||||||
|
await s1.asave(must_create=True)
|
||||||
|
|
||||||
|
# Logout in another context.
|
||||||
|
s2 = self.backend(s1.session_key)
|
||||||
|
await s2.adelete()
|
||||||
|
|
||||||
|
# Modify session in first context.
|
||||||
|
await s1.aset("test_data", "value2")
|
||||||
|
with self.assertRaises(UpdateError):
|
||||||
|
# This should throw an exception as the session is deleted, not
|
||||||
|
# resurrect the session.
|
||||||
|
await s1.asave()
|
||||||
|
|
||||||
|
self.assertEqual(await s1.aload(), {})
|
||||||
|
|
||||||
|
|
||||||
class DatabaseSessionTests(SessionTestsMixin, TestCase):
|
class DatabaseSessionTests(SessionTestsMixin, TestCase):
|
||||||
backend = DatabaseSession
|
backend = DatabaseSession
|
||||||
@ -456,6 +729,25 @@ class DatabaseSessionTests(SessionTestsMixin, TestCase):
|
|||||||
# ... and one is deleted.
|
# ... and one is deleted.
|
||||||
self.assertEqual(1, self.model.objects.count())
|
self.assertEqual(1, self.model.objects.count())
|
||||||
|
|
||||||
|
async def test_aclear_expired(self):
|
||||||
|
self.assertEqual(await self.model.objects.acount(), 0)
|
||||||
|
|
||||||
|
# Object in the future.
|
||||||
|
await self.session.aset("key", "value")
|
||||||
|
await self.session.aset_expiry(3600)
|
||||||
|
await self.session.asave()
|
||||||
|
# Object in the past.
|
||||||
|
other_session = self.backend()
|
||||||
|
await other_session.aset("key", "value")
|
||||||
|
await other_session.aset_expiry(-3600)
|
||||||
|
await other_session.asave()
|
||||||
|
|
||||||
|
# Two sessions are in the database before clearing expired.
|
||||||
|
self.assertEqual(await self.model.objects.acount(), 2)
|
||||||
|
await self.session.aclear_expired()
|
||||||
|
await other_session.aclear_expired()
|
||||||
|
self.assertEqual(await self.model.objects.acount(), 1)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(USE_TZ=True)
|
@override_settings(USE_TZ=True)
|
||||||
class DatabaseSessionWithTimeZoneTests(DatabaseSessionTests):
|
class DatabaseSessionWithTimeZoneTests(DatabaseSessionTests):
|
||||||
@ -491,11 +783,28 @@ class CustomDatabaseSessionTests(DatabaseSessionTests):
|
|||||||
self.session.set_expiry(None)
|
self.session.set_expiry(None)
|
||||||
self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
|
self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
|
||||||
|
|
||||||
|
async def test_custom_expiry_reset_async(self):
|
||||||
|
await self.session.aset_expiry(None)
|
||||||
|
await self.session.aset_expiry(10)
|
||||||
|
await self.session.aset_expiry(None)
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.aget_expiry_age(), self.custom_session_cookie_age
|
||||||
|
)
|
||||||
|
|
||||||
def test_default_expiry(self):
|
def test_default_expiry(self):
|
||||||
self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
|
self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
|
||||||
self.session.set_expiry(0)
|
self.session.set_expiry(0)
|
||||||
self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
|
self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
|
||||||
|
|
||||||
|
async def test_default_expiry_async(self):
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.aget_expiry_age(), self.custom_session_cookie_age
|
||||||
|
)
|
||||||
|
await self.session.aset_expiry(0)
|
||||||
|
self.assertEqual(
|
||||||
|
await self.session.aget_expiry_age(), self.custom_session_cookie_age
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CacheDBSessionTests(SessionTestsMixin, TestCase):
|
class CacheDBSessionTests(SessionTestsMixin, TestCase):
|
||||||
backend = CacheDBSession
|
backend = CacheDBSession
|
||||||
@ -533,6 +842,22 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
|
|||||||
self.assertEqual(log.message, f"Error saving to cache ({session._cache})")
|
self.assertEqual(log.message, f"Error saving to cache ({session._cache})")
|
||||||
self.assertEqual(str(log.exc_info[1]), "Faked exception saving to cache")
|
self.assertEqual(str(log.exc_info[1]), "Faked exception saving to cache")
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
CACHES={"default": {"BACKEND": "cache.failing_cache.CacheClass"}}
|
||||||
|
)
|
||||||
|
async def test_cache_async_set_failure_non_fatal(self):
|
||||||
|
"""Failing to write to the cache does not raise errors."""
|
||||||
|
session = self.backend()
|
||||||
|
await session.aset("key", "val")
|
||||||
|
|
||||||
|
with self.assertLogs("django.contrib.sessions", "ERROR") as cm:
|
||||||
|
await session.asave()
|
||||||
|
|
||||||
|
# A proper ERROR log message was recorded.
|
||||||
|
log = cm.records[-1]
|
||||||
|
self.assertEqual(log.message, f"Error saving to cache ({session._cache})")
|
||||||
|
self.assertEqual(str(log.exc_info[1]), "Faked exception saving to cache")
|
||||||
|
|
||||||
|
|
||||||
@override_settings(USE_TZ=True)
|
@override_settings(USE_TZ=True)
|
||||||
class CacheDBSessionWithTimeZoneTests(CacheDBSessionTests):
|
class CacheDBSessionWithTimeZoneTests(CacheDBSessionTests):
|
||||||
@ -673,6 +998,12 @@ class CacheSessionTests(SessionTestsMixin, SimpleTestCase):
|
|||||||
self.session.save()
|
self.session.save()
|
||||||
self.assertIsNotNone(caches["default"].get(self.session.cache_key))
|
self.assertIsNotNone(caches["default"].get(self.session.cache_key))
|
||||||
|
|
||||||
|
async def test_create_and_save_async(self):
|
||||||
|
self.session = self.backend()
|
||||||
|
await self.session.acreate()
|
||||||
|
await self.session.asave()
|
||||||
|
self.assertIsNotNone(caches["default"].get(await self.session.acache_key()))
|
||||||
|
|
||||||
|
|
||||||
class SessionMiddlewareTests(TestCase):
|
class SessionMiddlewareTests(TestCase):
|
||||||
request_factory = RequestFactory()
|
request_factory = RequestFactory()
|
||||||
@ -899,6 +1230,9 @@ class CookieSessionTests(SessionTestsMixin, SimpleTestCase):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def test_save_async(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_cycle(self):
|
def test_cycle(self):
|
||||||
"""
|
"""
|
||||||
This test tested cycle_key() which would create a new session
|
This test tested cycle_key() which would create a new session
|
||||||
@ -908,11 +1242,17 @@ class CookieSessionTests(SessionTestsMixin, SimpleTestCase):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def test_cycle_async(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
def test_actual_expiry(self):
|
def test_actual_expiry(self):
|
||||||
# The cookie backend doesn't handle non-default expiry dates, see #19201
|
# The cookie backend doesn't handle non-default expiry dates, see #19201
|
||||||
super().test_actual_expiry()
|
super().test_actual_expiry()
|
||||||
|
|
||||||
|
async def test_actual_expiry_async(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_unpickling_exception(self):
|
def test_unpickling_exception(self):
|
||||||
# signed_cookies backend should handle unpickle exceptions gracefully
|
# signed_cookies backend should handle unpickle exceptions gracefully
|
||||||
# by creating a new session
|
# by creating a new session
|
||||||
@ -927,12 +1267,26 @@ class CookieSessionTests(SessionTestsMixin, SimpleTestCase):
|
|||||||
def test_session_load_does_not_create_record(self):
|
def test_session_load_does_not_create_record(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
"Cookie backend doesn't have an external store to create records in."
|
||||||
|
)
|
||||||
|
async def test_session_load_does_not_create_record_async(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@unittest.skip(
|
@unittest.skip(
|
||||||
"CookieSession is stored in the client and there is no way to query it."
|
"CookieSession is stored in the client and there is no way to query it."
|
||||||
)
|
)
|
||||||
def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self):
|
def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skip(
|
||||||
|
"CookieSession is stored in the client and there is no way to query it."
|
||||||
|
)
|
||||||
|
async def test_session_asave_does_not_resurrect_session_logged_out_in_other_context(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ClearSessionsCommandTests(SimpleTestCase):
|
class ClearSessionsCommandTests(SimpleTestCase):
|
||||||
def test_clearsessions_unsupported(self):
|
def test_clearsessions_unsupported(self):
|
||||||
@ -956,26 +1310,51 @@ class SessionBaseTests(SimpleTestCase):
|
|||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
self.session.create()
|
self.session.create()
|
||||||
|
|
||||||
|
async def test_acreate(self):
|
||||||
|
msg = self.not_implemented_msg % "a create"
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
await self.session.acreate()
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
msg = self.not_implemented_msg % "a delete"
|
msg = self.not_implemented_msg % "a delete"
|
||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
self.session.delete()
|
self.session.delete()
|
||||||
|
|
||||||
|
async def test_adelete(self):
|
||||||
|
msg = self.not_implemented_msg % "a delete"
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
await self.session.adelete()
|
||||||
|
|
||||||
def test_exists(self):
|
def test_exists(self):
|
||||||
msg = self.not_implemented_msg % "an exists"
|
msg = self.not_implemented_msg % "an exists"
|
||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
self.session.exists(None)
|
self.session.exists(None)
|
||||||
|
|
||||||
|
async def test_aexists(self):
|
||||||
|
msg = self.not_implemented_msg % "an exists"
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
await self.session.aexists(None)
|
||||||
|
|
||||||
def test_load(self):
|
def test_load(self):
|
||||||
msg = self.not_implemented_msg % "a load"
|
msg = self.not_implemented_msg % "a load"
|
||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
self.session.load()
|
self.session.load()
|
||||||
|
|
||||||
|
async def test_aload(self):
|
||||||
|
msg = self.not_implemented_msg % "a load"
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
await self.session.aload()
|
||||||
|
|
||||||
def test_save(self):
|
def test_save(self):
|
||||||
msg = self.not_implemented_msg % "a save"
|
msg = self.not_implemented_msg % "a save"
|
||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
self.session.save()
|
self.session.save()
|
||||||
|
|
||||||
|
async def test_asave(self):
|
||||||
|
msg = self.not_implemented_msg % "a save"
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, msg):
|
||||||
|
await self.session.asave()
|
||||||
|
|
||||||
def test_test_cookie(self):
|
def test_test_cookie(self):
|
||||||
self.assertIs(self.session.has_key(self.session.TEST_COOKIE_NAME), False)
|
self.assertIs(self.session.has_key(self.session.TEST_COOKIE_NAME), False)
|
||||||
self.session.set_test_cookie()
|
self.session.set_test_cookie()
|
||||||
@ -983,5 +1362,12 @@ class SessionBaseTests(SimpleTestCase):
|
|||||||
self.session.delete_test_cookie()
|
self.session.delete_test_cookie()
|
||||||
self.assertIs(self.session.has_key(self.session.TEST_COOKIE_NAME), False)
|
self.assertIs(self.session.has_key(self.session.TEST_COOKIE_NAME), False)
|
||||||
|
|
||||||
|
async def test_atest_cookie(self):
|
||||||
|
self.assertIs(await self.session.ahas_key(self.session.TEST_COOKIE_NAME), False)
|
||||||
|
await self.session.aset_test_cookie()
|
||||||
|
self.assertIs(await self.session.atest_cookie_worked(), True)
|
||||||
|
await self.session.adelete_test_cookie()
|
||||||
|
self.assertIs(await self.session.ahas_key(self.session.TEST_COOKIE_NAME), False)
|
||||||
|
|
||||||
def test_is_empty(self):
|
def test_is_empty(self):
|
||||||
self.assertIs(self.session.is_empty(), True)
|
self.assertIs(self.session.is_empty(), True)
|
||||||
|
Loading…
Reference in New Issue
Block a user