Initial commit. Basic models mostly done.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
13
venv/lib/python3.8/site-packages/django/core/asgi.py
Normal file
13
venv/lib/python3.8/site-packages/django/core/asgi.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import django
|
||||
from django.core.handlers.asgi import ASGIHandler
|
||||
|
||||
|
||||
def get_asgi_application():
|
||||
"""
|
||||
The public interface to Django's ASGI support. Return an ASGI 3 callable.
|
||||
|
||||
Avoids making django.core.handlers.ASGIHandler a public API, in case the
|
||||
internal implementation changes or moves in the future.
|
||||
"""
|
||||
django.setup(set_prefix=False)
|
||||
return ASGIHandler()
|
||||
124
venv/lib/python3.8/site-packages/django/core/cache/__init__.py
vendored
Normal file
124
venv/lib/python3.8/site-packages/django/core/cache/__init__.py
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
Caching framework.
|
||||
|
||||
This package defines set of cache backends that all conform to a simple API.
|
||||
In a nutshell, a cache is a set of values -- which can be any object that
|
||||
may be pickled -- identified by string keys. For the complete API, see
|
||||
the abstract BaseCache class in django.core.cache.backends.base.
|
||||
|
||||
Client code should use the `cache` variable defined here to access the default
|
||||
cache backend and look up non-default cache backends in the `caches` dict-like
|
||||
object.
|
||||
|
||||
See docs/topics/cache.txt for information on the public API.
|
||||
"""
|
||||
from asgiref.local import Local
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.cache.backends.base import (
|
||||
BaseCache, CacheKeyWarning, InvalidCacheBackendError,
|
||||
)
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
__all__ = [
|
||||
'cache', 'caches', 'DEFAULT_CACHE_ALIAS', 'InvalidCacheBackendError',
|
||||
'CacheKeyWarning', 'BaseCache',
|
||||
]
|
||||
|
||||
DEFAULT_CACHE_ALIAS = 'default'
|
||||
|
||||
|
||||
def _create_cache(backend, **kwargs):
|
||||
try:
|
||||
# Try to get the CACHES entry for the given backend name first
|
||||
try:
|
||||
conf = settings.CACHES[backend]
|
||||
except KeyError:
|
||||
try:
|
||||
# Trying to import the given backend, in case it's a dotted path
|
||||
import_string(backend)
|
||||
except ImportError as e:
|
||||
raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
|
||||
backend, e))
|
||||
location = kwargs.pop('LOCATION', '')
|
||||
params = kwargs
|
||||
else:
|
||||
params = {**conf, **kwargs}
|
||||
backend = params.pop('BACKEND')
|
||||
location = params.pop('LOCATION', '')
|
||||
backend_cls = import_string(backend)
|
||||
except ImportError as e:
|
||||
raise InvalidCacheBackendError(
|
||||
"Could not find backend '%s': %s" % (backend, e))
|
||||
return backend_cls(location, params)
|
||||
|
||||
|
||||
class CacheHandler:
|
||||
"""
|
||||
A Cache Handler to manage access to Cache instances.
|
||||
|
||||
Ensure only one instance of each alias exists per thread.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._caches = Local()
|
||||
|
||||
def __getitem__(self, alias):
|
||||
try:
|
||||
return self._caches.caches[alias]
|
||||
except AttributeError:
|
||||
self._caches.caches = {}
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if alias not in settings.CACHES:
|
||||
raise InvalidCacheBackendError(
|
||||
"Could not find config for '%s' in settings.CACHES" % alias
|
||||
)
|
||||
|
||||
cache = _create_cache(alias)
|
||||
self._caches.caches[alias] = cache
|
||||
return cache
|
||||
|
||||
def all(self):
|
||||
return getattr(self._caches, 'caches', {}).values()
|
||||
|
||||
|
||||
caches = CacheHandler()
|
||||
|
||||
|
||||
class DefaultCacheProxy:
|
||||
"""
|
||||
Proxy access to the default Cache object's attributes.
|
||||
|
||||
This allows the legacy `cache` object to be thread-safe using the new
|
||||
``caches`` API.
|
||||
"""
|
||||
def __getattr__(self, name):
|
||||
return getattr(caches[DEFAULT_CACHE_ALIAS], name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
return setattr(caches[DEFAULT_CACHE_ALIAS], name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
return delattr(caches[DEFAULT_CACHE_ALIAS], name)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in caches[DEFAULT_CACHE_ALIAS]
|
||||
|
||||
def __eq__(self, other):
|
||||
return caches[DEFAULT_CACHE_ALIAS] == other
|
||||
|
||||
|
||||
cache = DefaultCacheProxy()
|
||||
|
||||
|
||||
def close_caches(**kwargs):
|
||||
# Some caches -- python-memcached in particular -- need to do a cleanup at the
|
||||
# end of a request cycle. If not implemented in a particular backend
|
||||
# cache.close is a no-op
|
||||
for cache in caches.all():
|
||||
cache.close()
|
||||
|
||||
|
||||
signals.request_finished.connect(close_caches)
|
||||
BIN
venv/lib/python3.8/site-packages/django/core/cache/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/__pycache__/utils.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/__pycache__/utils.cpython-38.pyc
vendored
Normal file
Binary file not shown.
0
venv/lib/python3.8/site-packages/django/core/cache/backends/__init__.py
vendored
Normal file
0
venv/lib/python3.8/site-packages/django/core/cache/backends/__init__.py
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/base.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/base.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/db.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/db.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/dummy.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/dummy.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/filebased.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/filebased.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/locmem.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/locmem.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/memcached.cpython-38.pyc
vendored
Normal file
BIN
venv/lib/python3.8/site-packages/django/core/cache/backends/__pycache__/memcached.cpython-38.pyc
vendored
Normal file
Binary file not shown.
282
venv/lib/python3.8/site-packages/django/core/cache/backends/base.py
vendored
Normal file
282
venv/lib/python3.8/site-packages/django/core/cache/backends/base.py
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
"Base Cache class."
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
|
||||
class InvalidCacheBackendError(ImproperlyConfigured):
|
||||
pass
|
||||
|
||||
|
||||
class CacheKeyWarning(RuntimeWarning):
|
||||
pass
|
||||
|
||||
|
||||
# Stub class to ensure not passing in a `timeout` argument results in
|
||||
# the default timeout
|
||||
DEFAULT_TIMEOUT = object()
|
||||
|
||||
# Memcached does not accept keys longer than this.
|
||||
MEMCACHE_MAX_KEY_LENGTH = 250
|
||||
|
||||
|
||||
def default_key_func(key, key_prefix, version):
|
||||
"""
|
||||
Default function to generate keys.
|
||||
|
||||
Construct the key used by all other methods. By default, prepend
|
||||
the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
|
||||
function with custom key making behavior.
|
||||
"""
|
||||
return '%s:%s:%s' % (key_prefix, version, key)
|
||||
|
||||
|
||||
def get_key_func(key_func):
|
||||
"""
|
||||
Function to decide which key function to use.
|
||||
|
||||
Default to ``default_key_func``.
|
||||
"""
|
||||
if key_func is not None:
|
||||
if callable(key_func):
|
||||
return key_func
|
||||
else:
|
||||
return import_string(key_func)
|
||||
return default_key_func
|
||||
|
||||
|
||||
class BaseCache:
|
||||
def __init__(self, params):
|
||||
timeout = params.get('timeout', params.get('TIMEOUT', 300))
|
||||
if timeout is not None:
|
||||
try:
|
||||
timeout = int(timeout)
|
||||
except (ValueError, TypeError):
|
||||
timeout = 300
|
||||
self.default_timeout = timeout
|
||||
|
||||
options = params.get('OPTIONS', {})
|
||||
max_entries = params.get('max_entries', options.get('MAX_ENTRIES', 300))
|
||||
try:
|
||||
self._max_entries = int(max_entries)
|
||||
except (ValueError, TypeError):
|
||||
self._max_entries = 300
|
||||
|
||||
cull_frequency = params.get('cull_frequency', options.get('CULL_FREQUENCY', 3))
|
||||
try:
|
||||
self._cull_frequency = int(cull_frequency)
|
||||
except (ValueError, TypeError):
|
||||
self._cull_frequency = 3
|
||||
|
||||
self.key_prefix = params.get('KEY_PREFIX', '')
|
||||
self.version = params.get('VERSION', 1)
|
||||
self.key_func = get_key_func(params.get('KEY_FUNCTION'))
|
||||
|
||||
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
|
||||
"""
|
||||
Return the timeout value usable by this backend based upon the provided
|
||||
timeout.
|
||||
"""
|
||||
if timeout == DEFAULT_TIMEOUT:
|
||||
timeout = self.default_timeout
|
||||
elif timeout == 0:
|
||||
# ticket 21147 - avoid time.time() related precision issues
|
||||
timeout = -1
|
||||
return None if timeout is None else time.time() + timeout
|
||||
|
||||
def make_key(self, key, version=None):
|
||||
"""
|
||||
Construct the key used by all other methods. By default, use the
|
||||
key_func to generate a key (which, by default, prepends the
|
||||
`key_prefix' and 'version'). A different key function can be provided
|
||||
at the time of cache construction; alternatively, you can subclass the
|
||||
cache backend to provide custom key making behavior.
|
||||
"""
|
||||
if version is None:
|
||||
version = self.version
|
||||
|
||||
return self.key_func(key, self.key_prefix, version)
|
||||
|
||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
"""
|
||||
Set a value in the cache if the key does not already exist. If
|
||||
timeout is given, use that timeout for the key; otherwise use the
|
||||
default cache timeout.
|
||||
|
||||
Return True if the value was stored, False otherwise.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseCache must provide an add() method')
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
"""
|
||||
Fetch a given key from the cache. If the key does not exist, return
|
||||
default, which itself defaults to None.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseCache must provide a get() method')
|
||||
|
||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
"""
|
||||
Set a value in the cache. If timeout is given, use that timeout for the
|
||||
key; otherwise use the default cache timeout.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseCache must provide a set() method')
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
"""
|
||||
Update the key's expiry time using timeout. Return True if successful
|
||||
or False if the key does not exist.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseCache must provide a touch() method')
|
||||
|
||||
def delete(self, key, version=None):
|
||||
"""
|
||||
Delete a key from the cache, failing silently.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseCache must provide a delete() method')
|
||||
|
||||
def get_many(self, keys, version=None):
|
||||
"""
|
||||
Fetch a bunch of keys from the cache. For certain backends (memcached,
|
||||
pgsql) this can be *much* faster when fetching multiple values.
|
||||
|
||||
Return a dict mapping each key in keys to its value. If the given
|
||||
key is missing, it will be missing from the response dict.
|
||||
"""
|
||||
d = {}
|
||||
for k in keys:
|
||||
val = self.get(k, version=version)
|
||||
if val is not None:
|
||||
d[k] = val
|
||||
return d
|
||||
|
||||
def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
"""
|
||||
Fetch a given key from the cache. If the key does not exist,
|
||||
add the key and set it to the default value. The default value can
|
||||
also be any callable. If timeout is given, use that timeout for the
|
||||
key; otherwise use the default cache timeout.
|
||||
|
||||
Return the value of the key stored or retrieved.
|
||||
"""
|
||||
val = self.get(key, version=version)
|
||||
if val is None:
|
||||
if callable(default):
|
||||
default = default()
|
||||
if default is not None:
|
||||
self.add(key, default, timeout=timeout, version=version)
|
||||
# Fetch the value again to avoid a race condition if another
|
||||
# caller added a value between the first get() and the add()
|
||||
# above.
|
||||
return self.get(key, default, version=version)
|
||||
return val
|
||||
|
||||
def has_key(self, key, version=None):
|
||||
"""
|
||||
Return True if the key is in the cache and has not expired.
|
||||
"""
|
||||
return self.get(key, version=version) is not None
|
||||
|
||||
def incr(self, key, delta=1, version=None):
|
||||
"""
|
||||
Add delta to value in the cache. If the key does not exist, raise a
|
||||
ValueError exception.
|
||||
"""
|
||||
value = self.get(key, version=version)
|
||||
if value is None:
|
||||
raise ValueError("Key '%s' not found" % key)
|
||||
new_value = value + delta
|
||||
self.set(key, new_value, version=version)
|
||||
return new_value
|
||||
|
||||
def decr(self, key, delta=1, version=None):
|
||||
"""
|
||||
Subtract delta from value in the cache. If the key does not exist, raise
|
||||
a ValueError exception.
|
||||
"""
|
||||
return self.incr(key, -delta, version=version)
|
||||
|
||||
def __contains__(self, key):
|
||||
"""
|
||||
Return True if the key is in the cache and has not expired.
|
||||
"""
|
||||
# This is a separate method, rather than just a copy of has_key(),
|
||||
# so that it always has the same functionality as has_key(), even
|
||||
# if a subclass overrides it.
|
||||
return self.has_key(key)
|
||||
|
||||
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
"""
|
||||
Set a bunch of values in the cache at once from a dict of key/value
|
||||
pairs. For certain backends (memcached), this is much more efficient
|
||||
than calling set() multiple times.
|
||||
|
||||
If timeout is given, use that timeout for the key; otherwise use the
|
||||
default cache timeout.
|
||||
|
||||
On backends that support it, return a list of keys that failed
|
||||
insertion, or an empty list if all keys were inserted successfully.
|
||||
"""
|
||||
for key, value in data.items():
|
||||
self.set(key, value, timeout=timeout, version=version)
|
||||
return []
|
||||
|
||||
def delete_many(self, keys, version=None):
|
||||
"""
|
||||
Delete a bunch of values in the cache at once. For certain backends
|
||||
(memcached), this is much more efficient than calling delete() multiple
|
||||
times.
|
||||
"""
|
||||
for key in keys:
|
||||
self.delete(key, version=version)
|
||||
|
||||
def clear(self):
|
||||
"""Remove *all* values from the cache at once."""
|
||||
raise NotImplementedError('subclasses of BaseCache must provide a clear() method')
|
||||
|
||||
def validate_key(self, key):
|
||||
"""
|
||||
Warn about keys that would not be portable to the memcached
|
||||
backend. This encourages (but does not force) writing backend-portable
|
||||
cache code.
|
||||
"""
|
||||
if len(key) > MEMCACHE_MAX_KEY_LENGTH:
|
||||
warnings.warn(
|
||||
'Cache key will cause errors if used with memcached: %r '
|
||||
'(longer than %s)' % (key, MEMCACHE_MAX_KEY_LENGTH), CacheKeyWarning
|
||||
)
|
||||
for char in key:
|
||||
if ord(char) < 33 or ord(char) == 127:
|
||||
warnings.warn(
|
||||
'Cache key contains characters that will cause errors if '
|
||||
'used with memcached: %r' % key, CacheKeyWarning
|
||||
)
|
||||
break
|
||||
|
||||
def incr_version(self, key, delta=1, version=None):
|
||||
"""
|
||||
Add delta to the cache version for the supplied key. Return the new
|
||||
version.
|
||||
"""
|
||||
if version is None:
|
||||
version = self.version
|
||||
|
||||
value = self.get(key, version=version)
|
||||
if value is None:
|
||||
raise ValueError("Key '%s' not found" % key)
|
||||
|
||||
self.set(key, value, version=version + delta)
|
||||
self.delete(key, version=version)
|
||||
return version + delta
|
||||
|
||||
def decr_version(self, key, delta=1, version=None):
|
||||
"""
|
||||
Subtract delta from the cache version for the supplied key. Return the
|
||||
new version.
|
||||
"""
|
||||
return self.incr_version(key, -delta, version)
|
||||
|
||||
def close(self, **kwargs):
|
||||
"""Close the cache connection"""
|
||||
pass
|
||||
277
venv/lib/python3.8/site-packages/django/core/cache/backends/db.py
vendored
Normal file
277
venv/lib/python3.8/site-packages/django/core/cache/backends/db.py
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
"Database cache backend."
|
||||
import base64
|
||||
import pickle
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||
from django.db import DatabaseError, connections, models, router, transaction
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Options:
|
||||
"""A class that will quack like a Django model _meta class.
|
||||
|
||||
This allows cache operations to be controlled by the router
|
||||
"""
|
||||
def __init__(self, table):
|
||||
self.db_table = table
|
||||
self.app_label = 'django_cache'
|
||||
self.model_name = 'cacheentry'
|
||||
self.verbose_name = 'cache entry'
|
||||
self.verbose_name_plural = 'cache entries'
|
||||
self.object_name = 'CacheEntry'
|
||||
self.abstract = False
|
||||
self.managed = True
|
||||
self.proxy = False
|
||||
self.swapped = False
|
||||
|
||||
|
||||
class BaseDatabaseCache(BaseCache):
|
||||
def __init__(self, table, params):
|
||||
super().__init__(params)
|
||||
self._table = table
|
||||
|
||||
class CacheEntry:
|
||||
_meta = Options(table)
|
||||
self.cache_model_class = CacheEntry
|
||||
|
||||
|
||||
class DatabaseCache(BaseDatabaseCache):
|
||||
|
||||
# This class uses cursors provided by the database connection. This means
|
||||
# it reads expiration values as aware or naive datetimes, depending on the
|
||||
# value of USE_TZ and whether the database supports time zones. The ORM's
|
||||
# conversion and adaptation infrastructure is then used to avoid comparing
|
||||
# aware and naive datetimes accidentally.
|
||||
|
||||
pickle_protocol = pickle.HIGHEST_PROTOCOL
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
return self.get_many([key], version).get(key, default)
|
||||
|
||||
def get_many(self, keys, version=None):
|
||||
if not keys:
|
||||
return {}
|
||||
|
||||
key_map = {}
|
||||
for key in keys:
|
||||
self.validate_key(key)
|
||||
key_map[self.make_key(key, version)] = key
|
||||
|
||||
db = router.db_for_read(self.cache_model_class)
|
||||
connection = connections[db]
|
||||
quote_name = connection.ops.quote_name
|
||||
table = quote_name(self._table)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
'SELECT %s, %s, %s FROM %s WHERE %s IN (%s)' % (
|
||||
quote_name('cache_key'),
|
||||
quote_name('value'),
|
||||
quote_name('expires'),
|
||||
table,
|
||||
quote_name('cache_key'),
|
||||
', '.join(['%s'] * len(key_map)),
|
||||
),
|
||||
list(key_map),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
result = {}
|
||||
expired_keys = []
|
||||
expression = models.Expression(output_field=models.DateTimeField())
|
||||
converters = (connection.ops.get_db_converters(expression) + expression.get_db_converters(connection))
|
||||
for key, value, expires in rows:
|
||||
for converter in converters:
|
||||
expires = converter(expires, expression, connection)
|
||||
if expires < timezone.now():
|
||||
expired_keys.append(key)
|
||||
else:
|
||||
value = connection.ops.process_clob(value)
|
||||
value = pickle.loads(base64.b64decode(value.encode()))
|
||||
result[key_map.get(key)] = value
|
||||
self._base_delete_many(expired_keys)
|
||||
return result
|
||||
|
||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
self._base_set('set', key, value, timeout)
|
||||
|
||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
return self._base_set('add', key, value, timeout)
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
return self._base_set('touch', key, None, timeout)
|
||||
|
||||
def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
|
||||
timeout = self.get_backend_timeout(timeout)
|
||||
db = router.db_for_write(self.cache_model_class)
|
||||
connection = connections[db]
|
||||
quote_name = connection.ops.quote_name
|
||||
table = quote_name(self._table)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT COUNT(*) FROM %s" % table)
|
||||
num = cursor.fetchone()[0]
|
||||
now = timezone.now()
|
||||
now = now.replace(microsecond=0)
|
||||
if timeout is None:
|
||||
exp = datetime.max
|
||||
elif settings.USE_TZ:
|
||||
exp = datetime.utcfromtimestamp(timeout)
|
||||
else:
|
||||
exp = datetime.fromtimestamp(timeout)
|
||||
exp = exp.replace(microsecond=0)
|
||||
if num > self._max_entries:
|
||||
self._cull(db, cursor, now)
|
||||
pickled = pickle.dumps(value, self.pickle_protocol)
|
||||
# The DB column is expecting a string, so make sure the value is a
|
||||
# string, not bytes. Refs #19274.
|
||||
b64encoded = base64.b64encode(pickled).decode('latin1')
|
||||
try:
|
||||
# Note: typecasting for datetimes is needed by some 3rd party
|
||||
# database backends. All core backends work without typecasting,
|
||||
# so be careful about changes here - test suite will NOT pick
|
||||
# regressions.
|
||||
with transaction.atomic(using=db):
|
||||
cursor.execute(
|
||||
'SELECT %s, %s FROM %s WHERE %s = %%s' % (
|
||||
quote_name('cache_key'),
|
||||
quote_name('expires'),
|
||||
table,
|
||||
quote_name('cache_key'),
|
||||
),
|
||||
[key]
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
current_expires = result[1]
|
||||
expression = models.Expression(output_field=models.DateTimeField())
|
||||
for converter in (connection.ops.get_db_converters(expression) +
|
||||
expression.get_db_converters(connection)):
|
||||
current_expires = converter(current_expires, expression, connection)
|
||||
|
||||
exp = connection.ops.adapt_datetimefield_value(exp)
|
||||
if result and mode == 'touch':
|
||||
cursor.execute(
|
||||
'UPDATE %s SET %s = %%s WHERE %s = %%s' % (
|
||||
table,
|
||||
quote_name('expires'),
|
||||
quote_name('cache_key')
|
||||
),
|
||||
[exp, key]
|
||||
)
|
||||
elif result and (mode == 'set' or (mode == 'add' and current_expires < now)):
|
||||
cursor.execute(
|
||||
'UPDATE %s SET %s = %%s, %s = %%s WHERE %s = %%s' % (
|
||||
table,
|
||||
quote_name('value'),
|
||||
quote_name('expires'),
|
||||
quote_name('cache_key'),
|
||||
),
|
||||
[b64encoded, exp, key]
|
||||
)
|
||||
elif mode != 'touch':
|
||||
cursor.execute(
|
||||
'INSERT INTO %s (%s, %s, %s) VALUES (%%s, %%s, %%s)' % (
|
||||
table,
|
||||
quote_name('cache_key'),
|
||||
quote_name('value'),
|
||||
quote_name('expires'),
|
||||
),
|
||||
[key, b64encoded, exp]
|
||||
)
|
||||
else:
|
||||
return False # touch failed.
|
||||
except DatabaseError:
|
||||
# To be threadsafe, updates/inserts are allowed to fail silently
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def delete(self, key, version=None):
|
||||
self.delete_many([key], version)
|
||||
|
||||
def delete_many(self, keys, version=None):
|
||||
key_list = []
|
||||
for key in keys:
|
||||
self.validate_key(key)
|
||||
key_list.append(self.make_key(key, version))
|
||||
self._base_delete_many(key_list)
|
||||
|
||||
def _base_delete_many(self, keys):
|
||||
if not keys:
|
||||
return
|
||||
|
||||
db = router.db_for_write(self.cache_model_class)
|
||||
connection = connections[db]
|
||||
quote_name = connection.ops.quote_name
|
||||
table = quote_name(self._table)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
'DELETE FROM %s WHERE %s IN (%s)' % (
|
||||
table,
|
||||
quote_name('cache_key'),
|
||||
', '.join(['%s'] * len(keys)),
|
||||
),
|
||||
keys,
|
||||
)
|
||||
|
||||
def has_key(self, key, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
|
||||
db = router.db_for_read(self.cache_model_class)
|
||||
connection = connections[db]
|
||||
quote_name = connection.ops.quote_name
|
||||
|
||||
if settings.USE_TZ:
|
||||
now = datetime.utcnow()
|
||||
else:
|
||||
now = datetime.now()
|
||||
now = now.replace(microsecond=0)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
'SELECT %s FROM %s WHERE %s = %%s and expires > %%s' % (
|
||||
quote_name('cache_key'),
|
||||
quote_name(self._table),
|
||||
quote_name('cache_key'),
|
||||
),
|
||||
[key, connection.ops.adapt_datetimefield_value(now)]
|
||||
)
|
||||
return cursor.fetchone() is not None
|
||||
|
||||
def _cull(self, db, cursor, now):
|
||||
if self._cull_frequency == 0:
|
||||
self.clear()
|
||||
else:
|
||||
connection = connections[db]
|
||||
table = connection.ops.quote_name(self._table)
|
||||
cursor.execute("DELETE FROM %s WHERE expires < %%s" % table,
|
||||
[connection.ops.adapt_datetimefield_value(now)])
|
||||
cursor.execute("SELECT COUNT(*) FROM %s" % table)
|
||||
num = cursor.fetchone()[0]
|
||||
if num > self._max_entries:
|
||||
cull_num = num // self._cull_frequency
|
||||
cursor.execute(
|
||||
connection.ops.cache_key_culling_sql() % table,
|
||||
[cull_num])
|
||||
cursor.execute("DELETE FROM %s "
|
||||
"WHERE cache_key < %%s" % table,
|
||||
[cursor.fetchone()[0]])
|
||||
|
||||
def clear(self):
|
||||
db = router.db_for_write(self.cache_model_class)
|
||||
connection = connections[db]
|
||||
table = connection.ops.quote_name(self._table)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('DELETE FROM %s' % table)
|
||||
38
venv/lib/python3.8/site-packages/django/core/cache/backends/dummy.py
vendored
Normal file
38
venv/lib/python3.8/site-packages/django/core/cache/backends/dummy.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"Dummy cache backend"
|
||||
|
||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||
|
||||
|
||||
class DummyCache(BaseCache):
|
||||
def __init__(self, host, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
return True
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
return default
|
||||
|
||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
self.validate_key(key)
|
||||
return False
|
||||
|
||||
def delete(self, key, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
|
||||
def has_key(self, key, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
return False
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
157
venv/lib/python3.8/site-packages/django/core/cache/backends/filebased.py
vendored
Normal file
157
venv/lib/python3.8/site-packages/django/core/cache/backends/filebased.py
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
"File-based cache backend"
|
||||
import glob
|
||||
import hashlib
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import tempfile
|
||||
import time
|
||||
import zlib
|
||||
|
||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||
from django.core.files import locks
|
||||
from django.core.files.move import file_move_safe
|
||||
|
||||
|
||||
class FileBasedCache(BaseCache):
|
||||
cache_suffix = '.djcache'
|
||||
pickle_protocol = pickle.HIGHEST_PROTOCOL
|
||||
|
||||
def __init__(self, dir, params):
|
||||
super().__init__(params)
|
||||
self._dir = os.path.abspath(dir)
|
||||
self._createdir()
|
||||
|
||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
if self.has_key(key, version):
|
||||
return False
|
||||
self.set(key, value, timeout, version)
|
||||
return True
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
fname = self._key_to_file(key, version)
|
||||
try:
|
||||
with open(fname, 'rb') as f:
|
||||
if not self._is_expired(f):
|
||||
return pickle.loads(zlib.decompress(f.read()))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return default
|
||||
|
||||
def _write_content(self, file, timeout, value):
|
||||
expiry = self.get_backend_timeout(timeout)
|
||||
file.write(pickle.dumps(expiry, self.pickle_protocol))
|
||||
file.write(zlib.compress(pickle.dumps(value, self.pickle_protocol)))
|
||||
|
||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
self._createdir() # Cache dir can be deleted at any time.
|
||||
fname = self._key_to_file(key, version)
|
||||
self._cull() # make some room if necessary
|
||||
fd, tmp_path = tempfile.mkstemp(dir=self._dir)
|
||||
renamed = False
|
||||
try:
|
||||
with open(fd, 'wb') as f:
|
||||
self._write_content(f, timeout, value)
|
||||
file_move_safe(tmp_path, fname, allow_overwrite=True)
|
||||
renamed = True
|
||||
finally:
|
||||
if not renamed:
|
||||
os.remove(tmp_path)
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
try:
|
||||
with open(self._key_to_file(key, version), 'r+b') as f:
|
||||
try:
|
||||
locks.lock(f, locks.LOCK_EX)
|
||||
if self._is_expired(f):
|
||||
return False
|
||||
else:
|
||||
previous_value = pickle.loads(zlib.decompress(f.read()))
|
||||
f.seek(0)
|
||||
self._write_content(f, timeout, previous_value)
|
||||
return True
|
||||
finally:
|
||||
locks.unlock(f)
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def delete(self, key, version=None):
|
||||
self._delete(self._key_to_file(key, version))
|
||||
|
||||
def _delete(self, fname):
|
||||
if not fname.startswith(self._dir) or not os.path.exists(fname):
|
||||
return
|
||||
try:
|
||||
os.remove(fname)
|
||||
except FileNotFoundError:
|
||||
# The file may have been removed by another process.
|
||||
pass
|
||||
|
||||
def has_key(self, key, version=None):
|
||||
fname = self._key_to_file(key, version)
|
||||
if os.path.exists(fname):
|
||||
with open(fname, 'rb') as f:
|
||||
return not self._is_expired(f)
|
||||
return False
|
||||
|
||||
def _cull(self):
|
||||
"""
|
||||
Remove random cache entries if max_entries is reached at a ratio
|
||||
of num_entries / cull_frequency. A value of 0 for CULL_FREQUENCY means
|
||||
that the entire cache will be purged.
|
||||
"""
|
||||
filelist = self._list_cache_files()
|
||||
num_entries = len(filelist)
|
||||
if num_entries < self._max_entries:
|
||||
return # return early if no culling is required
|
||||
if self._cull_frequency == 0:
|
||||
return self.clear() # Clear the cache when CULL_FREQUENCY = 0
|
||||
# Delete a random selection of entries
|
||||
filelist = random.sample(filelist,
|
||||
int(num_entries / self._cull_frequency))
|
||||
for fname in filelist:
|
||||
self._delete(fname)
|
||||
|
||||
def _createdir(self):
|
||||
os.makedirs(self._dir, 0o700, exist_ok=True)
|
||||
|
||||
def _key_to_file(self, key, version=None):
|
||||
"""
|
||||
Convert a key into a cache file path. Basically this is the
|
||||
root cache path joined with the md5sum of the key and a suffix.
|
||||
"""
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
return os.path.join(self._dir, ''.join(
|
||||
[hashlib.md5(key.encode()).hexdigest(), self.cache_suffix]))
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Remove all the cache files.
|
||||
"""
|
||||
for fname in self._list_cache_files():
|
||||
self._delete(fname)
|
||||
|
||||
def _is_expired(self, f):
|
||||
"""
|
||||
Take an open cache file `f` and delete it if it's expired.
|
||||
"""
|
||||
try:
|
||||
exp = pickle.load(f)
|
||||
except EOFError:
|
||||
exp = 0 # An empty file is considered expired.
|
||||
if exp is not None and exp < time.time():
|
||||
f.close() # On Windows a file has to be closed before deleting
|
||||
self._delete(f.name)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _list_cache_files(self):
|
||||
"""
|
||||
Get a list of paths to all the cache files. These are all the files
|
||||
in the root cache dir that end on the cache_suffix.
|
||||
"""
|
||||
return [
|
||||
os.path.join(self._dir, fname)
|
||||
for fname in glob.glob1(self._dir, '*%s' % self.cache_suffix)
|
||||
]
|
||||
122
venv/lib/python3.8/site-packages/django/core/cache/backends/locmem.py
vendored
Normal file
122
venv/lib/python3.8/site-packages/django/core/cache/backends/locmem.py
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
"Thread-safe in-memory cache backend."
|
||||
import pickle
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
from threading import Lock
|
||||
|
||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||
|
||||
# Global in-memory store of cache data. Keyed by name, to provide
|
||||
# multiple named local memory caches.
|
||||
_caches = {}
|
||||
_expire_info = {}
|
||||
_locks = {}
|
||||
|
||||
|
||||
class LocMemCache(BaseCache):
|
||||
pickle_protocol = pickle.HIGHEST_PROTOCOL
|
||||
|
||||
def __init__(self, name, params):
|
||||
super().__init__(params)
|
||||
self._cache = _caches.setdefault(name, OrderedDict())
|
||||
self._expire_info = _expire_info.setdefault(name, {})
|
||||
self._lock = _locks.setdefault(name, Lock())
|
||||
|
||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
pickled = pickle.dumps(value, self.pickle_protocol)
|
||||
with self._lock:
|
||||
if self._has_expired(key):
|
||||
self._set(key, pickled, timeout)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
with self._lock:
|
||||
if self._has_expired(key):
|
||||
self._delete(key)
|
||||
return default
|
||||
pickled = self._cache[key]
|
||||
self._cache.move_to_end(key, last=False)
|
||||
return pickle.loads(pickled)
|
||||
|
||||
def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
|
||||
if len(self._cache) >= self._max_entries:
|
||||
self._cull()
|
||||
self._cache[key] = value
|
||||
self._cache.move_to_end(key, last=False)
|
||||
self._expire_info[key] = self.get_backend_timeout(timeout)
|
||||
|
||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
pickled = pickle.dumps(value, self.pickle_protocol)
|
||||
with self._lock:
|
||||
self._set(key, pickled, timeout)
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
with self._lock:
|
||||
if self._has_expired(key):
|
||||
return False
|
||||
self._expire_info[key] = self.get_backend_timeout(timeout)
|
||||
return True
|
||||
|
||||
def incr(self, key, delta=1, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
with self._lock:
|
||||
if self._has_expired(key):
|
||||
self._delete(key)
|
||||
raise ValueError("Key '%s' not found" % key)
|
||||
pickled = self._cache[key]
|
||||
value = pickle.loads(pickled)
|
||||
new_value = value + delta
|
||||
pickled = pickle.dumps(new_value, self.pickle_protocol)
|
||||
self._cache[key] = pickled
|
||||
self._cache.move_to_end(key, last=False)
|
||||
return new_value
|
||||
|
||||
def has_key(self, key, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
with self._lock:
|
||||
if self._has_expired(key):
|
||||
self._delete(key)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _has_expired(self, key):
|
||||
exp = self._expire_info.get(key, -1)
|
||||
return exp is not None and exp <= time.time()
|
||||
|
||||
def _cull(self):
|
||||
if self._cull_frequency == 0:
|
||||
self._cache.clear()
|
||||
self._expire_info.clear()
|
||||
else:
|
||||
count = len(self._cache) // self._cull_frequency
|
||||
for i in range(count):
|
||||
key, _ = self._cache.popitem()
|
||||
del self._expire_info[key]
|
||||
|
||||
def _delete(self, key):
|
||||
try:
|
||||
del self._cache[key]
|
||||
del self._expire_info[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def delete(self, key, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
with self._lock:
|
||||
self._delete(key)
|
||||
|
||||
def clear(self):
|
||||
with self._lock:
|
||||
self._cache.clear()
|
||||
self._expire_info.clear()
|
||||
193
venv/lib/python3.8/site-packages/django/core/cache/backends/memcached.py
vendored
Normal file
193
venv/lib/python3.8/site-packages/django/core/cache/backends/memcached.py
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
"Memcached cache backend"
|
||||
|
||||
import pickle
|
||||
import re
|
||||
import time
|
||||
|
||||
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class BaseMemcachedCache(BaseCache):
|
||||
def __init__(self, server, params, library, value_not_found_exception):
|
||||
super().__init__(params)
|
||||
if isinstance(server, str):
|
||||
self._servers = re.split('[;,]', server)
|
||||
else:
|
||||
self._servers = server
|
||||
|
||||
# The exception type to catch from the underlying library for a key
|
||||
# that was not found. This is a ValueError for python-memcache,
|
||||
# pylibmc.NotFound for pylibmc, and cmemcache will return None without
|
||||
# raising an exception.
|
||||
self.LibraryValueNotFoundException = value_not_found_exception
|
||||
|
||||
self._lib = library
|
||||
self._options = params.get('OPTIONS') or {}
|
||||
|
||||
@property
|
||||
def _cache(self):
|
||||
"""
|
||||
Implement transparent thread-safe access to a memcached client.
|
||||
"""
|
||||
if getattr(self, '_client', None) is None:
|
||||
self._client = self._lib.Client(self._servers, **self._options)
|
||||
|
||||
return self._client
|
||||
|
||||
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
|
||||
"""
|
||||
Memcached deals with long (> 30 days) timeouts in a special
|
||||
way. Call this function to obtain a safe value for your timeout.
|
||||
"""
|
||||
if timeout == DEFAULT_TIMEOUT:
|
||||
timeout = self.default_timeout
|
||||
|
||||
if timeout is None:
|
||||
# Using 0 in memcache sets a non-expiring timeout.
|
||||
return 0
|
||||
elif int(timeout) == 0:
|
||||
# Other cache backends treat 0 as set-and-expire. To achieve this
|
||||
# in memcache backends, a negative timeout must be passed.
|
||||
timeout = -1
|
||||
|
||||
if timeout > 2592000: # 60*60*24*30, 30 days
|
||||
# See https://github.com/memcached/memcached/wiki/Programming#expiration
|
||||
# "Expiration times can be set from 0, meaning "never expire", to
|
||||
# 30 days. Any time higher than 30 days is interpreted as a Unix
|
||||
# timestamp date. If you want to expire an object on January 1st of
|
||||
# next year, this is how you do that."
|
||||
#
|
||||
# This means that we have to switch to absolute timestamps.
|
||||
timeout += int(time.time())
|
||||
return int(timeout)
|
||||
|
||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
return self._cache.add(key, value, self.get_backend_timeout(timeout))
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
return self._cache.get(key, default)
|
||||
|
||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
if not self._cache.set(key, value, self.get_backend_timeout(timeout)):
|
||||
# make sure the key doesn't keep its old value in case of failure to set (memcached's 1MB limit)
|
||||
self._cache.delete(key)
|
||||
|
||||
def delete(self, key, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self._cache.delete(key)
|
||||
|
||||
def get_many(self, keys, version=None):
|
||||
key_map = {self.make_key(key, version=version): key for key in keys}
|
||||
ret = self._cache.get_multi(key_map.keys())
|
||||
return {key_map[k]: v for k, v in ret.items()}
|
||||
|
||||
def close(self, **kwargs):
|
||||
# Many clients don't clean up connections properly.
|
||||
self._cache.disconnect_all()
|
||||
|
||||
def incr(self, key, delta=1, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
# memcached doesn't support a negative delta
|
||||
if delta < 0:
|
||||
return self._cache.decr(key, -delta)
|
||||
try:
|
||||
val = self._cache.incr(key, delta)
|
||||
|
||||
# python-memcache responds to incr on nonexistent keys by
|
||||
# raising a ValueError, pylibmc by raising a pylibmc.NotFound
|
||||
# and Cmemcache returns None. In all cases,
|
||||
# we should raise a ValueError though.
|
||||
except self.LibraryValueNotFoundException:
|
||||
val = None
|
||||
if val is None:
|
||||
raise ValueError("Key '%s' not found" % key)
|
||||
return val
|
||||
|
||||
def decr(self, key, delta=1, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
# memcached doesn't support a negative delta
|
||||
if delta < 0:
|
||||
return self._cache.incr(key, -delta)
|
||||
try:
|
||||
val = self._cache.decr(key, delta)
|
||||
|
||||
# python-memcache responds to incr on nonexistent keys by
|
||||
# raising a ValueError, pylibmc by raising a pylibmc.NotFound
|
||||
# and Cmemcache returns None. In all cases,
|
||||
# we should raise a ValueError though.
|
||||
except self.LibraryValueNotFoundException:
|
||||
val = None
|
||||
if val is None:
|
||||
raise ValueError("Key '%s' not found" % key)
|
||||
return val
|
||||
|
||||
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
safe_data = {}
|
||||
original_keys = {}
|
||||
for key, value in data.items():
|
||||
safe_key = self.make_key(key, version=version)
|
||||
safe_data[safe_key] = value
|
||||
original_keys[safe_key] = key
|
||||
failed_keys = self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
|
||||
return [original_keys[k] for k in failed_keys]
|
||||
|
||||
def delete_many(self, keys, version=None):
|
||||
self._cache.delete_multi(self.make_key(key, version=version) for key in keys)
|
||||
|
||||
def clear(self):
|
||||
self._cache.flush_all()
|
||||
|
||||
|
||||
class MemcachedCache(BaseMemcachedCache):
|
||||
"An implementation of a cache binding using python-memcached"
|
||||
def __init__(self, server, params):
|
||||
import memcache
|
||||
super().__init__(server, params, library=memcache, value_not_found_exception=ValueError)
|
||||
|
||||
@property
|
||||
def _cache(self):
|
||||
if getattr(self, '_client', None) is None:
|
||||
client_kwargs = {'pickleProtocol': pickle.HIGHEST_PROTOCOL}
|
||||
client_kwargs.update(self._options)
|
||||
self._client = self._lib.Client(self._servers, **client_kwargs)
|
||||
return self._client
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
return self._cache.touch(key, self.get_backend_timeout(timeout)) != 0
|
||||
|
||||
def get(self, key, default=None, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
val = self._cache.get(key)
|
||||
# python-memcached doesn't support default values in get().
|
||||
# https://github.com/linsomniac/python-memcached/issues/159
|
||||
# Remove this method if that issue is fixed.
|
||||
if val is None:
|
||||
return default
|
||||
return val
|
||||
|
||||
|
||||
class PyLibMCCache(BaseMemcachedCache):
|
||||
"An implementation of a cache binding using pylibmc"
|
||||
def __init__(self, server, params):
|
||||
import pylibmc
|
||||
super().__init__(server, params, library=pylibmc, value_not_found_exception=pylibmc.NotFound)
|
||||
|
||||
@cached_property
|
||||
def _cache(self):
|
||||
return self._lib.Client(self._servers, **self._options)
|
||||
|
||||
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
if timeout == 0:
|
||||
return self._cache.delete(key)
|
||||
return self._cache.touch(key, self.get_backend_timeout(timeout))
|
||||
|
||||
def close(self, **kwargs):
|
||||
# libmemcached manages its own connections. Don't call disconnect_all()
|
||||
# as it resets the failover state and creates unnecessary reconnects.
|
||||
pass
|
||||
12
venv/lib/python3.8/site-packages/django/core/cache/utils.py
vendored
Normal file
12
venv/lib/python3.8/site-packages/django/core/cache/utils.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import hashlib
|
||||
from urllib.parse import quote
|
||||
|
||||
TEMPLATE_FRAGMENT_KEY_TEMPLATE = 'template.cache.%s.%s'
|
||||
|
||||
|
||||
def make_template_fragment_key(fragment_name, vary_on=None):
|
||||
if vary_on is None:
|
||||
vary_on = ()
|
||||
key = ':'.join(quote(str(var)) for var in vary_on)
|
||||
args = hashlib.md5(key.encode())
|
||||
return TEMPLATE_FRAGMENT_KEY_TEMPLATE % (fragment_name, args.hexdigest())
|
||||
@@ -0,0 +1,24 @@
|
||||
from .messages import (
|
||||
CRITICAL, DEBUG, ERROR, INFO, WARNING, CheckMessage, Critical, Debug,
|
||||
Error, Info, Warning,
|
||||
)
|
||||
from .registry import Tags, register, run_checks, tag_exists
|
||||
|
||||
# Import these to force registration of checks
|
||||
import django.core.checks.caches # NOQA isort:skip
|
||||
import django.core.checks.database # NOQA isort:skip
|
||||
import django.core.checks.model_checks # NOQA isort:skip
|
||||
import django.core.checks.security.base # NOQA isort:skip
|
||||
import django.core.checks.security.csrf # NOQA isort:skip
|
||||
import django.core.checks.security.sessions # NOQA isort:skip
|
||||
import django.core.checks.templates # NOQA isort:skip
|
||||
import django.core.checks.translation # NOQA isort:skip
|
||||
import django.core.checks.urls # NOQA isort:skip
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CheckMessage',
|
||||
'Debug', 'Info', 'Warning', 'Error', 'Critical',
|
||||
'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL',
|
||||
'register', 'run_checks', 'tag_exists', 'Tags',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
from django.conf import settings
|
||||
from django.core.cache import DEFAULT_CACHE_ALIAS
|
||||
|
||||
from . import Error, Tags, register
|
||||
|
||||
E001 = Error(
|
||||
"You must define a '%s' cache in your CACHES setting." % DEFAULT_CACHE_ALIAS,
|
||||
id='caches.E001',
|
||||
)
|
||||
|
||||
|
||||
@register(Tags.caches)
|
||||
def check_default_cache_is_configured(app_configs, **kwargs):
|
||||
if DEFAULT_CACHE_ALIAS not in settings.CACHES:
|
||||
return [E001]
|
||||
return []
|
||||
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
from django.db import connections
|
||||
|
||||
from . import Tags, register
|
||||
|
||||
|
||||
@register(Tags.database)
|
||||
def check_database_backends(*args, **kwargs):
|
||||
issues = []
|
||||
for conn in connections.all():
|
||||
issues.extend(conn.validation.check(**kwargs))
|
||||
return issues
|
||||
@@ -0,0 +1,75 @@
|
||||
# Levels
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARNING = 30
|
||||
ERROR = 40
|
||||
CRITICAL = 50
|
||||
|
||||
|
||||
class CheckMessage:
|
||||
|
||||
def __init__(self, level, msg, hint=None, obj=None, id=None):
|
||||
assert isinstance(level, int), "The first argument should be level."
|
||||
self.level = level
|
||||
self.msg = msg
|
||||
self.hint = hint
|
||||
self.obj = obj
|
||||
self.id = id
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__) and
|
||||
all(getattr(self, attr) == getattr(other, attr)
|
||||
for attr in ['level', 'msg', 'hint', 'obj', 'id'])
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
from django.db import models
|
||||
|
||||
if self.obj is None:
|
||||
obj = "?"
|
||||
elif isinstance(self.obj, models.base.ModelBase):
|
||||
# We need to hardcode ModelBase and Field cases because its __str__
|
||||
# method doesn't return "applabel.modellabel" and cannot be changed.
|
||||
obj = self.obj._meta.label
|
||||
else:
|
||||
obj = str(self.obj)
|
||||
id = "(%s) " % self.id if self.id else ""
|
||||
hint = "\n\tHINT: %s" % self.hint if self.hint else ''
|
||||
return "%s: %s%s%s" % (obj, id, self.msg, hint)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: level=%r, msg=%r, hint=%r, obj=%r, id=%r>" % \
|
||||
(self.__class__.__name__, self.level, self.msg, self.hint, self.obj, self.id)
|
||||
|
||||
def is_serious(self, level=ERROR):
|
||||
return self.level >= level
|
||||
|
||||
def is_silenced(self):
|
||||
from django.conf import settings
|
||||
return self.id in settings.SILENCED_SYSTEM_CHECKS
|
||||
|
||||
|
||||
class Debug(CheckMessage):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(DEBUG, *args, **kwargs)
|
||||
|
||||
|
||||
class Info(CheckMessage):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(INFO, *args, **kwargs)
|
||||
|
||||
|
||||
class Warning(CheckMessage):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(WARNING, *args, **kwargs)
|
||||
|
||||
|
||||
class Error(CheckMessage):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(ERROR, *args, **kwargs)
|
||||
|
||||
|
||||
class Critical(CheckMessage):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(CRITICAL, *args, **kwargs)
|
||||
@@ -0,0 +1,210 @@
|
||||
import inspect
|
||||
import types
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.checks import Error, Tags, Warning, register
|
||||
|
||||
|
||||
@register(Tags.models)
|
||||
def check_all_models(app_configs=None, **kwargs):
|
||||
db_table_models = defaultdict(list)
|
||||
indexes = defaultdict(list)
|
||||
constraints = defaultdict(list)
|
||||
errors = []
|
||||
if app_configs is None:
|
||||
models = apps.get_models()
|
||||
else:
|
||||
models = chain.from_iterable(app_config.get_models() for app_config in app_configs)
|
||||
for model in models:
|
||||
if model._meta.managed and not model._meta.proxy:
|
||||
db_table_models[model._meta.db_table].append(model._meta.label)
|
||||
if not inspect.ismethod(model.check):
|
||||
errors.append(
|
||||
Error(
|
||||
"The '%s.check()' class method is currently overridden by %r."
|
||||
% (model.__name__, model.check),
|
||||
obj=model,
|
||||
id='models.E020'
|
||||
)
|
||||
)
|
||||
else:
|
||||
errors.extend(model.check(**kwargs))
|
||||
for model_index in model._meta.indexes:
|
||||
indexes[model_index.name].append(model._meta.label)
|
||||
for model_constraint in model._meta.constraints:
|
||||
constraints[model_constraint.name].append(model._meta.label)
|
||||
if settings.DATABASE_ROUTERS:
|
||||
error_class, error_id = Warning, 'models.W035'
|
||||
error_hint = (
|
||||
'You have configured settings.DATABASE_ROUTERS. Verify that %s '
|
||||
'are correctly routed to separate databases.'
|
||||
)
|
||||
else:
|
||||
error_class, error_id = Error, 'models.E028'
|
||||
error_hint = None
|
||||
for db_table, model_labels in db_table_models.items():
|
||||
if len(model_labels) != 1:
|
||||
model_labels_str = ', '.join(model_labels)
|
||||
errors.append(
|
||||
error_class(
|
||||
"db_table '%s' is used by multiple models: %s."
|
||||
% (db_table, model_labels_str),
|
||||
obj=db_table,
|
||||
hint=(error_hint % model_labels_str) if error_hint else None,
|
||||
id=error_id,
|
||||
)
|
||||
)
|
||||
for index_name, model_labels in indexes.items():
|
||||
if len(model_labels) > 1:
|
||||
model_labels = set(model_labels)
|
||||
errors.append(
|
||||
Error(
|
||||
"index name '%s' is not unique %s %s." % (
|
||||
index_name,
|
||||
'for model' if len(model_labels) == 1 else 'amongst models:',
|
||||
', '.join(sorted(model_labels)),
|
||||
),
|
||||
id='models.E029' if len(model_labels) == 1 else 'models.E030',
|
||||
),
|
||||
)
|
||||
for constraint_name, model_labels in constraints.items():
|
||||
if len(model_labels) > 1:
|
||||
model_labels = set(model_labels)
|
||||
errors.append(
|
||||
Error(
|
||||
"constraint name '%s' is not unique %s %s." % (
|
||||
constraint_name,
|
||||
'for model' if len(model_labels) == 1 else 'amongst models:',
|
||||
', '.join(sorted(model_labels)),
|
||||
),
|
||||
id='models.E031' if len(model_labels) == 1 else 'models.E032',
|
||||
),
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
def _check_lazy_references(apps, ignore=None):
|
||||
"""
|
||||
Ensure all lazy (i.e. string) model references have been resolved.
|
||||
|
||||
Lazy references are used in various places throughout Django, primarily in
|
||||
related fields and model signals. Identify those common cases and provide
|
||||
more helpful error messages for them.
|
||||
|
||||
The ignore parameter is used by StateApps to exclude swappable models from
|
||||
this check.
|
||||
"""
|
||||
pending_models = set(apps._pending_operations) - (ignore or set())
|
||||
|
||||
# Short circuit if there aren't any errors.
|
||||
if not pending_models:
|
||||
return []
|
||||
|
||||
from django.db.models import signals
|
||||
model_signals = {
|
||||
signal: name for name, signal in vars(signals).items()
|
||||
if isinstance(signal, signals.ModelSignal)
|
||||
}
|
||||
|
||||
def extract_operation(obj):
|
||||
"""
|
||||
Take a callable found in Apps._pending_operations and identify the
|
||||
original callable passed to Apps.lazy_model_operation(). If that
|
||||
callable was a partial, return the inner, non-partial function and
|
||||
any arguments and keyword arguments that were supplied with it.
|
||||
|
||||
obj is a callback defined locally in Apps.lazy_model_operation() and
|
||||
annotated there with a `func` attribute so as to imitate a partial.
|
||||
"""
|
||||
operation, args, keywords = obj, [], {}
|
||||
while hasattr(operation, 'func'):
|
||||
args.extend(getattr(operation, 'args', []))
|
||||
keywords.update(getattr(operation, 'keywords', {}))
|
||||
operation = operation.func
|
||||
return operation, args, keywords
|
||||
|
||||
def app_model_error(model_key):
|
||||
try:
|
||||
apps.get_app_config(model_key[0])
|
||||
model_error = "app '%s' doesn't provide model '%s'" % model_key
|
||||
except LookupError:
|
||||
model_error = "app '%s' isn't installed" % model_key[0]
|
||||
return model_error
|
||||
|
||||
# Here are several functions which return CheckMessage instances for the
|
||||
# most common usages of lazy operations throughout Django. These functions
|
||||
# take the model that was being waited on as an (app_label, modelname)
|
||||
# pair, the original lazy function, and its positional and keyword args as
|
||||
# determined by extract_operation().
|
||||
|
||||
def field_error(model_key, func, args, keywords):
|
||||
error_msg = (
|
||||
"The field %(field)s was declared with a lazy reference "
|
||||
"to '%(model)s', but %(model_error)s."
|
||||
)
|
||||
params = {
|
||||
'model': '.'.join(model_key),
|
||||
'field': keywords['field'],
|
||||
'model_error': app_model_error(model_key),
|
||||
}
|
||||
return Error(error_msg % params, obj=keywords['field'], id='fields.E307')
|
||||
|
||||
def signal_connect_error(model_key, func, args, keywords):
|
||||
error_msg = (
|
||||
"%(receiver)s was connected to the '%(signal)s' signal with a "
|
||||
"lazy reference to the sender '%(model)s', but %(model_error)s."
|
||||
)
|
||||
receiver = args[0]
|
||||
# The receiver is either a function or an instance of class
|
||||
# defining a `__call__` method.
|
||||
if isinstance(receiver, types.FunctionType):
|
||||
description = "The function '%s'" % receiver.__name__
|
||||
elif isinstance(receiver, types.MethodType):
|
||||
description = "Bound method '%s.%s'" % (receiver.__self__.__class__.__name__, receiver.__name__)
|
||||
else:
|
||||
description = "An instance of class '%s'" % receiver.__class__.__name__
|
||||
signal_name = model_signals.get(func.__self__, 'unknown')
|
||||
params = {
|
||||
'model': '.'.join(model_key),
|
||||
'receiver': description,
|
||||
'signal': signal_name,
|
||||
'model_error': app_model_error(model_key),
|
||||
}
|
||||
return Error(error_msg % params, obj=receiver.__module__, id='signals.E001')
|
||||
|
||||
def default_error(model_key, func, args, keywords):
|
||||
error_msg = "%(op)s contains a lazy reference to %(model)s, but %(model_error)s."
|
||||
params = {
|
||||
'op': func,
|
||||
'model': '.'.join(model_key),
|
||||
'model_error': app_model_error(model_key),
|
||||
}
|
||||
return Error(error_msg % params, obj=func, id='models.E022')
|
||||
|
||||
# Maps common uses of lazy operations to corresponding error functions
|
||||
# defined above. If a key maps to None, no error will be produced.
|
||||
# default_error() will be used for usages that don't appear in this dict.
|
||||
known_lazy = {
|
||||
('django.db.models.fields.related', 'resolve_related_class'): field_error,
|
||||
('django.db.models.fields.related', 'set_managed'): None,
|
||||
('django.dispatch.dispatcher', 'connect'): signal_connect_error,
|
||||
}
|
||||
|
||||
def build_error(model_key, func, args, keywords):
|
||||
key = (func.__module__, func.__name__)
|
||||
error_fn = known_lazy.get(key, default_error)
|
||||
return error_fn(model_key, func, args, keywords) if error_fn else None
|
||||
|
||||
return sorted(filter(None, (
|
||||
build_error(model_key, *extract_operation(func))
|
||||
for model_key in pending_models
|
||||
for func in apps._pending_operations[model_key]
|
||||
)), key=lambda error: error.msg)
|
||||
|
||||
|
||||
@register(Tags.models)
|
||||
def check_lazy_references(app_configs=None, **kwargs):
|
||||
return _check_lazy_references(apps)
|
||||
@@ -0,0 +1,97 @@
|
||||
from itertools import chain
|
||||
|
||||
from django.utils.itercompat import is_iterable
|
||||
|
||||
|
||||
class Tags:
|
||||
"""
|
||||
Built-in tags for internal checks.
|
||||
"""
|
||||
admin = 'admin'
|
||||
caches = 'caches'
|
||||
compatibility = 'compatibility'
|
||||
database = 'database'
|
||||
models = 'models'
|
||||
security = 'security'
|
||||
signals = 'signals'
|
||||
templates = 'templates'
|
||||
translation = 'translation'
|
||||
urls = 'urls'
|
||||
|
||||
|
||||
class CheckRegistry:
|
||||
|
||||
def __init__(self):
|
||||
self.registered_checks = set()
|
||||
self.deployment_checks = set()
|
||||
|
||||
def register(self, check=None, *tags, **kwargs):
|
||||
"""
|
||||
Can be used as a function or a decorator. Register given function
|
||||
`f` labeled with given `tags`. The function should receive **kwargs
|
||||
and return list of Errors and Warnings.
|
||||
|
||||
Example::
|
||||
|
||||
registry = CheckRegistry()
|
||||
@registry.register('mytag', 'anothertag')
|
||||
def my_check(apps, **kwargs):
|
||||
# ... perform checks and collect `errors` ...
|
||||
return errors
|
||||
# or
|
||||
registry.register(my_check, 'mytag', 'anothertag')
|
||||
"""
|
||||
def inner(check):
|
||||
check.tags = tags
|
||||
checks = self.deployment_checks if kwargs.get('deploy') else self.registered_checks
|
||||
checks.add(check)
|
||||
return check
|
||||
|
||||
if callable(check):
|
||||
return inner(check)
|
||||
else:
|
||||
if check:
|
||||
tags += (check,)
|
||||
return inner
|
||||
|
||||
def run_checks(self, app_configs=None, tags=None, include_deployment_checks=False):
|
||||
"""
|
||||
Run all registered checks and return list of Errors and Warnings.
|
||||
"""
|
||||
errors = []
|
||||
checks = self.get_checks(include_deployment_checks)
|
||||
|
||||
if tags is not None:
|
||||
checks = [check for check in checks if not set(check.tags).isdisjoint(tags)]
|
||||
else:
|
||||
# By default, 'database'-tagged checks are not run as they do more
|
||||
# than mere static code analysis.
|
||||
checks = [check for check in checks if Tags.database not in check.tags]
|
||||
|
||||
for check in checks:
|
||||
new_errors = check(app_configs=app_configs)
|
||||
assert is_iterable(new_errors), (
|
||||
"The function %r did not return a list. All functions registered "
|
||||
"with the checks registry must return a list." % check)
|
||||
errors.extend(new_errors)
|
||||
return errors
|
||||
|
||||
def tag_exists(self, tag, include_deployment_checks=False):
|
||||
return tag in self.tags_available(include_deployment_checks)
|
||||
|
||||
def tags_available(self, deployment_checks=False):
|
||||
return set(chain.from_iterable(
|
||||
check.tags for check in self.get_checks(deployment_checks)
|
||||
))
|
||||
|
||||
def get_checks(self, include_deployment_checks=False):
|
||||
checks = list(self.registered_checks)
|
||||
if include_deployment_checks:
|
||||
checks.extend(self.deployment_checks)
|
||||
return checks
|
||||
|
||||
|
||||
registry = CheckRegistry()
|
||||
register = registry.register
|
||||
run_checks = registry.run_checks
|
||||
tag_exists = registry.tag_exists
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,225 @@
|
||||
from django.conf import settings
|
||||
|
||||
from .. import Error, Tags, Warning, register
|
||||
|
||||
REFERRER_POLICY_VALUES = {
|
||||
'no-referrer', 'no-referrer-when-downgrade', 'origin',
|
||||
'origin-when-cross-origin', 'same-origin', 'strict-origin',
|
||||
'strict-origin-when-cross-origin', 'unsafe-url',
|
||||
}
|
||||
|
||||
SECRET_KEY_MIN_LENGTH = 50
|
||||
SECRET_KEY_MIN_UNIQUE_CHARACTERS = 5
|
||||
|
||||
W001 = Warning(
|
||||
"You do not have 'django.middleware.security.SecurityMiddleware' "
|
||||
"in your MIDDLEWARE so the SECURE_HSTS_SECONDS, "
|
||||
"SECURE_CONTENT_TYPE_NOSNIFF, SECURE_BROWSER_XSS_FILTER, "
|
||||
"SECURE_REFERRER_POLICY, and SECURE_SSL_REDIRECT settings will have no "
|
||||
"effect.",
|
||||
id='security.W001',
|
||||
)
|
||||
|
||||
W002 = Warning(
|
||||
"You do not have "
|
||||
"'django.middleware.clickjacking.XFrameOptionsMiddleware' in your "
|
||||
"MIDDLEWARE, so your pages will not be served with an "
|
||||
"'x-frame-options' header. Unless there is a good reason for your "
|
||||
"site to be served in a frame, you should consider enabling this "
|
||||
"header to help prevent clickjacking attacks.",
|
||||
id='security.W002',
|
||||
)
|
||||
|
||||
W004 = Warning(
|
||||
"You have not set a value for the SECURE_HSTS_SECONDS setting. "
|
||||
"If your entire site is served only over SSL, you may want to consider "
|
||||
"setting a value and enabling HTTP Strict Transport Security. "
|
||||
"Be sure to read the documentation first; enabling HSTS carelessly "
|
||||
"can cause serious, irreversible problems.",
|
||||
id='security.W004',
|
||||
)
|
||||
|
||||
W005 = Warning(
|
||||
"You have not set the SECURE_HSTS_INCLUDE_SUBDOMAINS setting to True. "
|
||||
"Without this, your site is potentially vulnerable to attack "
|
||||
"via an insecure connection to a subdomain. Only set this to True if "
|
||||
"you are certain that all subdomains of your domain should be served "
|
||||
"exclusively via SSL.",
|
||||
id='security.W005',
|
||||
)
|
||||
|
||||
W006 = Warning(
|
||||
"Your SECURE_CONTENT_TYPE_NOSNIFF setting is not set to True, "
|
||||
"so your pages will not be served with an "
|
||||
"'X-Content-Type-Options: nosniff' header. "
|
||||
"You should consider enabling this header to prevent the "
|
||||
"browser from identifying content types incorrectly.",
|
||||
id='security.W006',
|
||||
)
|
||||
|
||||
W008 = Warning(
|
||||
"Your SECURE_SSL_REDIRECT setting is not set to True. "
|
||||
"Unless your site should be available over both SSL and non-SSL "
|
||||
"connections, you may want to either set this setting True "
|
||||
"or configure a load balancer or reverse-proxy server "
|
||||
"to redirect all connections to HTTPS.",
|
||||
id='security.W008',
|
||||
)
|
||||
|
||||
W009 = Warning(
|
||||
"Your SECRET_KEY has less than %(min_length)s characters or less than "
|
||||
"%(min_unique_chars)s unique characters. Please generate a long and random "
|
||||
"SECRET_KEY, otherwise many of Django's security-critical features will be "
|
||||
"vulnerable to attack." % {
|
||||
'min_length': SECRET_KEY_MIN_LENGTH,
|
||||
'min_unique_chars': SECRET_KEY_MIN_UNIQUE_CHARACTERS,
|
||||
},
|
||||
id='security.W009',
|
||||
)
|
||||
|
||||
W018 = Warning(
|
||||
"You should not have DEBUG set to True in deployment.",
|
||||
id='security.W018',
|
||||
)
|
||||
|
||||
W019 = Warning(
|
||||
"You have "
|
||||
"'django.middleware.clickjacking.XFrameOptionsMiddleware' in your "
|
||||
"MIDDLEWARE, but X_FRAME_OPTIONS is not set to 'DENY'. "
|
||||
"Unless there is a good reason for your site to serve other parts of "
|
||||
"itself in a frame, you should change it to 'DENY'.",
|
||||
id='security.W019',
|
||||
)
|
||||
|
||||
W020 = Warning(
|
||||
"ALLOWED_HOSTS must not be empty in deployment.",
|
||||
id='security.W020',
|
||||
)
|
||||
|
||||
W021 = Warning(
|
||||
"You have not set the SECURE_HSTS_PRELOAD setting to True. Without this, "
|
||||
"your site cannot be submitted to the browser preload list.",
|
||||
id='security.W021',
|
||||
)
|
||||
|
||||
W022 = Warning(
|
||||
'You have not set the SECURE_REFERRER_POLICY setting. Without this, your '
|
||||
'site will not send a Referrer-Policy header. You should consider '
|
||||
'enabling this header to protect user privacy.',
|
||||
id='security.W022',
|
||||
)
|
||||
|
||||
E023 = Error(
|
||||
'You have set the SECURE_REFERRER_POLICY setting to an invalid value.',
|
||||
hint='Valid values are: {}.'.format(', '.join(sorted(REFERRER_POLICY_VALUES))),
|
||||
id='security.E023',
|
||||
)
|
||||
|
||||
|
||||
def _security_middleware():
|
||||
return 'django.middleware.security.SecurityMiddleware' in settings.MIDDLEWARE
|
||||
|
||||
|
||||
def _xframe_middleware():
|
||||
return 'django.middleware.clickjacking.XFrameOptionsMiddleware' in settings.MIDDLEWARE
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_security_middleware(app_configs, **kwargs):
|
||||
passed_check = _security_middleware()
|
||||
return [] if passed_check else [W001]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_xframe_options_middleware(app_configs, **kwargs):
|
||||
passed_check = _xframe_middleware()
|
||||
return [] if passed_check else [W002]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_sts(app_configs, **kwargs):
|
||||
passed_check = not _security_middleware() or settings.SECURE_HSTS_SECONDS
|
||||
return [] if passed_check else [W004]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_sts_include_subdomains(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
not _security_middleware() or
|
||||
not settings.SECURE_HSTS_SECONDS or
|
||||
settings.SECURE_HSTS_INCLUDE_SUBDOMAINS is True
|
||||
)
|
||||
return [] if passed_check else [W005]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_sts_preload(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
not _security_middleware() or
|
||||
not settings.SECURE_HSTS_SECONDS or
|
||||
settings.SECURE_HSTS_PRELOAD is True
|
||||
)
|
||||
return [] if passed_check else [W021]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_content_type_nosniff(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
not _security_middleware() or
|
||||
settings.SECURE_CONTENT_TYPE_NOSNIFF is True
|
||||
)
|
||||
return [] if passed_check else [W006]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_ssl_redirect(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
not _security_middleware() or
|
||||
settings.SECURE_SSL_REDIRECT is True
|
||||
)
|
||||
return [] if passed_check else [W008]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_secret_key(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
getattr(settings, 'SECRET_KEY', None) and
|
||||
len(set(settings.SECRET_KEY)) >= SECRET_KEY_MIN_UNIQUE_CHARACTERS and
|
||||
len(settings.SECRET_KEY) >= SECRET_KEY_MIN_LENGTH
|
||||
)
|
||||
return [] if passed_check else [W009]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_debug(app_configs, **kwargs):
|
||||
passed_check = not settings.DEBUG
|
||||
return [] if passed_check else [W018]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_xframe_deny(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
not _xframe_middleware() or
|
||||
settings.X_FRAME_OPTIONS == 'DENY'
|
||||
)
|
||||
return [] if passed_check else [W019]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_allowed_hosts(app_configs, **kwargs):
|
||||
return [] if settings.ALLOWED_HOSTS else [W020]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_referrer_policy(app_configs, **kwargs):
|
||||
if _security_middleware():
|
||||
if settings.SECURE_REFERRER_POLICY is None:
|
||||
return [W022]
|
||||
# Support a comma-separated string or iterable of values to allow fallback.
|
||||
if isinstance(settings.SECURE_REFERRER_POLICY, str):
|
||||
values = {v.strip() for v in settings.SECURE_REFERRER_POLICY.split(',')}
|
||||
else:
|
||||
values = set(settings.SECURE_REFERRER_POLICY)
|
||||
if not values <= REFERRER_POLICY_VALUES:
|
||||
return [E023]
|
||||
return []
|
||||
@@ -0,0 +1,40 @@
|
||||
from django.conf import settings
|
||||
|
||||
from .. import Tags, Warning, register
|
||||
|
||||
W003 = Warning(
|
||||
"You don't appear to be using Django's built-in "
|
||||
"cross-site request forgery protection via the middleware "
|
||||
"('django.middleware.csrf.CsrfViewMiddleware' is not in your "
|
||||
"MIDDLEWARE). Enabling the middleware is the safest approach "
|
||||
"to ensure you don't leave any holes.",
|
||||
id='security.W003',
|
||||
)
|
||||
|
||||
W016 = Warning(
|
||||
"You have 'django.middleware.csrf.CsrfViewMiddleware' in your "
|
||||
"MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. "
|
||||
"Using a secure-only CSRF cookie makes it more difficult for network "
|
||||
"traffic sniffers to steal the CSRF token.",
|
||||
id='security.W016',
|
||||
)
|
||||
|
||||
|
||||
def _csrf_middleware():
|
||||
return 'django.middleware.csrf.CsrfViewMiddleware' in settings.MIDDLEWARE
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_csrf_middleware(app_configs, **kwargs):
|
||||
passed_check = _csrf_middleware()
|
||||
return [] if passed_check else [W003]
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_csrf_cookie_secure(app_configs, **kwargs):
|
||||
passed_check = (
|
||||
settings.CSRF_USE_SESSIONS or
|
||||
not _csrf_middleware() or
|
||||
settings.CSRF_COOKIE_SECURE
|
||||
)
|
||||
return [] if passed_check else [W016]
|
||||
@@ -0,0 +1,97 @@
|
||||
from django.conf import settings
|
||||
|
||||
from .. import Tags, Warning, register
|
||||
|
||||
|
||||
def add_session_cookie_message(message):
|
||||
return message + (
|
||||
" Using a secure-only session cookie makes it more difficult for "
|
||||
"network traffic sniffers to hijack user sessions."
|
||||
)
|
||||
|
||||
|
||||
W010 = Warning(
|
||||
add_session_cookie_message(
|
||||
"You have 'django.contrib.sessions' in your INSTALLED_APPS, "
|
||||
"but you have not set SESSION_COOKIE_SECURE to True."
|
||||
),
|
||||
id='security.W010',
|
||||
)
|
||||
|
||||
W011 = Warning(
|
||||
add_session_cookie_message(
|
||||
"You have 'django.contrib.sessions.middleware.SessionMiddleware' "
|
||||
"in your MIDDLEWARE, but you have not set "
|
||||
"SESSION_COOKIE_SECURE to True."
|
||||
),
|
||||
id='security.W011',
|
||||
)
|
||||
|
||||
W012 = Warning(
|
||||
add_session_cookie_message("SESSION_COOKIE_SECURE is not set to True."),
|
||||
id='security.W012',
|
||||
)
|
||||
|
||||
|
||||
def add_httponly_message(message):
|
||||
return message + (
|
||||
" Using an HttpOnly session cookie makes it more difficult for "
|
||||
"cross-site scripting attacks to hijack user sessions."
|
||||
)
|
||||
|
||||
|
||||
W013 = Warning(
|
||||
add_httponly_message(
|
||||
"You have 'django.contrib.sessions' in your INSTALLED_APPS, "
|
||||
"but you have not set SESSION_COOKIE_HTTPONLY to True.",
|
||||
),
|
||||
id='security.W013',
|
||||
)
|
||||
|
||||
W014 = Warning(
|
||||
add_httponly_message(
|
||||
"You have 'django.contrib.sessions.middleware.SessionMiddleware' "
|
||||
"in your MIDDLEWARE, but you have not set "
|
||||
"SESSION_COOKIE_HTTPONLY to True."
|
||||
),
|
||||
id='security.W014',
|
||||
)
|
||||
|
||||
W015 = Warning(
|
||||
add_httponly_message("SESSION_COOKIE_HTTPONLY is not set to True."),
|
||||
id='security.W015',
|
||||
)
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_session_cookie_secure(app_configs, **kwargs):
|
||||
errors = []
|
||||
if not settings.SESSION_COOKIE_SECURE:
|
||||
if _session_app():
|
||||
errors.append(W010)
|
||||
if _session_middleware():
|
||||
errors.append(W011)
|
||||
if len(errors) > 1:
|
||||
errors = [W012]
|
||||
return errors
|
||||
|
||||
|
||||
@register(Tags.security, deploy=True)
|
||||
def check_session_cookie_httponly(app_configs, **kwargs):
|
||||
errors = []
|
||||
if not settings.SESSION_COOKIE_HTTPONLY:
|
||||
if _session_app():
|
||||
errors.append(W013)
|
||||
if _session_middleware():
|
||||
errors.append(W014)
|
||||
if len(errors) > 1:
|
||||
errors = [W015]
|
||||
return errors
|
||||
|
||||
|
||||
def _session_middleware():
|
||||
return 'django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE
|
||||
|
||||
|
||||
def _session_app():
|
||||
return "django.contrib.sessions" in settings.INSTALLED_APPS
|
||||
@@ -0,0 +1,35 @@
|
||||
import copy
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from . import Error, Tags, register
|
||||
|
||||
E001 = Error(
|
||||
"You have 'APP_DIRS': True in your TEMPLATES but also specify 'loaders' "
|
||||
"in OPTIONS. Either remove APP_DIRS or remove the 'loaders' option.",
|
||||
id='templates.E001',
|
||||
)
|
||||
E002 = Error(
|
||||
"'string_if_invalid' in TEMPLATES OPTIONS must be a string but got: {} ({}).",
|
||||
id="templates.E002",
|
||||
)
|
||||
|
||||
|
||||
@register(Tags.templates)
|
||||
def check_setting_app_dirs_loaders(app_configs, **kwargs):
|
||||
return [E001] if any(
|
||||
conf.get('APP_DIRS') and 'loaders' in conf.get('OPTIONS', {})
|
||||
for conf in settings.TEMPLATES
|
||||
) else []
|
||||
|
||||
|
||||
@register(Tags.templates)
|
||||
def check_string_if_invalid_is_string(app_configs, **kwargs):
|
||||
errors = []
|
||||
for conf in settings.TEMPLATES:
|
||||
string_if_invalid = conf.get('OPTIONS', {}).get('string_if_invalid', '')
|
||||
if not isinstance(string_if_invalid, str):
|
||||
error = copy.copy(E002)
|
||||
error.msg = error.msg.format(string_if_invalid, type(string_if_invalid).__name__)
|
||||
errors.append(error)
|
||||
return errors
|
||||
@@ -0,0 +1,64 @@
|
||||
from django.conf import settings
|
||||
from django.utils.translation import get_supported_language_variant
|
||||
from django.utils.translation.trans_real import language_code_re
|
||||
|
||||
from . import Error, Tags, register
|
||||
|
||||
E001 = Error(
|
||||
'You have provided an invalid value for the LANGUAGE_CODE setting: {!r}.',
|
||||
id='translation.E001',
|
||||
)
|
||||
|
||||
E002 = Error(
|
||||
'You have provided an invalid language code in the LANGUAGES setting: {!r}.',
|
||||
id='translation.E002',
|
||||
)
|
||||
|
||||
E003 = Error(
|
||||
'You have provided an invalid language code in the LANGUAGES_BIDI setting: {!r}.',
|
||||
id='translation.E003',
|
||||
)
|
||||
|
||||
E004 = Error(
|
||||
'You have provided a value for the LANGUAGE_CODE setting that is not in '
|
||||
'the LANGUAGES setting.',
|
||||
id='translation.E004',
|
||||
)
|
||||
|
||||
|
||||
@register(Tags.translation)
|
||||
def check_setting_language_code(app_configs, **kwargs):
|
||||
"""Error if LANGUAGE_CODE setting is invalid."""
|
||||
tag = settings.LANGUAGE_CODE
|
||||
if not isinstance(tag, str) or not language_code_re.match(tag):
|
||||
return [Error(E001.msg.format(tag), id=E001.id)]
|
||||
return []
|
||||
|
||||
|
||||
@register(Tags.translation)
|
||||
def check_setting_languages(app_configs, **kwargs):
|
||||
"""Error if LANGUAGES setting is invalid."""
|
||||
return [
|
||||
Error(E002.msg.format(tag), id=E002.id)
|
||||
for tag, _ in settings.LANGUAGES if not isinstance(tag, str) or not language_code_re.match(tag)
|
||||
]
|
||||
|
||||
|
||||
@register(Tags.translation)
|
||||
def check_setting_languages_bidi(app_configs, **kwargs):
|
||||
"""Error if LANGUAGES_BIDI setting is invalid."""
|
||||
return [
|
||||
Error(E003.msg.format(tag), id=E003.id)
|
||||
for tag in settings.LANGUAGES_BIDI if not isinstance(tag, str) or not language_code_re.match(tag)
|
||||
]
|
||||
|
||||
|
||||
@register(Tags.translation)
|
||||
def check_language_settings_consistent(app_configs, **kwargs):
|
||||
"""Error if language settings are not consistent with each other."""
|
||||
try:
|
||||
get_supported_language_variant(settings.LANGUAGE_CODE)
|
||||
except LookupError:
|
||||
return [E004]
|
||||
else:
|
||||
return []
|
||||
110
venv/lib/python3.8/site-packages/django/core/checks/urls.py
Normal file
110
venv/lib/python3.8/site-packages/django/core/checks/urls.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from collections import Counter
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from . import Error, Tags, Warning, register
|
||||
|
||||
|
||||
@register(Tags.urls)
|
||||
def check_url_config(app_configs, **kwargs):
|
||||
if getattr(settings, 'ROOT_URLCONF', None):
|
||||
from django.urls import get_resolver
|
||||
resolver = get_resolver()
|
||||
return check_resolver(resolver)
|
||||
return []
|
||||
|
||||
|
||||
def check_resolver(resolver):
|
||||
"""
|
||||
Recursively check the resolver.
|
||||
"""
|
||||
check_method = getattr(resolver, 'check', None)
|
||||
if check_method is not None:
|
||||
return check_method()
|
||||
elif not hasattr(resolver, 'resolve'):
|
||||
return get_warning_for_invalid_pattern(resolver)
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
@register(Tags.urls)
|
||||
def check_url_namespaces_unique(app_configs, **kwargs):
|
||||
"""
|
||||
Warn if URL namespaces used in applications aren't unique.
|
||||
"""
|
||||
if not getattr(settings, 'ROOT_URLCONF', None):
|
||||
return []
|
||||
|
||||
from django.urls import get_resolver
|
||||
resolver = get_resolver()
|
||||
all_namespaces = _load_all_namespaces(resolver)
|
||||
counter = Counter(all_namespaces)
|
||||
non_unique_namespaces = [n for n, count in counter.items() if count > 1]
|
||||
errors = []
|
||||
for namespace in non_unique_namespaces:
|
||||
errors.append(Warning(
|
||||
"URL namespace '{}' isn't unique. You may not be able to reverse "
|
||||
"all URLs in this namespace".format(namespace),
|
||||
id="urls.W005",
|
||||
))
|
||||
return errors
|
||||
|
||||
|
||||
def _load_all_namespaces(resolver, parents=()):
|
||||
"""
|
||||
Recursively load all namespaces from URL patterns.
|
||||
"""
|
||||
url_patterns = getattr(resolver, 'url_patterns', [])
|
||||
namespaces = [
|
||||
':'.join(parents + (url.namespace,)) for url in url_patterns
|
||||
if getattr(url, 'namespace', None) is not None
|
||||
]
|
||||
for pattern in url_patterns:
|
||||
namespace = getattr(pattern, 'namespace', None)
|
||||
current = parents
|
||||
if namespace is not None:
|
||||
current += (namespace,)
|
||||
namespaces.extend(_load_all_namespaces(pattern, current))
|
||||
return namespaces
|
||||
|
||||
|
||||
def get_warning_for_invalid_pattern(pattern):
|
||||
"""
|
||||
Return a list containing a warning that the pattern is invalid.
|
||||
|
||||
describe_pattern() cannot be used here, because we cannot rely on the
|
||||
urlpattern having regex or name attributes.
|
||||
"""
|
||||
if isinstance(pattern, str):
|
||||
hint = (
|
||||
"Try removing the string '{}'. The list of urlpatterns should not "
|
||||
"have a prefix string as the first element.".format(pattern)
|
||||
)
|
||||
elif isinstance(pattern, tuple):
|
||||
hint = "Try using path() instead of a tuple."
|
||||
else:
|
||||
hint = None
|
||||
|
||||
return [Error(
|
||||
"Your URL pattern {!r} is invalid. Ensure that urlpatterns is a list "
|
||||
"of path() and/or re_path() instances.".format(pattern),
|
||||
hint=hint,
|
||||
id="urls.E004",
|
||||
)]
|
||||
|
||||
|
||||
@register(Tags.urls)
|
||||
def check_url_settings(app_configs, **kwargs):
|
||||
errors = []
|
||||
for name in ('STATIC_URL', 'MEDIA_URL'):
|
||||
value = getattr(settings, name)
|
||||
if value and not value.endswith('/'):
|
||||
errors.append(E006(name))
|
||||
return errors
|
||||
|
||||
|
||||
def E006(name):
|
||||
return Error(
|
||||
'The {} setting must end with a slash.'.format(name),
|
||||
id='urls.E006',
|
||||
)
|
||||
193
venv/lib/python3.8/site-packages/django/core/exceptions.py
Normal file
193
venv/lib/python3.8/site-packages/django/core/exceptions.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Global Django exception and warning classes.
|
||||
"""
|
||||
|
||||
|
||||
class FieldDoesNotExist(Exception):
|
||||
"""The requested model field does not exist"""
|
||||
pass
|
||||
|
||||
|
||||
class AppRegistryNotReady(Exception):
|
||||
"""The django.apps registry is not populated yet"""
|
||||
pass
|
||||
|
||||
|
||||
class ObjectDoesNotExist(Exception):
|
||||
"""The requested object does not exist"""
|
||||
silent_variable_failure = True
|
||||
|
||||
|
||||
class MultipleObjectsReturned(Exception):
|
||||
"""The query returned multiple objects when only one was expected."""
|
||||
pass
|
||||
|
||||
|
||||
class SuspiciousOperation(Exception):
|
||||
"""The user did something suspicious"""
|
||||
|
||||
|
||||
class SuspiciousMultipartForm(SuspiciousOperation):
|
||||
"""Suspect MIME request in multipart form data"""
|
||||
pass
|
||||
|
||||
|
||||
class SuspiciousFileOperation(SuspiciousOperation):
|
||||
"""A Suspicious filesystem operation was attempted"""
|
||||
pass
|
||||
|
||||
|
||||
class DisallowedHost(SuspiciousOperation):
|
||||
"""HTTP_HOST header contains invalid value"""
|
||||
pass
|
||||
|
||||
|
||||
class DisallowedRedirect(SuspiciousOperation):
|
||||
"""Redirect to scheme not in allowed list"""
|
||||
pass
|
||||
|
||||
|
||||
class TooManyFieldsSent(SuspiciousOperation):
|
||||
"""
|
||||
The number of fields in a GET or POST request exceeded
|
||||
settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RequestDataTooBig(SuspiciousOperation):
|
||||
"""
|
||||
The size of the request (excluding any file uploads) exceeded
|
||||
settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RequestAborted(Exception):
|
||||
"""The request was closed before it was completed, or timed out."""
|
||||
pass
|
||||
|
||||
|
||||
class PermissionDenied(Exception):
|
||||
"""The user did not have permission to do that"""
|
||||
pass
|
||||
|
||||
|
||||
class ViewDoesNotExist(Exception):
|
||||
"""The requested view does not exist"""
|
||||
pass
|
||||
|
||||
|
||||
class MiddlewareNotUsed(Exception):
|
||||
"""This middleware is not used in this server configuration"""
|
||||
pass
|
||||
|
||||
|
||||
class ImproperlyConfigured(Exception):
|
||||
"""Django is somehow improperly configured"""
|
||||
pass
|
||||
|
||||
|
||||
class FieldError(Exception):
|
||||
"""Some kind of problem with a model field."""
|
||||
pass
|
||||
|
||||
|
||||
NON_FIELD_ERRORS = '__all__'
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
"""An error while validating data."""
|
||||
def __init__(self, message, code=None, params=None):
|
||||
"""
|
||||
The `message` argument can be a single error, a list of errors, or a
|
||||
dictionary that maps field names to lists of errors. What we define as
|
||||
an "error" can be either a simple string or an instance of
|
||||
ValidationError with its message attribute set, and what we define as
|
||||
list or dictionary can be an actual `list` or `dict` or an instance
|
||||
of ValidationError with its `error_list` or `error_dict` attribute set.
|
||||
"""
|
||||
super().__init__(message, code, params)
|
||||
|
||||
if isinstance(message, ValidationError):
|
||||
if hasattr(message, 'error_dict'):
|
||||
message = message.error_dict
|
||||
elif not hasattr(message, 'message'):
|
||||
message = message.error_list
|
||||
else:
|
||||
message, code, params = message.message, message.code, message.params
|
||||
|
||||
if isinstance(message, dict):
|
||||
self.error_dict = {}
|
||||
for field, messages in message.items():
|
||||
if not isinstance(messages, ValidationError):
|
||||
messages = ValidationError(messages)
|
||||
self.error_dict[field] = messages.error_list
|
||||
|
||||
elif isinstance(message, list):
|
||||
self.error_list = []
|
||||
for message in message:
|
||||
# Normalize plain strings to instances of ValidationError.
|
||||
if not isinstance(message, ValidationError):
|
||||
message = ValidationError(message)
|
||||
if hasattr(message, 'error_dict'):
|
||||
self.error_list.extend(sum(message.error_dict.values(), []))
|
||||
else:
|
||||
self.error_list.extend(message.error_list)
|
||||
|
||||
else:
|
||||
self.message = message
|
||||
self.code = code
|
||||
self.params = params
|
||||
self.error_list = [self]
|
||||
|
||||
@property
|
||||
def message_dict(self):
|
||||
# Trigger an AttributeError if this ValidationError
|
||||
# doesn't have an error_dict.
|
||||
getattr(self, 'error_dict')
|
||||
|
||||
return dict(self)
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
if hasattr(self, 'error_dict'):
|
||||
return sum(dict(self).values(), [])
|
||||
return list(self)
|
||||
|
||||
def update_error_dict(self, error_dict):
|
||||
if hasattr(self, 'error_dict'):
|
||||
for field, error_list in self.error_dict.items():
|
||||
error_dict.setdefault(field, []).extend(error_list)
|
||||
else:
|
||||
error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
|
||||
return error_dict
|
||||
|
||||
def __iter__(self):
|
||||
if hasattr(self, 'error_dict'):
|
||||
for field, errors in self.error_dict.items():
|
||||
yield field, list(ValidationError(errors))
|
||||
else:
|
||||
for error in self.error_list:
|
||||
message = error.message
|
||||
if error.params:
|
||||
message %= error.params
|
||||
yield str(message)
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'error_dict'):
|
||||
return repr(dict(self))
|
||||
return repr(list(self))
|
||||
|
||||
def __repr__(self):
|
||||
return 'ValidationError(%s)' % self
|
||||
|
||||
|
||||
class EmptyResultSet(Exception):
|
||||
"""A database query predicate is impossible."""
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousOnlyOperation(Exception):
|
||||
"""The user tried to call a sync-only function from an async context."""
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.core.files.base import File
|
||||
|
||||
__all__ = ['File']
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
160
venv/lib/python3.8/site-packages/django/core/files/base.py
Normal file
160
venv/lib/python3.8/site-packages/django/core/files/base.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import os
|
||||
from io import BytesIO, StringIO, UnsupportedOperation
|
||||
|
||||
from django.core.files.utils import FileProxyMixin
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class File(FileProxyMixin):
|
||||
DEFAULT_CHUNK_SIZE = 64 * 2 ** 10
|
||||
|
||||
def __init__(self, file, name=None):
|
||||
self.file = file
|
||||
if name is None:
|
||||
name = getattr(file, 'name', None)
|
||||
self.name = name
|
||||
if hasattr(file, 'mode'):
|
||||
self.mode = file.mode
|
||||
|
||||
def __str__(self):
|
||||
return self.name or ''
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self or "None")
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.name)
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
@cached_property
|
||||
def size(self):
|
||||
if hasattr(self.file, 'size'):
|
||||
return self.file.size
|
||||
if hasattr(self.file, 'name'):
|
||||
try:
|
||||
return os.path.getsize(self.file.name)
|
||||
except (OSError, TypeError):
|
||||
pass
|
||||
if hasattr(self.file, 'tell') and hasattr(self.file, 'seek'):
|
||||
pos = self.file.tell()
|
||||
self.file.seek(0, os.SEEK_END)
|
||||
size = self.file.tell()
|
||||
self.file.seek(pos)
|
||||
return size
|
||||
raise AttributeError("Unable to determine the file's size.")
|
||||
|
||||
def chunks(self, chunk_size=None):
|
||||
"""
|
||||
Read the file and yield chunks of ``chunk_size`` bytes (defaults to
|
||||
``File.DEFAULT_CHUNK_SIZE``).
|
||||
"""
|
||||
chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE
|
||||
try:
|
||||
self.seek(0)
|
||||
except (AttributeError, UnsupportedOperation):
|
||||
pass
|
||||
|
||||
while True:
|
||||
data = self.read(chunk_size)
|
||||
if not data:
|
||||
break
|
||||
yield data
|
||||
|
||||
def multiple_chunks(self, chunk_size=None):
|
||||
"""
|
||||
Return ``True`` if you can expect multiple chunks.
|
||||
|
||||
NB: If a particular file representation is in memory, subclasses should
|
||||
always return ``False`` -- there's no good reason to read from memory in
|
||||
chunks.
|
||||
"""
|
||||
return self.size > (chunk_size or self.DEFAULT_CHUNK_SIZE)
|
||||
|
||||
def __iter__(self):
|
||||
# Iterate over this file-like object by newlines
|
||||
buffer_ = None
|
||||
for chunk in self.chunks():
|
||||
for line in chunk.splitlines(True):
|
||||
if buffer_:
|
||||
if endswith_cr(buffer_) and not equals_lf(line):
|
||||
# Line split after a \r newline; yield buffer_.
|
||||
yield buffer_
|
||||
# Continue with line.
|
||||
else:
|
||||
# Line either split without a newline (line
|
||||
# continues after buffer_) or with \r\n
|
||||
# newline (line == b'\n').
|
||||
line = buffer_ + line
|
||||
# buffer_ handled, clear it.
|
||||
buffer_ = None
|
||||
|
||||
# If this is the end of a \n or \r\n line, yield.
|
||||
if endswith_lf(line):
|
||||
yield line
|
||||
else:
|
||||
buffer_ = line
|
||||
|
||||
if buffer_ is not None:
|
||||
yield buffer_
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close()
|
||||
|
||||
def open(self, mode=None):
|
||||
if not self.closed:
|
||||
self.seek(0)
|
||||
elif self.name and os.path.exists(self.name):
|
||||
self.file = open(self.name, mode or self.mode)
|
||||
else:
|
||||
raise ValueError("The file cannot be reopened.")
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
|
||||
|
||||
class ContentFile(File):
|
||||
"""
|
||||
A File-like object that takes just raw content, rather than an actual file.
|
||||
"""
|
||||
def __init__(self, content, name=None):
|
||||
stream_class = StringIO if isinstance(content, str) else BytesIO
|
||||
super().__init__(stream_class(content), name=name)
|
||||
self.size = len(content)
|
||||
|
||||
def __str__(self):
|
||||
return 'Raw content'
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def open(self, mode=None):
|
||||
self.seek(0)
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def write(self, data):
|
||||
self.__dict__.pop('size', None) # Clear the computed size.
|
||||
return self.file.write(data)
|
||||
|
||||
|
||||
def endswith_cr(line):
|
||||
"""Return True if line (a text or bytestring) ends with '\r'."""
|
||||
return line.endswith('\r' if isinstance(line, str) else b'\r')
|
||||
|
||||
|
||||
def endswith_lf(line):
|
||||
"""Return True if line (a text or bytestring) ends with '\n'."""
|
||||
return line.endswith('\n' if isinstance(line, str) else b'\n')
|
||||
|
||||
|
||||
def equals_lf(line):
|
||||
"""Return True if line (a text or bytestring) equals '\n'."""
|
||||
return line == ('\n' if isinstance(line, str) else b'\n')
|
||||
84
venv/lib/python3.8/site-packages/django/core/files/images.py
Normal file
84
venv/lib/python3.8/site-packages/django/core/files/images.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
Utility functions for handling images.
|
||||
|
||||
Requires Pillow as you might imagine.
|
||||
"""
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
from django.core.files import File
|
||||
|
||||
|
||||
class ImageFile(File):
|
||||
"""
|
||||
A mixin for use alongside django.core.files.base.File, which provides
|
||||
additional features for dealing with images.
|
||||
"""
|
||||
@property
|
||||
def width(self):
|
||||
return self._get_image_dimensions()[0]
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._get_image_dimensions()[1]
|
||||
|
||||
def _get_image_dimensions(self):
|
||||
if not hasattr(self, '_dimensions_cache'):
|
||||
close = self.closed
|
||||
self.open()
|
||||
self._dimensions_cache = get_image_dimensions(self, close=close)
|
||||
return self._dimensions_cache
|
||||
|
||||
|
||||
def get_image_dimensions(file_or_path, close=False):
|
||||
"""
|
||||
Return the (width, height) of an image, given an open file or a path. Set
|
||||
'close' to True to close the file at the end if it is initially in an open
|
||||
state.
|
||||
"""
|
||||
from PIL import ImageFile as PillowImageFile
|
||||
|
||||
p = PillowImageFile.Parser()
|
||||
if hasattr(file_or_path, 'read'):
|
||||
file = file_or_path
|
||||
file_pos = file.tell()
|
||||
file.seek(0)
|
||||
else:
|
||||
file = open(file_or_path, 'rb')
|
||||
close = True
|
||||
try:
|
||||
# Most of the time Pillow only needs a small chunk to parse the image
|
||||
# and get the dimensions, but with some TIFF files Pillow needs to
|
||||
# parse the whole file.
|
||||
chunk_size = 1024
|
||||
while 1:
|
||||
data = file.read(chunk_size)
|
||||
if not data:
|
||||
break
|
||||
try:
|
||||
p.feed(data)
|
||||
except zlib.error as e:
|
||||
# ignore zlib complaining on truncated stream, just feed more
|
||||
# data to parser (ticket #19457).
|
||||
if e.args[0].startswith("Error -5"):
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
except struct.error:
|
||||
# Ignore PIL failing on a too short buffer when reads return
|
||||
# less bytes than expected. Skip and feed more data to the
|
||||
# parser (ticket #24544).
|
||||
pass
|
||||
except RuntimeError:
|
||||
# e.g. "RuntimeError: could not create decoder object" for
|
||||
# WebP files. A different chunk_size may work.
|
||||
pass
|
||||
if p.image:
|
||||
return p.image.size
|
||||
chunk_size *= 2
|
||||
return (None, None)
|
||||
finally:
|
||||
if close:
|
||||
file.close()
|
||||
else:
|
||||
file.seek(file_pos)
|
||||
113
venv/lib/python3.8/site-packages/django/core/files/locks.py
Normal file
113
venv/lib/python3.8/site-packages/django/core/files/locks.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Portable file locking utilities.
|
||||
|
||||
Based partially on an example by Jonathan Feignberg in the Python
|
||||
Cookbook [1] (licensed under the Python Software License) and a ctypes port by
|
||||
Anatoly Techtonik for Roundup [2] (license [3]).
|
||||
|
||||
[1] http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
|
||||
[2] https://sourceforge.net/p/roundup/code/ci/default/tree/roundup/backends/portalocker.py
|
||||
[3] https://sourceforge.net/p/roundup/code/ci/default/tree/COPYING.txt
|
||||
|
||||
Example Usage::
|
||||
|
||||
>>> from django.core.files import locks
|
||||
>>> with open('./file', 'wb') as f:
|
||||
... locks.lock(f, locks.LOCK_EX)
|
||||
... f.write('Django')
|
||||
"""
|
||||
import os
|
||||
|
||||
__all__ = ('LOCK_EX', 'LOCK_SH', 'LOCK_NB', 'lock', 'unlock')
|
||||
|
||||
|
||||
def _fd(f):
|
||||
"""Get a filedescriptor from something which could be a file or an fd."""
|
||||
return f.fileno() if hasattr(f, 'fileno') else f
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
import msvcrt
|
||||
from ctypes import (sizeof, c_ulong, c_void_p, c_int64,
|
||||
Structure, Union, POINTER, windll, byref)
|
||||
from ctypes.wintypes import BOOL, DWORD, HANDLE
|
||||
|
||||
LOCK_SH = 0 # the default
|
||||
LOCK_NB = 0x1 # LOCKFILE_FAIL_IMMEDIATELY
|
||||
LOCK_EX = 0x2 # LOCKFILE_EXCLUSIVE_LOCK
|
||||
|
||||
# --- Adapted from the pyserial project ---
|
||||
# detect size of ULONG_PTR
|
||||
if sizeof(c_ulong) != sizeof(c_void_p):
|
||||
ULONG_PTR = c_int64
|
||||
else:
|
||||
ULONG_PTR = c_ulong
|
||||
PVOID = c_void_p
|
||||
|
||||
# --- Union inside Structure by stackoverflow:3480240 ---
|
||||
class _OFFSET(Structure):
|
||||
_fields_ = [
|
||||
('Offset', DWORD),
|
||||
('OffsetHigh', DWORD)]
|
||||
|
||||
class _OFFSET_UNION(Union):
|
||||
_anonymous_ = ['_offset']
|
||||
_fields_ = [
|
||||
('_offset', _OFFSET),
|
||||
('Pointer', PVOID)]
|
||||
|
||||
class OVERLAPPED(Structure):
|
||||
_anonymous_ = ['_offset_union']
|
||||
_fields_ = [
|
||||
('Internal', ULONG_PTR),
|
||||
('InternalHigh', ULONG_PTR),
|
||||
('_offset_union', _OFFSET_UNION),
|
||||
('hEvent', HANDLE)]
|
||||
|
||||
LPOVERLAPPED = POINTER(OVERLAPPED)
|
||||
|
||||
# --- Define function prototypes for extra safety ---
|
||||
LockFileEx = windll.kernel32.LockFileEx
|
||||
LockFileEx.restype = BOOL
|
||||
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
||||
UnlockFileEx = windll.kernel32.UnlockFileEx
|
||||
UnlockFileEx.restype = BOOL
|
||||
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
||||
|
||||
def lock(f, flags):
|
||||
hfile = msvcrt.get_osfhandle(_fd(f))
|
||||
overlapped = OVERLAPPED()
|
||||
ret = LockFileEx(hfile, flags, 0, 0, 0xFFFF0000, byref(overlapped))
|
||||
return bool(ret)
|
||||
|
||||
def unlock(f):
|
||||
hfile = msvcrt.get_osfhandle(_fd(f))
|
||||
overlapped = OVERLAPPED()
|
||||
ret = UnlockFileEx(hfile, 0, 0, 0xFFFF0000, byref(overlapped))
|
||||
return bool(ret)
|
||||
else:
|
||||
try:
|
||||
import fcntl
|
||||
LOCK_SH = fcntl.LOCK_SH # shared lock
|
||||
LOCK_NB = fcntl.LOCK_NB # non-blocking
|
||||
LOCK_EX = fcntl.LOCK_EX
|
||||
except (ImportError, AttributeError):
|
||||
# File locking is not supported.
|
||||
LOCK_EX = LOCK_SH = LOCK_NB = 0
|
||||
|
||||
# Dummy functions that don't do anything.
|
||||
def lock(f, flags):
|
||||
# File is not locked
|
||||
return False
|
||||
|
||||
def unlock(f):
|
||||
# File is unlocked
|
||||
return True
|
||||
else:
|
||||
def lock(f, flags):
|
||||
ret = fcntl.flock(_fd(f), flags)
|
||||
return ret == 0
|
||||
|
||||
def unlock(f):
|
||||
ret = fcntl.flock(_fd(f), fcntl.LOCK_UN)
|
||||
return ret == 0
|
||||
87
venv/lib/python3.8/site-packages/django/core/files/move.py
Normal file
87
venv/lib/python3.8/site-packages/django/core/files/move.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Move a file in the safest way possible::
|
||||
|
||||
>>> from django.core.files.move import file_move_safe
|
||||
>>> file_move_safe("/tmp/old_file", "/tmp/new_file")
|
||||
"""
|
||||
|
||||
import errno
|
||||
import os
|
||||
from shutil import copystat
|
||||
|
||||
from django.core.files import locks
|
||||
|
||||
__all__ = ['file_move_safe']
|
||||
|
||||
|
||||
def _samefile(src, dst):
|
||||
# Macintosh, Unix.
|
||||
if hasattr(os.path, 'samefile'):
|
||||
try:
|
||||
return os.path.samefile(src, dst)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# All other platforms: check for same pathname.
|
||||
return (os.path.normcase(os.path.abspath(src)) ==
|
||||
os.path.normcase(os.path.abspath(dst)))
|
||||
|
||||
|
||||
def file_move_safe(old_file_name, new_file_name, chunk_size=1024 * 64, allow_overwrite=False):
|
||||
"""
|
||||
Move a file from one location to another in the safest way possible.
|
||||
|
||||
First, try ``os.rename``, which is simple but will break across filesystems.
|
||||
If that fails, stream manually from one file to another in pure Python.
|
||||
|
||||
If the destination file exists and ``allow_overwrite`` is ``False``, raise
|
||||
``FileExistsError``.
|
||||
"""
|
||||
# There's no reason to move if we don't have to.
|
||||
if _samefile(old_file_name, new_file_name):
|
||||
return
|
||||
|
||||
try:
|
||||
if not allow_overwrite and os.access(new_file_name, os.F_OK):
|
||||
raise FileExistsError('Destination file %s exists and allow_overwrite is False.' % new_file_name)
|
||||
|
||||
os.rename(old_file_name, new_file_name)
|
||||
return
|
||||
except OSError:
|
||||
# OSError happens with os.rename() if moving to another filesystem or
|
||||
# when moving opened files on certain operating systems.
|
||||
pass
|
||||
|
||||
# first open the old file, so that it won't go away
|
||||
with open(old_file_name, 'rb') as old_file:
|
||||
# now open the new file, not forgetting allow_overwrite
|
||||
fd = os.open(new_file_name, (os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
|
||||
(os.O_EXCL if not allow_overwrite else 0)))
|
||||
try:
|
||||
locks.lock(fd, locks.LOCK_EX)
|
||||
current_chunk = None
|
||||
while current_chunk != b'':
|
||||
current_chunk = old_file.read(chunk_size)
|
||||
os.write(fd, current_chunk)
|
||||
finally:
|
||||
locks.unlock(fd)
|
||||
os.close(fd)
|
||||
|
||||
try:
|
||||
copystat(old_file_name, new_file_name)
|
||||
except PermissionError as e:
|
||||
# Certain filesystems (e.g. CIFS) fail to copy the file's metadata if
|
||||
# the type of the destination filesystem isn't the same as the source
|
||||
# filesystem; ignore that.
|
||||
if e.errno != errno.EPERM:
|
||||
raise
|
||||
|
||||
try:
|
||||
os.remove(old_file_name)
|
||||
except PermissionError as e:
|
||||
# Certain operating systems (Cygwin and Windows)
|
||||
# fail when deleting opened files, ignore it. (For the
|
||||
# systems where this happens, temporary files will be auto-deleted
|
||||
# on close anyway.)
|
||||
if getattr(e, 'winerror', 0) != 32:
|
||||
raise
|
||||
367
venv/lib/python3.8/site-packages/django/core/files/storage.py
Normal file
367
venv/lib/python3.8/site-packages/django/core/files/storage.py
Normal file
@@ -0,0 +1,367 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import SuspiciousFileOperation
|
||||
from django.core.files import File, locks
|
||||
from django.core.files.move import file_move_safe
|
||||
from django.core.signals import setting_changed
|
||||
from django.utils import timezone
|
||||
from django.utils._os import safe_join
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.encoding import filepath_to_uri
|
||||
from django.utils.functional import LazyObject, cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.text import get_valid_filename
|
||||
|
||||
__all__ = (
|
||||
'Storage', 'FileSystemStorage', 'DefaultStorage', 'default_storage',
|
||||
'get_storage_class',
|
||||
)
|
||||
|
||||
|
||||
class Storage:
|
||||
"""
|
||||
A base storage class, providing some default behaviors that all other
|
||||
storage systems can inherit or override, as necessary.
|
||||
"""
|
||||
|
||||
# The following methods represent a public interface to private methods.
|
||||
# These shouldn't be overridden by subclasses unless absolutely necessary.
|
||||
|
||||
def open(self, name, mode='rb'):
|
||||
"""Retrieve the specified file from storage."""
|
||||
return self._open(name, mode)
|
||||
|
||||
def save(self, name, content, max_length=None):
|
||||
"""
|
||||
Save new content to the file specified by name. The content should be
|
||||
a proper File object or any Python file-like object, ready to be read
|
||||
from the beginning.
|
||||
"""
|
||||
# Get the proper name for the file, as it will actually be saved.
|
||||
if name is None:
|
||||
name = content.name
|
||||
|
||||
if not hasattr(content, 'chunks'):
|
||||
content = File(content, name)
|
||||
|
||||
name = self.get_available_name(name, max_length=max_length)
|
||||
return self._save(name, content)
|
||||
|
||||
# These methods are part of the public API, with default implementations.
|
||||
|
||||
def get_valid_name(self, name):
|
||||
"""
|
||||
Return a filename, based on the provided filename, that's suitable for
|
||||
use in the target storage system.
|
||||
"""
|
||||
return get_valid_filename(name)
|
||||
|
||||
def get_alternative_name(self, file_root, file_ext):
|
||||
"""
|
||||
Return an alternative filename, by adding an underscore and a random 7
|
||||
character alphanumeric string (before the file extension, if one
|
||||
exists) to the filename.
|
||||
"""
|
||||
return '%s_%s%s' % (file_root, get_random_string(7), file_ext)
|
||||
|
||||
def get_available_name(self, name, max_length=None):
|
||||
"""
|
||||
Return a filename that's free on the target storage system and
|
||||
available for new content to be written to.
|
||||
"""
|
||||
dir_name, file_name = os.path.split(name)
|
||||
file_root, file_ext = os.path.splitext(file_name)
|
||||
# If the filename already exists, generate an alternative filename
|
||||
# until it doesn't exist.
|
||||
# Truncate original name if required, so the new filename does not
|
||||
# exceed the max_length.
|
||||
while self.exists(name) or (max_length and len(name) > max_length):
|
||||
# file_ext includes the dot.
|
||||
name = os.path.join(dir_name, self.get_alternative_name(file_root, file_ext))
|
||||
if max_length is None:
|
||||
continue
|
||||
# Truncate file_root if max_length exceeded.
|
||||
truncation = len(name) - max_length
|
||||
if truncation > 0:
|
||||
file_root = file_root[:-truncation]
|
||||
# Entire file_root was truncated in attempt to find an available filename.
|
||||
if not file_root:
|
||||
raise SuspiciousFileOperation(
|
||||
'Storage can not find an available filename for "%s". '
|
||||
'Please make sure that the corresponding file field '
|
||||
'allows sufficient "max_length".' % name
|
||||
)
|
||||
name = os.path.join(dir_name, self.get_alternative_name(file_root, file_ext))
|
||||
return name
|
||||
|
||||
def generate_filename(self, filename):
|
||||
"""
|
||||
Validate the filename by calling get_valid_name() and return a filename
|
||||
to be passed to the save() method.
|
||||
"""
|
||||
# `filename` may include a path as returned by FileField.upload_to.
|
||||
dirname, filename = os.path.split(filename)
|
||||
return os.path.normpath(os.path.join(dirname, self.get_valid_name(filename)))
|
||||
|
||||
def path(self, name):
|
||||
"""
|
||||
Return a local filesystem path where the file can be retrieved using
|
||||
Python's built-in open() function. Storage systems that can't be
|
||||
accessed using open() should *not* implement this method.
|
||||
"""
|
||||
raise NotImplementedError("This backend doesn't support absolute paths.")
|
||||
|
||||
# The following methods form the public API for storage systems, but with
|
||||
# no default implementations. Subclasses must implement *all* of these.
|
||||
|
||||
def delete(self, name):
|
||||
"""
|
||||
Delete the specified file from the storage system.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a delete() method')
|
||||
|
||||
def exists(self, name):
|
||||
"""
|
||||
Return True if a file referenced by the given name already exists in the
|
||||
storage system, or False if the name is available for a new file.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide an exists() method')
|
||||
|
||||
def listdir(self, path):
|
||||
"""
|
||||
List the contents of the specified path. Return a 2-tuple of lists:
|
||||
the first item being directories, the second item being files.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a listdir() method')
|
||||
|
||||
def size(self, name):
|
||||
"""
|
||||
Return the total size, in bytes, of the file specified by name.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a size() method')
|
||||
|
||||
def url(self, name):
|
||||
"""
|
||||
Return an absolute URL where the file's contents can be accessed
|
||||
directly by a Web browser.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a url() method')
|
||||
|
||||
def get_accessed_time(self, name):
|
||||
"""
|
||||
Return the last accessed time (as a datetime) of the file specified by
|
||||
name. The datetime will be timezone-aware if USE_TZ=True.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a get_accessed_time() method')
|
||||
|
||||
def get_created_time(self, name):
|
||||
"""
|
||||
Return the creation time (as a datetime) of the file specified by name.
|
||||
The datetime will be timezone-aware if USE_TZ=True.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a get_created_time() method')
|
||||
|
||||
def get_modified_time(self, name):
|
||||
"""
|
||||
Return the last modified time (as a datetime) of the file specified by
|
||||
name. The datetime will be timezone-aware if USE_TZ=True.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of Storage must provide a get_modified_time() method')
|
||||
|
||||
|
||||
@deconstructible
|
||||
class FileSystemStorage(Storage):
|
||||
"""
|
||||
Standard filesystem storage
|
||||
"""
|
||||
# The combination of O_CREAT and O_EXCL makes os.open() raise OSError if
|
||||
# the file already exists before it's opened.
|
||||
OS_OPEN_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)
|
||||
|
||||
def __init__(self, location=None, base_url=None, file_permissions_mode=None,
|
||||
directory_permissions_mode=None):
|
||||
self._location = location
|
||||
self._base_url = base_url
|
||||
self._file_permissions_mode = file_permissions_mode
|
||||
self._directory_permissions_mode = directory_permissions_mode
|
||||
setting_changed.connect(self._clear_cached_properties)
|
||||
|
||||
def _clear_cached_properties(self, setting, **kwargs):
|
||||
"""Reset setting based property values."""
|
||||
if setting == 'MEDIA_ROOT':
|
||||
self.__dict__.pop('base_location', None)
|
||||
self.__dict__.pop('location', None)
|
||||
elif setting == 'MEDIA_URL':
|
||||
self.__dict__.pop('base_url', None)
|
||||
elif setting == 'FILE_UPLOAD_PERMISSIONS':
|
||||
self.__dict__.pop('file_permissions_mode', None)
|
||||
elif setting == 'FILE_UPLOAD_DIRECTORY_PERMISSIONS':
|
||||
self.__dict__.pop('directory_permissions_mode', None)
|
||||
|
||||
def _value_or_setting(self, value, setting):
|
||||
return setting if value is None else value
|
||||
|
||||
@cached_property
|
||||
def base_location(self):
|
||||
return self._value_or_setting(self._location, settings.MEDIA_ROOT)
|
||||
|
||||
@cached_property
|
||||
def location(self):
|
||||
return os.path.abspath(self.base_location)
|
||||
|
||||
@cached_property
|
||||
def base_url(self):
|
||||
if self._base_url is not None and not self._base_url.endswith('/'):
|
||||
self._base_url += '/'
|
||||
return self._value_or_setting(self._base_url, settings.MEDIA_URL)
|
||||
|
||||
@cached_property
|
||||
def file_permissions_mode(self):
|
||||
return self._value_or_setting(self._file_permissions_mode, settings.FILE_UPLOAD_PERMISSIONS)
|
||||
|
||||
@cached_property
|
||||
def directory_permissions_mode(self):
|
||||
return self._value_or_setting(self._directory_permissions_mode, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS)
|
||||
|
||||
def _open(self, name, mode='rb'):
|
||||
return File(open(self.path(name), mode))
|
||||
|
||||
def _save(self, name, content):
|
||||
full_path = self.path(name)
|
||||
|
||||
# Create any intermediate directories that do not exist.
|
||||
directory = os.path.dirname(full_path)
|
||||
try:
|
||||
if self.directory_permissions_mode is not None:
|
||||
# os.makedirs applies the global umask, so we reset it,
|
||||
# for consistency with file_permissions_mode behavior.
|
||||
old_umask = os.umask(0)
|
||||
try:
|
||||
os.makedirs(directory, self.directory_permissions_mode, exist_ok=True)
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
else:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
except FileExistsError:
|
||||
raise FileExistsError('%s exists and is not a directory.' % directory)
|
||||
|
||||
# There's a potential race condition between get_available_name and
|
||||
# saving the file; it's possible that two threads might return the
|
||||
# same name, at which point all sorts of fun happens. So we need to
|
||||
# try to create the file, but if it already exists we have to go back
|
||||
# to get_available_name() and try again.
|
||||
|
||||
while True:
|
||||
try:
|
||||
# This file has a file path that we can move.
|
||||
if hasattr(content, 'temporary_file_path'):
|
||||
file_move_safe(content.temporary_file_path(), full_path)
|
||||
|
||||
# This is a normal uploadedfile that we can stream.
|
||||
else:
|
||||
# The current umask value is masked out by os.open!
|
||||
fd = os.open(full_path, self.OS_OPEN_FLAGS, 0o666)
|
||||
_file = None
|
||||
try:
|
||||
locks.lock(fd, locks.LOCK_EX)
|
||||
for chunk in content.chunks():
|
||||
if _file is None:
|
||||
mode = 'wb' if isinstance(chunk, bytes) else 'wt'
|
||||
_file = os.fdopen(fd, mode)
|
||||
_file.write(chunk)
|
||||
finally:
|
||||
locks.unlock(fd)
|
||||
if _file is not None:
|
||||
_file.close()
|
||||
else:
|
||||
os.close(fd)
|
||||
except FileExistsError:
|
||||
# A new name is needed if the file exists.
|
||||
name = self.get_available_name(name)
|
||||
full_path = self.path(name)
|
||||
else:
|
||||
# OK, the file save worked. Break out of the loop.
|
||||
break
|
||||
|
||||
if self.file_permissions_mode is not None:
|
||||
os.chmod(full_path, self.file_permissions_mode)
|
||||
|
||||
# Store filenames with forward slashes, even on Windows.
|
||||
return name.replace('\\', '/')
|
||||
|
||||
def delete(self, name):
|
||||
assert name, "The name argument is not allowed to be empty."
|
||||
name = self.path(name)
|
||||
# If the file or directory exists, delete it from the filesystem.
|
||||
try:
|
||||
if os.path.isdir(name):
|
||||
os.rmdir(name)
|
||||
else:
|
||||
os.remove(name)
|
||||
except FileNotFoundError:
|
||||
# FileNotFoundError is raised if the file or directory was removed
|
||||
# concurrently.
|
||||
pass
|
||||
|
||||
def exists(self, name):
|
||||
return os.path.exists(self.path(name))
|
||||
|
||||
def listdir(self, path):
|
||||
path = self.path(path)
|
||||
directories, files = [], []
|
||||
for entry in os.scandir(path):
|
||||
if entry.is_dir():
|
||||
directories.append(entry.name)
|
||||
else:
|
||||
files.append(entry.name)
|
||||
return directories, files
|
||||
|
||||
def path(self, name):
|
||||
return safe_join(self.location, name)
|
||||
|
||||
def size(self, name):
|
||||
return os.path.getsize(self.path(name))
|
||||
|
||||
def url(self, name):
|
||||
if self.base_url is None:
|
||||
raise ValueError("This file is not accessible via a URL.")
|
||||
url = filepath_to_uri(name)
|
||||
if url is not None:
|
||||
url = url.lstrip('/')
|
||||
return urljoin(self.base_url, url)
|
||||
|
||||
def _datetime_from_timestamp(self, ts):
|
||||
"""
|
||||
If timezone support is enabled, make an aware datetime object in UTC;
|
||||
otherwise make a naive one in the local timezone.
|
||||
"""
|
||||
if settings.USE_TZ:
|
||||
# Safe to use .replace() because UTC doesn't have DST
|
||||
return datetime.utcfromtimestamp(ts).replace(tzinfo=timezone.utc)
|
||||
else:
|
||||
return datetime.fromtimestamp(ts)
|
||||
|
||||
def get_accessed_time(self, name):
|
||||
return self._datetime_from_timestamp(os.path.getatime(self.path(name)))
|
||||
|
||||
def get_created_time(self, name):
|
||||
return self._datetime_from_timestamp(os.path.getctime(self.path(name)))
|
||||
|
||||
def get_modified_time(self, name):
|
||||
return self._datetime_from_timestamp(os.path.getmtime(self.path(name)))
|
||||
|
||||
|
||||
def get_storage_class(import_path=None):
|
||||
return import_string(import_path or settings.DEFAULT_FILE_STORAGE)
|
||||
|
||||
|
||||
class DefaultStorage(LazyObject):
|
||||
def _setup(self):
|
||||
self._wrapped = get_storage_class()()
|
||||
|
||||
|
||||
default_storage = DefaultStorage()
|
||||
74
venv/lib/python3.8/site-packages/django/core/files/temp.py
Normal file
74
venv/lib/python3.8/site-packages/django/core/files/temp.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
The temp module provides a NamedTemporaryFile that can be reopened in the same
|
||||
process on any platform. Most platforms use the standard Python
|
||||
tempfile.NamedTemporaryFile class, but Windows users are given a custom class.
|
||||
|
||||
This is needed because the Python implementation of NamedTemporaryFile uses the
|
||||
O_TEMPORARY flag under Windows, which prevents the file from being reopened
|
||||
if the same flag is not provided [1][2]. Note that this does not address the
|
||||
more general issue of opening a file for writing and reading in multiple
|
||||
processes in a manner that works across platforms.
|
||||
|
||||
The custom version of NamedTemporaryFile doesn't support the same keyword
|
||||
arguments available in tempfile.NamedTemporaryFile.
|
||||
|
||||
1: https://mail.python.org/pipermail/python-list/2005-December/336957.html
|
||||
2: https://bugs.python.org/issue14243
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from django.core.files.utils import FileProxyMixin
|
||||
|
||||
__all__ = ('NamedTemporaryFile', 'gettempdir',)
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
class TemporaryFile(FileProxyMixin):
|
||||
"""
|
||||
Temporary file object constructor that supports reopening of the
|
||||
temporary file in Windows.
|
||||
|
||||
Unlike tempfile.NamedTemporaryFile from the standard library,
|
||||
__init__() doesn't support the 'delete', 'buffering', 'encoding', or
|
||||
'newline' keyword arguments.
|
||||
"""
|
||||
def __init__(self, mode='w+b', bufsize=-1, suffix='', prefix='', dir=None):
|
||||
fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir)
|
||||
self.name = name
|
||||
self.file = os.fdopen(fd, mode, bufsize)
|
||||
self.close_called = False
|
||||
|
||||
# Because close can be called during shutdown
|
||||
# we need to cache os.unlink and access it
|
||||
# as self.unlink only
|
||||
unlink = os.unlink
|
||||
|
||||
def close(self):
|
||||
if not self.close_called:
|
||||
self.close_called = True
|
||||
try:
|
||||
self.file.close()
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
self.unlink(self.name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __enter__(self):
|
||||
self.file.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
self.file.__exit__(exc, value, tb)
|
||||
|
||||
NamedTemporaryFile = TemporaryFile
|
||||
else:
|
||||
NamedTemporaryFile = tempfile.NamedTemporaryFile
|
||||
|
||||
gettempdir = tempfile.gettempdir
|
||||
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Classes representing uploaded files.
|
||||
"""
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files import temp as tempfile
|
||||
from django.core.files.base import File
|
||||
|
||||
__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile',
|
||||
'SimpleUploadedFile')
|
||||
|
||||
|
||||
class UploadedFile(File):
|
||||
"""
|
||||
An abstract uploaded file (``TemporaryUploadedFile`` and
|
||||
``InMemoryUploadedFile`` are the built-in concrete subclasses).
|
||||
|
||||
An ``UploadedFile`` object behaves somewhat like a file object and
|
||||
represents some file data that the user submitted with a form.
|
||||
"""
|
||||
|
||||
def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None):
|
||||
super().__init__(file, name)
|
||||
self.size = size
|
||||
self.content_type = content_type
|
||||
self.charset = charset
|
||||
self.content_type_extra = content_type_extra
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type)
|
||||
|
||||
def _get_name(self):
|
||||
return self._name
|
||||
|
||||
def _set_name(self, name):
|
||||
# Sanitize the file name so that it can't be dangerous.
|
||||
if name is not None:
|
||||
# Just use the basename of the file -- anything else is dangerous.
|
||||
name = os.path.basename(name)
|
||||
|
||||
# File names longer than 255 characters can cause problems on older OSes.
|
||||
if len(name) > 255:
|
||||
name, ext = os.path.splitext(name)
|
||||
ext = ext[:255]
|
||||
name = name[:255 - len(ext)] + ext
|
||||
|
||||
self._name = name
|
||||
|
||||
name = property(_get_name, _set_name)
|
||||
|
||||
|
||||
class TemporaryUploadedFile(UploadedFile):
|
||||
"""
|
||||
A file uploaded to a temporary location (i.e. stream-to-disk).
|
||||
"""
|
||||
def __init__(self, name, content_type, size, charset, content_type_extra=None):
|
||||
_, ext = os.path.splitext(name)
|
||||
file = tempfile.NamedTemporaryFile(suffix='.upload' + ext, dir=settings.FILE_UPLOAD_TEMP_DIR)
|
||||
super().__init__(file, name, content_type, size, charset, content_type_extra)
|
||||
|
||||
def temporary_file_path(self):
|
||||
"""Return the full path of this file."""
|
||||
return self.file.name
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
return self.file.close()
|
||||
except FileNotFoundError:
|
||||
# The file was moved or deleted before the tempfile could unlink
|
||||
# it. Still sets self.file.close_called and calls
|
||||
# self.file.file.close() before the exception.
|
||||
pass
|
||||
|
||||
|
||||
class InMemoryUploadedFile(UploadedFile):
|
||||
"""
|
||||
A file uploaded into memory (i.e. stream-to-memory).
|
||||
"""
|
||||
def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
|
||||
super().__init__(file, name, content_type, size, charset, content_type_extra)
|
||||
self.field_name = field_name
|
||||
|
||||
def open(self, mode=None):
|
||||
self.file.seek(0)
|
||||
return self
|
||||
|
||||
def chunks(self, chunk_size=None):
|
||||
self.file.seek(0)
|
||||
yield self.read()
|
||||
|
||||
def multiple_chunks(self, chunk_size=None):
|
||||
# Since it's in memory, we'll never have multiple chunks.
|
||||
return False
|
||||
|
||||
|
||||
class SimpleUploadedFile(InMemoryUploadedFile):
|
||||
"""
|
||||
A simple representation of a file, which just has content, size, and a name.
|
||||
"""
|
||||
def __init__(self, name, content, content_type='text/plain'):
|
||||
content = content or b''
|
||||
super().__init__(BytesIO(content), None, name, content_type, len(content), None, None)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, file_dict):
|
||||
"""
|
||||
Create a SimpleUploadedFile object from a dictionary with keys:
|
||||
- filename
|
||||
- content-type
|
||||
- content
|
||||
"""
|
||||
return cls(file_dict['filename'],
|
||||
file_dict['content'],
|
||||
file_dict.get('content-type', 'text/plain'))
|
||||
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
Base file upload handler classes, and the built-in concrete subclasses
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadedfile import (
|
||||
InMemoryUploadedFile, TemporaryUploadedFile,
|
||||
)
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
__all__ = [
|
||||
'UploadFileException', 'StopUpload', 'SkipFile', 'FileUploadHandler',
|
||||
'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', 'load_handler',
|
||||
'StopFutureHandlers'
|
||||
]
|
||||
|
||||
|
||||
class UploadFileException(Exception):
|
||||
"""
|
||||
Any error having to do with uploading files.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StopUpload(UploadFileException):
|
||||
"""
|
||||
This exception is raised when an upload must abort.
|
||||
"""
|
||||
def __init__(self, connection_reset=False):
|
||||
"""
|
||||
If ``connection_reset`` is ``True``, Django knows will halt the upload
|
||||
without consuming the rest of the upload. This will cause the browser to
|
||||
show a "connection reset" error.
|
||||
"""
|
||||
self.connection_reset = connection_reset
|
||||
|
||||
def __str__(self):
|
||||
if self.connection_reset:
|
||||
return 'StopUpload: Halt current upload.'
|
||||
else:
|
||||
return 'StopUpload: Consume request data, then halt.'
|
||||
|
||||
|
||||
class SkipFile(UploadFileException):
|
||||
"""
|
||||
This exception is raised by an upload handler that wants to skip a given file.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StopFutureHandlers(UploadFileException):
|
||||
"""
|
||||
Upload handlers that have handled a file and do not want future handlers to
|
||||
run should raise this exception instead of returning None.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class FileUploadHandler:
|
||||
"""
|
||||
Base class for streaming upload handlers.
|
||||
"""
|
||||
chunk_size = 64 * 2 ** 10 # : The default chunk size is 64 KB.
|
||||
|
||||
def __init__(self, request=None):
|
||||
self.file_name = None
|
||||
self.content_type = None
|
||||
self.content_length = None
|
||||
self.charset = None
|
||||
self.content_type_extra = None
|
||||
self.request = request
|
||||
|
||||
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
|
||||
"""
|
||||
Handle the raw input from the client.
|
||||
|
||||
Parameters:
|
||||
|
||||
:input_data:
|
||||
An object that supports reading via .read().
|
||||
:META:
|
||||
``request.META``.
|
||||
:content_length:
|
||||
The (integer) value of the Content-Length header from the
|
||||
client.
|
||||
:boundary: The boundary from the Content-Type header. Be sure to
|
||||
prepend two '--'.
|
||||
"""
|
||||
pass
|
||||
|
||||
def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
|
||||
"""
|
||||
Signal that a new file has been started.
|
||||
|
||||
Warning: As with any data from the client, you should not trust
|
||||
content_length (and sometimes won't even get it).
|
||||
"""
|
||||
self.field_name = field_name
|
||||
self.file_name = file_name
|
||||
self.content_type = content_type
|
||||
self.content_length = content_length
|
||||
self.charset = charset
|
||||
self.content_type_extra = content_type_extra
|
||||
|
||||
def receive_data_chunk(self, raw_data, start):
|
||||
"""
|
||||
Receive data from the streamed upload parser. ``start`` is the position
|
||||
in the file of the chunk.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of FileUploadHandler must provide a receive_data_chunk() method')
|
||||
|
||||
def file_complete(self, file_size):
|
||||
"""
|
||||
Signal that a file has completed. File size corresponds to the actual
|
||||
size accumulated by all the chunks.
|
||||
|
||||
Subclasses should return a valid ``UploadedFile`` object.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of FileUploadHandler must provide a file_complete() method')
|
||||
|
||||
def upload_complete(self):
|
||||
"""
|
||||
Signal that the upload is complete. Subclasses should perform cleanup
|
||||
that is necessary for this handler.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TemporaryFileUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
Upload handler that streams data into a temporary file.
|
||||
"""
|
||||
def new_file(self, *args, **kwargs):
|
||||
"""
|
||||
Create the file object to append to as data is coming in.
|
||||
"""
|
||||
super().new_file(*args, **kwargs)
|
||||
self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset, self.content_type_extra)
|
||||
|
||||
def receive_data_chunk(self, raw_data, start):
|
||||
self.file.write(raw_data)
|
||||
|
||||
def file_complete(self, file_size):
|
||||
self.file.seek(0)
|
||||
self.file.size = file_size
|
||||
return self.file
|
||||
|
||||
|
||||
class MemoryFileUploadHandler(FileUploadHandler):
|
||||
"""
|
||||
File upload handler to stream uploads into memory (used for small files).
|
||||
"""
|
||||
|
||||
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
|
||||
"""
|
||||
Use the content_length to signal whether or not this handler should be
|
||||
used.
|
||||
"""
|
||||
# Check the content-length header to see if we should
|
||||
# If the post is too large, we cannot use the Memory handler.
|
||||
self.activated = content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE
|
||||
|
||||
def new_file(self, *args, **kwargs):
|
||||
super().new_file(*args, **kwargs)
|
||||
if self.activated:
|
||||
self.file = BytesIO()
|
||||
raise StopFutureHandlers()
|
||||
|
||||
def receive_data_chunk(self, raw_data, start):
|
||||
"""Add the data to the BytesIO file."""
|
||||
if self.activated:
|
||||
self.file.write(raw_data)
|
||||
else:
|
||||
return raw_data
|
||||
|
||||
def file_complete(self, file_size):
|
||||
"""Return a file object if this handler is activated."""
|
||||
if not self.activated:
|
||||
return
|
||||
|
||||
self.file.seek(0)
|
||||
return InMemoryUploadedFile(
|
||||
file=self.file,
|
||||
field_name=self.field_name,
|
||||
name=self.file_name,
|
||||
content_type=self.content_type,
|
||||
size=file_size,
|
||||
charset=self.charset,
|
||||
content_type_extra=self.content_type_extra
|
||||
)
|
||||
|
||||
|
||||
def load_handler(path, *args, **kwargs):
|
||||
"""
|
||||
Given a path to a handler, return an instance of that handler.
|
||||
|
||||
E.g.::
|
||||
>>> from django.http import HttpRequest
|
||||
>>> request = HttpRequest()
|
||||
>>> load_handler('django.core.files.uploadhandler.TemporaryFileUploadHandler', request)
|
||||
<TemporaryFileUploadHandler object at 0x...>
|
||||
"""
|
||||
return import_string(path)(*args, **kwargs)
|
||||
52
venv/lib/python3.8/site-packages/django/core/files/utils.py
Normal file
52
venv/lib/python3.8/site-packages/django/core/files/utils.py
Normal file
@@ -0,0 +1,52 @@
|
||||
class FileProxyMixin:
|
||||
"""
|
||||
A mixin class used to forward file methods to an underlaying file
|
||||
object. The internal file object has to be called "file"::
|
||||
|
||||
class FileProxy(FileProxyMixin):
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
"""
|
||||
|
||||
encoding = property(lambda self: self.file.encoding)
|
||||
fileno = property(lambda self: self.file.fileno)
|
||||
flush = property(lambda self: self.file.flush)
|
||||
isatty = property(lambda self: self.file.isatty)
|
||||
newlines = property(lambda self: self.file.newlines)
|
||||
read = property(lambda self: self.file.read)
|
||||
readinto = property(lambda self: self.file.readinto)
|
||||
readline = property(lambda self: self.file.readline)
|
||||
readlines = property(lambda self: self.file.readlines)
|
||||
seek = property(lambda self: self.file.seek)
|
||||
tell = property(lambda self: self.file.tell)
|
||||
truncate = property(lambda self: self.file.truncate)
|
||||
write = property(lambda self: self.file.write)
|
||||
writelines = property(lambda self: self.file.writelines)
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return not self.file or self.file.closed
|
||||
|
||||
def readable(self):
|
||||
if self.closed:
|
||||
return False
|
||||
if hasattr(self.file, 'readable'):
|
||||
return self.file.readable()
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
if self.closed:
|
||||
return False
|
||||
if hasattr(self.file, 'writable'):
|
||||
return self.file.writable()
|
||||
return 'w' in getattr(self.file, 'mode', '')
|
||||
|
||||
def seekable(self):
|
||||
if self.closed:
|
||||
return False
|
||||
if hasattr(self.file, 'seekable'):
|
||||
return self.file.seekable()
|
||||
return True
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.file)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
292
venv/lib/python3.8/site-packages/django/core/handlers/asgi.py
Normal file
292
venv/lib/python3.8/site-packages/django/core/handlers/asgi.py
Normal file
@@ -0,0 +1,292 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.exceptions import RequestAborted, RequestDataTooBig
|
||||
from django.core.handlers import base
|
||||
from django.http import (
|
||||
FileResponse, HttpRequest, HttpResponse, HttpResponseBadRequest,
|
||||
HttpResponseServerError, QueryDict, parse_cookie,
|
||||
)
|
||||
from django.urls import set_script_prefix
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
logger = logging.getLogger('django.request')
|
||||
|
||||
|
||||
class ASGIRequest(HttpRequest):
|
||||
"""
|
||||
Custom request subclass that decodes from an ASGI-standard request dict
|
||||
and wraps request body handling.
|
||||
"""
|
||||
# Number of seconds until a Request gives up on trying to read a request
|
||||
# body and aborts.
|
||||
body_receive_timeout = 60
|
||||
|
||||
def __init__(self, scope, body_file):
|
||||
self.scope = scope
|
||||
self._post_parse_error = False
|
||||
self._read_started = False
|
||||
self.resolver_match = None
|
||||
self.script_name = self.scope.get('root_path', '')
|
||||
if self.script_name and scope['path'].startswith(self.script_name):
|
||||
# TODO: Better is-prefix checking, slash handling?
|
||||
self.path_info = scope['path'][len(self.script_name):]
|
||||
else:
|
||||
self.path_info = scope['path']
|
||||
# The Django path is different from ASGI scope path args, it should
|
||||
# combine with script name.
|
||||
if self.script_name:
|
||||
self.path = '%s/%s' % (
|
||||
self.script_name.rstrip('/'),
|
||||
self.path_info.replace('/', '', 1),
|
||||
)
|
||||
else:
|
||||
self.path = scope['path']
|
||||
# HTTP basics.
|
||||
self.method = self.scope['method'].upper()
|
||||
# Ensure query string is encoded correctly.
|
||||
query_string = self.scope.get('query_string', '')
|
||||
if isinstance(query_string, bytes):
|
||||
query_string = query_string.decode()
|
||||
self.META = {
|
||||
'REQUEST_METHOD': self.method,
|
||||
'QUERY_STRING': query_string,
|
||||
'SCRIPT_NAME': self.script_name,
|
||||
'PATH_INFO': self.path_info,
|
||||
# WSGI-expecting code will need these for a while
|
||||
'wsgi.multithread': True,
|
||||
'wsgi.multiprocess': True,
|
||||
}
|
||||
if self.scope.get('client'):
|
||||
self.META['REMOTE_ADDR'] = self.scope['client'][0]
|
||||
self.META['REMOTE_HOST'] = self.META['REMOTE_ADDR']
|
||||
self.META['REMOTE_PORT'] = self.scope['client'][1]
|
||||
if self.scope.get('server'):
|
||||
self.META['SERVER_NAME'] = self.scope['server'][0]
|
||||
self.META['SERVER_PORT'] = str(self.scope['server'][1])
|
||||
else:
|
||||
self.META['SERVER_NAME'] = 'unknown'
|
||||
self.META['SERVER_PORT'] = '0'
|
||||
# Headers go into META.
|
||||
for name, value in self.scope.get('headers', []):
|
||||
name = name.decode('latin1')
|
||||
if name == 'content-length':
|
||||
corrected_name = 'CONTENT_LENGTH'
|
||||
elif name == 'content-type':
|
||||
corrected_name = 'CONTENT_TYPE'
|
||||
else:
|
||||
corrected_name = 'HTTP_%s' % name.upper().replace('-', '_')
|
||||
# HTTP/2 say only ASCII chars are allowed in headers, but decode
|
||||
# latin1 just in case.
|
||||
value = value.decode('latin1')
|
||||
if corrected_name in self.META:
|
||||
value = self.META[corrected_name] + ',' + value
|
||||
self.META[corrected_name] = value
|
||||
# Pull out request encoding, if provided.
|
||||
self._set_content_type_params(self.META)
|
||||
# Directly assign the body file to be our stream.
|
||||
self._stream = body_file
|
||||
# Other bits.
|
||||
self.resolver_match = None
|
||||
|
||||
@cached_property
|
||||
def GET(self):
|
||||
return QueryDict(self.META['QUERY_STRING'])
|
||||
|
||||
def _get_scheme(self):
|
||||
return self.scope.get('scheme') or super()._get_scheme()
|
||||
|
||||
def _get_post(self):
|
||||
if not hasattr(self, '_post'):
|
||||
self._load_post_and_files()
|
||||
return self._post
|
||||
|
||||
def _set_post(self, post):
|
||||
self._post = post
|
||||
|
||||
def _get_files(self):
|
||||
if not hasattr(self, '_files'):
|
||||
self._load_post_and_files()
|
||||
return self._files
|
||||
|
||||
POST = property(_get_post, _set_post)
|
||||
FILES = property(_get_files)
|
||||
|
||||
@cached_property
|
||||
def COOKIES(self):
|
||||
return parse_cookie(self.META.get('HTTP_COOKIE', ''))
|
||||
|
||||
|
||||
class ASGIHandler(base.BaseHandler):
|
||||
"""Handler for ASGI requests."""
|
||||
request_class = ASGIRequest
|
||||
# Size to chunk response bodies into for multiple response messages.
|
||||
chunk_size = 2 ** 16
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.load_middleware()
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
"""
|
||||
Async entrypoint - parses the request and hands off to get_response.
|
||||
"""
|
||||
# Serve only HTTP connections.
|
||||
# FIXME: Allow to override this.
|
||||
if scope['type'] != 'http':
|
||||
raise ValueError(
|
||||
'Django can only handle ASGI/HTTP connections, not %s.'
|
||||
% scope['type']
|
||||
)
|
||||
# Receive the HTTP request body as a stream object.
|
||||
try:
|
||||
body_file = await self.read_body(receive)
|
||||
except RequestAborted:
|
||||
return
|
||||
# Request is complete and can be served.
|
||||
set_script_prefix(self.get_script_prefix(scope))
|
||||
await sync_to_async(signals.request_started.send)(sender=self.__class__, scope=scope)
|
||||
# Get the request and check for basic issues.
|
||||
request, error_response = self.create_request(scope, body_file)
|
||||
if request is None:
|
||||
await self.send_response(error_response, send)
|
||||
return
|
||||
# Get the response, using a threadpool via sync_to_async, if needed.
|
||||
if asyncio.iscoroutinefunction(self.get_response):
|
||||
response = await self.get_response(request)
|
||||
else:
|
||||
# If get_response is synchronous, run it non-blocking.
|
||||
response = await sync_to_async(self.get_response)(request)
|
||||
response._handler_class = self.__class__
|
||||
# Increase chunk size on file responses (ASGI servers handles low-level
|
||||
# chunking).
|
||||
if isinstance(response, FileResponse):
|
||||
response.block_size = self.chunk_size
|
||||
# Send the response.
|
||||
await self.send_response(response, send)
|
||||
|
||||
async def read_body(self, receive):
|
||||
"""Reads a HTTP body from an ASGI connection."""
|
||||
# Use the tempfile that auto rolls-over to a disk file as it fills up.
|
||||
body_file = tempfile.SpooledTemporaryFile(max_size=settings.FILE_UPLOAD_MAX_MEMORY_SIZE, mode='w+b')
|
||||
while True:
|
||||
message = await receive()
|
||||
if message['type'] == 'http.disconnect':
|
||||
# Early client disconnect.
|
||||
raise RequestAborted()
|
||||
# Add a body chunk from the message, if provided.
|
||||
if 'body' in message:
|
||||
body_file.write(message['body'])
|
||||
# Quit out if that's the end.
|
||||
if not message.get('more_body', False):
|
||||
break
|
||||
body_file.seek(0)
|
||||
return body_file
|
||||
|
||||
def create_request(self, scope, body_file):
|
||||
"""
|
||||
Create the Request object and returns either (request, None) or
|
||||
(None, response) if there is an error response.
|
||||
"""
|
||||
try:
|
||||
return self.request_class(scope, body_file), None
|
||||
except UnicodeDecodeError:
|
||||
logger.warning(
|
||||
'Bad Request (UnicodeDecodeError)',
|
||||
exc_info=sys.exc_info(),
|
||||
extra={'status_code': 400},
|
||||
)
|
||||
return None, HttpResponseBadRequest()
|
||||
except RequestDataTooBig:
|
||||
return None, HttpResponse('413 Payload too large', status=413)
|
||||
|
||||
def handle_uncaught_exception(self, request, resolver, exc_info):
|
||||
"""Last-chance handler for exceptions."""
|
||||
# There's no WSGI server to catch the exception further up
|
||||
# if this fails, so translate it into a plain text response.
|
||||
try:
|
||||
return super().handle_uncaught_exception(request, resolver, exc_info)
|
||||
except Exception:
|
||||
return HttpResponseServerError(
|
||||
traceback.format_exc() if settings.DEBUG else 'Internal Server Error',
|
||||
content_type='text/plain',
|
||||
)
|
||||
|
||||
async def send_response(self, response, send):
|
||||
"""Encode and send a response out over ASGI."""
|
||||
# Collect cookies into headers. Have to preserve header case as there
|
||||
# are some non-RFC compliant clients that require e.g. Content-Type.
|
||||
response_headers = []
|
||||
for header, value in response.items():
|
||||
if isinstance(header, str):
|
||||
header = header.encode('ascii')
|
||||
if isinstance(value, str):
|
||||
value = value.encode('latin1')
|
||||
response_headers.append((bytes(header), bytes(value)))
|
||||
for c in response.cookies.values():
|
||||
response_headers.append(
|
||||
(b'Set-Cookie', c.output(header='').encode('ascii').strip())
|
||||
)
|
||||
# Initial response message.
|
||||
await send({
|
||||
'type': 'http.response.start',
|
||||
'status': response.status_code,
|
||||
'headers': response_headers,
|
||||
})
|
||||
# Streaming responses need to be pinned to their iterator.
|
||||
if response.streaming:
|
||||
# Access `__iter__` and not `streaming_content` directly in case
|
||||
# it has been overridden in a subclass.
|
||||
for part in response:
|
||||
for chunk, _ in self.chunk_bytes(part):
|
||||
await send({
|
||||
'type': 'http.response.body',
|
||||
'body': chunk,
|
||||
# Ignore "more" as there may be more parts; instead,
|
||||
# use an empty final closing message with False.
|
||||
'more_body': True,
|
||||
})
|
||||
# Final closing message.
|
||||
await send({'type': 'http.response.body'})
|
||||
# Other responses just need chunking.
|
||||
else:
|
||||
# Yield chunks of response.
|
||||
for chunk, last in self.chunk_bytes(response.content):
|
||||
await send({
|
||||
'type': 'http.response.body',
|
||||
'body': chunk,
|
||||
'more_body': not last,
|
||||
})
|
||||
response.close()
|
||||
|
||||
@classmethod
|
||||
def chunk_bytes(cls, data):
|
||||
"""
|
||||
Chunks some data up so it can be sent in reasonable size messages.
|
||||
Yields (chunk, last_chunk) tuples.
|
||||
"""
|
||||
position = 0
|
||||
if not data:
|
||||
yield data, True
|
||||
return
|
||||
while position < len(data):
|
||||
yield (
|
||||
data[position:position + cls.chunk_size],
|
||||
(position + cls.chunk_size) >= len(data),
|
||||
)
|
||||
position += cls.chunk_size
|
||||
|
||||
def get_script_prefix(self, scope):
|
||||
"""
|
||||
Return the script prefix to use from either the scope or a setting.
|
||||
"""
|
||||
if settings.FORCE_SCRIPT_NAME:
|
||||
return settings.FORCE_SCRIPT_NAME
|
||||
return scope.get('root_path', '') or ''
|
||||
166
venv/lib/python3.8/site-packages/django/core/handlers/base.py
Normal file
166
venv/lib/python3.8/site-packages/django/core/handlers/base.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import logging
|
||||
import types
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
|
||||
from django.core.signals import request_finished
|
||||
from django.db import connections, transaction
|
||||
from django.urls import get_resolver, set_urlconf
|
||||
from django.utils.log import log_response
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from .exception import convert_exception_to_response
|
||||
|
||||
logger = logging.getLogger('django.request')
|
||||
|
||||
|
||||
class BaseHandler:
|
||||
_view_middleware = None
|
||||
_template_response_middleware = None
|
||||
_exception_middleware = None
|
||||
_middleware_chain = None
|
||||
|
||||
def load_middleware(self):
|
||||
"""
|
||||
Populate middleware lists from settings.MIDDLEWARE.
|
||||
|
||||
Must be called after the environment is fixed (see __call__ in subclasses).
|
||||
"""
|
||||
self._view_middleware = []
|
||||
self._template_response_middleware = []
|
||||
self._exception_middleware = []
|
||||
|
||||
handler = convert_exception_to_response(self._get_response)
|
||||
for middleware_path in reversed(settings.MIDDLEWARE):
|
||||
middleware = import_string(middleware_path)
|
||||
try:
|
||||
mw_instance = middleware(handler)
|
||||
except MiddlewareNotUsed as exc:
|
||||
if settings.DEBUG:
|
||||
if str(exc):
|
||||
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
|
||||
else:
|
||||
logger.debug('MiddlewareNotUsed: %r', middleware_path)
|
||||
continue
|
||||
|
||||
if mw_instance is None:
|
||||
raise ImproperlyConfigured(
|
||||
'Middleware factory %s returned None.' % middleware_path
|
||||
)
|
||||
|
||||
if hasattr(mw_instance, 'process_view'):
|
||||
self._view_middleware.insert(0, mw_instance.process_view)
|
||||
if hasattr(mw_instance, 'process_template_response'):
|
||||
self._template_response_middleware.append(mw_instance.process_template_response)
|
||||
if hasattr(mw_instance, 'process_exception'):
|
||||
self._exception_middleware.append(mw_instance.process_exception)
|
||||
|
||||
handler = convert_exception_to_response(mw_instance)
|
||||
|
||||
# We only assign to this when initialization is complete as it is used
|
||||
# as a flag for initialization being complete.
|
||||
self._middleware_chain = handler
|
||||
|
||||
def make_view_atomic(self, view):
|
||||
non_atomic_requests = getattr(view, '_non_atomic_requests', set())
|
||||
for db in connections.all():
|
||||
if db.settings_dict['ATOMIC_REQUESTS'] and db.alias not in non_atomic_requests:
|
||||
view = transaction.atomic(using=db.alias)(view)
|
||||
return view
|
||||
|
||||
def get_response(self, request):
|
||||
"""Return an HttpResponse object for the given HttpRequest."""
|
||||
# Setup default url resolver for this thread
|
||||
set_urlconf(settings.ROOT_URLCONF)
|
||||
response = self._middleware_chain(request)
|
||||
response._resource_closers.append(request.close)
|
||||
if response.status_code >= 400:
|
||||
log_response(
|
||||
'%s: %s', response.reason_phrase, request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
)
|
||||
return response
|
||||
|
||||
def _get_response(self, request):
|
||||
"""
|
||||
Resolve and call the view, then apply view, exception, and
|
||||
template_response middleware. This method is everything that happens
|
||||
inside the request/response middleware.
|
||||
"""
|
||||
response = None
|
||||
|
||||
if hasattr(request, 'urlconf'):
|
||||
urlconf = request.urlconf
|
||||
set_urlconf(urlconf)
|
||||
resolver = get_resolver(urlconf)
|
||||
else:
|
||||
resolver = get_resolver()
|
||||
|
||||
resolver_match = resolver.resolve(request.path_info)
|
||||
callback, callback_args, callback_kwargs = resolver_match
|
||||
request.resolver_match = resolver_match
|
||||
|
||||
# Apply view middleware
|
||||
for middleware_method in self._view_middleware:
|
||||
response = middleware_method(request, callback, callback_args, callback_kwargs)
|
||||
if response:
|
||||
break
|
||||
|
||||
if response is None:
|
||||
wrapped_callback = self.make_view_atomic(callback)
|
||||
try:
|
||||
response = wrapped_callback(request, *callback_args, **callback_kwargs)
|
||||
except Exception as e:
|
||||
response = self.process_exception_by_middleware(e, request)
|
||||
|
||||
# Complain if the view returned None (a common error).
|
||||
if response is None:
|
||||
if isinstance(callback, types.FunctionType): # FBV
|
||||
view_name = callback.__name__
|
||||
else: # CBV
|
||||
view_name = callback.__class__.__name__ + '.__call__'
|
||||
|
||||
raise ValueError(
|
||||
"The view %s.%s didn't return an HttpResponse object. It "
|
||||
"returned None instead." % (callback.__module__, view_name)
|
||||
)
|
||||
|
||||
# If the response supports deferred rendering, apply template
|
||||
# response middleware and then render the response
|
||||
elif hasattr(response, 'render') and callable(response.render):
|
||||
for middleware_method in self._template_response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
# Complain if the template response middleware returned None (a common error).
|
||||
if response is None:
|
||||
raise ValueError(
|
||||
"%s.process_template_response didn't return an "
|
||||
"HttpResponse object. It returned None instead."
|
||||
% (middleware_method.__self__.__class__.__name__)
|
||||
)
|
||||
|
||||
try:
|
||||
response = response.render()
|
||||
except Exception as e:
|
||||
response = self.process_exception_by_middleware(e, request)
|
||||
|
||||
return response
|
||||
|
||||
def process_exception_by_middleware(self, exception, request):
|
||||
"""
|
||||
Pass the exception to the exception middleware. If no middleware
|
||||
return a response for this exception, raise it.
|
||||
"""
|
||||
for middleware_method in self._exception_middleware:
|
||||
response = middleware_method(request, exception)
|
||||
if response:
|
||||
return response
|
||||
raise
|
||||
|
||||
|
||||
def reset_urlconf(sender, **kwargs):
|
||||
"""Reset the URLconf after each request is finished."""
|
||||
set_urlconf(None)
|
||||
|
||||
|
||||
request_finished.connect(reset_urlconf)
|
||||
@@ -0,0 +1,129 @@
|
||||
import logging
|
||||
import sys
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.exceptions import (
|
||||
PermissionDenied, RequestDataTooBig, SuspiciousOperation,
|
||||
TooManyFieldsSent,
|
||||
)
|
||||
from django.http import Http404
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.urls import get_resolver, get_urlconf
|
||||
from django.utils.log import log_response
|
||||
from django.views import debug
|
||||
|
||||
|
||||
def convert_exception_to_response(get_response):
|
||||
"""
|
||||
Wrap the given get_response callable in exception-to-response conversion.
|
||||
|
||||
All exceptions will be converted. All known 4xx exceptions (Http404,
|
||||
PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
|
||||
converted to the appropriate response, and all other exceptions will be
|
||||
converted to 500 responses.
|
||||
|
||||
This decorator is automatically applied to all middleware to ensure that
|
||||
no middleware leaks an exception and that the next middleware in the stack
|
||||
can rely on getting a response instead of an exception.
|
||||
"""
|
||||
@wraps(get_response)
|
||||
def inner(request):
|
||||
try:
|
||||
response = get_response(request)
|
||||
except Exception as exc:
|
||||
response = response_for_exception(request, exc)
|
||||
return response
|
||||
return inner
|
||||
|
||||
|
||||
def response_for_exception(request, exc):
|
||||
if isinstance(exc, Http404):
|
||||
if settings.DEBUG:
|
||||
response = debug.technical_404_response(request, exc)
|
||||
else:
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
|
||||
|
||||
elif isinstance(exc, PermissionDenied):
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
|
||||
log_response(
|
||||
'Forbidden (Permission denied): %s', request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
exc_info=sys.exc_info(),
|
||||
)
|
||||
|
||||
elif isinstance(exc, MultiPartParserError):
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
|
||||
log_response(
|
||||
'Bad request (Unable to parse request body): %s', request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
exc_info=sys.exc_info(),
|
||||
)
|
||||
|
||||
elif isinstance(exc, SuspiciousOperation):
|
||||
if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)):
|
||||
# POST data can't be accessed again, otherwise the original
|
||||
# exception would be raised.
|
||||
request._mark_post_parse_error()
|
||||
|
||||
# The request logger receives events for any problematic request
|
||||
# The security logger receives events for all SuspiciousOperations
|
||||
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
|
||||
security_logger.error(
|
||||
str(exc),
|
||||
extra={'status_code': 400, 'request': request},
|
||||
)
|
||||
if settings.DEBUG:
|
||||
response = debug.technical_500_response(request, *sys.exc_info(), status_code=400)
|
||||
else:
|
||||
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
|
||||
|
||||
elif isinstance(exc, SystemExit):
|
||||
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
|
||||
raise
|
||||
|
||||
else:
|
||||
signals.got_request_exception.send(sender=None, request=request)
|
||||
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
|
||||
log_response(
|
||||
'%s: %s', response.reason_phrase, request.path,
|
||||
response=response,
|
||||
request=request,
|
||||
exc_info=sys.exc_info(),
|
||||
)
|
||||
|
||||
# Force a TemplateResponse to be rendered.
|
||||
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
|
||||
response = response.render()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_exception_response(request, resolver, status_code, exception):
|
||||
try:
|
||||
callback, param_dict = resolver.resolve_error_handler(status_code)
|
||||
response = callback(request, **{**param_dict, 'exception': exception})
|
||||
except Exception:
|
||||
signals.got_request_exception.send(sender=None, request=request)
|
||||
response = handle_uncaught_exception(request, resolver, sys.exc_info())
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def handle_uncaught_exception(request, resolver, exc_info):
|
||||
"""
|
||||
Processing for any otherwise uncaught exceptions (those that will
|
||||
generate HTTP 500 responses).
|
||||
"""
|
||||
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
|
||||
raise
|
||||
|
||||
if settings.DEBUG:
|
||||
return debug.technical_500_response(request, *exc_info)
|
||||
|
||||
# Return an HttpResponse that displays a friendly error message.
|
||||
callback, param_dict = resolver.resolve_error_handler(500)
|
||||
return callback(request, **param_dict)
|
||||
210
venv/lib/python3.8/site-packages/django/core/handlers/wsgi.py
Normal file
210
venv/lib/python3.8/site-packages/django/core/handlers/wsgi.py
Normal file
@@ -0,0 +1,210 @@
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.handlers import base
|
||||
from django.http import HttpRequest, QueryDict, parse_cookie
|
||||
from django.urls import set_script_prefix
|
||||
from django.utils.encoding import repercent_broken_unicode
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
_slashes_re = re.compile(br'/+')
|
||||
|
||||
|
||||
class LimitedStream:
|
||||
"""Wrap another stream to disallow reading it past a number of bytes."""
|
||||
def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
|
||||
self.stream = stream
|
||||
self.remaining = limit
|
||||
self.buffer = b''
|
||||
self.buf_size = buf_size
|
||||
|
||||
def _read_limited(self, size=None):
|
||||
if size is None or size > self.remaining:
|
||||
size = self.remaining
|
||||
if size == 0:
|
||||
return b''
|
||||
result = self.stream.read(size)
|
||||
self.remaining -= len(result)
|
||||
return result
|
||||
|
||||
def read(self, size=None):
|
||||
if size is None:
|
||||
result = self.buffer + self._read_limited()
|
||||
self.buffer = b''
|
||||
elif size < len(self.buffer):
|
||||
result = self.buffer[:size]
|
||||
self.buffer = self.buffer[size:]
|
||||
else: # size >= len(self.buffer)
|
||||
result = self.buffer + self._read_limited(size - len(self.buffer))
|
||||
self.buffer = b''
|
||||
return result
|
||||
|
||||
def readline(self, size=None):
|
||||
while b'\n' not in self.buffer and \
|
||||
(size is None or len(self.buffer) < size):
|
||||
if size:
|
||||
# since size is not None here, len(self.buffer) < size
|
||||
chunk = self._read_limited(size - len(self.buffer))
|
||||
else:
|
||||
chunk = self._read_limited()
|
||||
if not chunk:
|
||||
break
|
||||
self.buffer += chunk
|
||||
sio = BytesIO(self.buffer)
|
||||
if size:
|
||||
line = sio.readline(size)
|
||||
else:
|
||||
line = sio.readline()
|
||||
self.buffer = sio.read()
|
||||
return line
|
||||
|
||||
|
||||
class WSGIRequest(HttpRequest):
|
||||
def __init__(self, environ):
|
||||
script_name = get_script_name(environ)
|
||||
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
|
||||
# trailing slash), operate as if '/' was requested.
|
||||
path_info = get_path_info(environ) or '/'
|
||||
self.environ = environ
|
||||
self.path_info = path_info
|
||||
# be careful to only replace the first slash in the path because of
|
||||
# http://test/something and http://test//something being different as
|
||||
# stated in https://www.ietf.org/rfc/rfc2396.txt
|
||||
self.path = '%s/%s' % (script_name.rstrip('/'),
|
||||
path_info.replace('/', '', 1))
|
||||
self.META = environ
|
||||
self.META['PATH_INFO'] = path_info
|
||||
self.META['SCRIPT_NAME'] = script_name
|
||||
self.method = environ['REQUEST_METHOD'].upper()
|
||||
# Set content_type, content_params, and encoding.
|
||||
self._set_content_type_params(environ)
|
||||
try:
|
||||
content_length = int(environ.get('CONTENT_LENGTH'))
|
||||
except (ValueError, TypeError):
|
||||
content_length = 0
|
||||
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
|
||||
self._read_started = False
|
||||
self.resolver_match = None
|
||||
|
||||
def _get_scheme(self):
|
||||
return self.environ.get('wsgi.url_scheme')
|
||||
|
||||
@cached_property
|
||||
def GET(self):
|
||||
# The WSGI spec says 'QUERY_STRING' may be absent.
|
||||
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
|
||||
return QueryDict(raw_query_string, encoding=self._encoding)
|
||||
|
||||
def _get_post(self):
|
||||
if not hasattr(self, '_post'):
|
||||
self._load_post_and_files()
|
||||
return self._post
|
||||
|
||||
def _set_post(self, post):
|
||||
self._post = post
|
||||
|
||||
@cached_property
|
||||
def COOKIES(self):
|
||||
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
|
||||
return parse_cookie(raw_cookie)
|
||||
|
||||
@property
|
||||
def FILES(self):
|
||||
if not hasattr(self, '_files'):
|
||||
self._load_post_and_files()
|
||||
return self._files
|
||||
|
||||
POST = property(_get_post, _set_post)
|
||||
|
||||
|
||||
class WSGIHandler(base.BaseHandler):
|
||||
request_class = WSGIRequest
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.load_middleware()
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
set_script_prefix(get_script_name(environ))
|
||||
signals.request_started.send(sender=self.__class__, environ=environ)
|
||||
request = self.request_class(environ)
|
||||
response = self.get_response(request)
|
||||
|
||||
response._handler_class = self.__class__
|
||||
|
||||
status = '%d %s' % (response.status_code, response.reason_phrase)
|
||||
response_headers = [
|
||||
*response.items(),
|
||||
*(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
|
||||
]
|
||||
start_response(status, response_headers)
|
||||
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
|
||||
# If `wsgi.file_wrapper` is used the WSGI server does not call
|
||||
# .close on the response, but on the file wrapper. Patch it to use
|
||||
# response.close instead which takes care of closing all files.
|
||||
response.file_to_stream.close = response.close
|
||||
response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size)
|
||||
return response
|
||||
|
||||
|
||||
def get_path_info(environ):
|
||||
"""Return the HTTP request's PATH_INFO as a string."""
|
||||
path_info = get_bytes_from_wsgi(environ, 'PATH_INFO', '/')
|
||||
|
||||
return repercent_broken_unicode(path_info).decode()
|
||||
|
||||
|
||||
def get_script_name(environ):
|
||||
"""
|
||||
Return the equivalent of the HTTP request's SCRIPT_NAME environment
|
||||
variable. If Apache mod_rewrite is used, return what would have been
|
||||
the script name prior to any rewriting (so it's the script name as seen
|
||||
from the client's perspective), unless the FORCE_SCRIPT_NAME setting is
|
||||
set (to anything).
|
||||
"""
|
||||
if settings.FORCE_SCRIPT_NAME is not None:
|
||||
return settings.FORCE_SCRIPT_NAME
|
||||
|
||||
# If Apache's mod_rewrite had a whack at the URL, Apache set either
|
||||
# SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
|
||||
# rewrites. Unfortunately not every Web server (lighttpd!) passes this
|
||||
# information through all the time, so FORCE_SCRIPT_NAME, above, is still
|
||||
# needed.
|
||||
script_url = get_bytes_from_wsgi(environ, 'SCRIPT_URL', '') or get_bytes_from_wsgi(environ, 'REDIRECT_URL', '')
|
||||
|
||||
if script_url:
|
||||
if b'//' in script_url:
|
||||
# mod_wsgi squashes multiple successive slashes in PATH_INFO,
|
||||
# do the same with script_url before manipulating paths (#17133).
|
||||
script_url = _slashes_re.sub(b'/', script_url)
|
||||
path_info = get_bytes_from_wsgi(environ, 'PATH_INFO', '')
|
||||
script_name = script_url[:-len(path_info)] if path_info else script_url
|
||||
else:
|
||||
script_name = get_bytes_from_wsgi(environ, 'SCRIPT_NAME', '')
|
||||
|
||||
return script_name.decode()
|
||||
|
||||
|
||||
def get_bytes_from_wsgi(environ, key, default):
|
||||
"""
|
||||
Get a value from the WSGI environ dictionary as bytes.
|
||||
|
||||
key and default should be strings.
|
||||
"""
|
||||
value = environ.get(key, default)
|
||||
# Non-ASCII values in the WSGI environ are arbitrarily decoded with
|
||||
# ISO-8859-1. This is wrong for Django websites where UTF-8 is the default.
|
||||
# Re-encode to recover the original bytestring.
|
||||
return value.encode('iso-8859-1')
|
||||
|
||||
|
||||
def get_str_from_wsgi(environ, key, default):
|
||||
"""
|
||||
Get a value from the WSGI environ dictionary as str.
|
||||
|
||||
key and default should be str objects.
|
||||
"""
|
||||
value = get_bytes_from_wsgi(environ, key, default)
|
||||
return value.decode(errors='replace')
|
||||
120
venv/lib/python3.8/site-packages/django/core/mail/__init__.py
Normal file
120
venv/lib/python3.8/site-packages/django/core/mail/__init__.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Tools for sending email.
|
||||
"""
|
||||
from django.conf import settings
|
||||
# Imported for backwards compatibility and for the sake
|
||||
# of a cleaner namespace. These symbols used to be in
|
||||
# django/core/mail.py before the introduction of email
|
||||
# backends and the subsequent reorganization (See #10355)
|
||||
from django.core.mail.message import (
|
||||
DEFAULT_ATTACHMENT_MIME_TYPE, BadHeaderError, EmailMessage,
|
||||
EmailMultiAlternatives, SafeMIMEMultipart, SafeMIMEText,
|
||||
forbid_multi_line_headers, make_msgid,
|
||||
)
|
||||
from django.core.mail.utils import DNS_NAME, CachedDnsName
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
__all__ = [
|
||||
'CachedDnsName', 'DNS_NAME', 'EmailMessage', 'EmailMultiAlternatives',
|
||||
'SafeMIMEText', 'SafeMIMEMultipart', 'DEFAULT_ATTACHMENT_MIME_TYPE',
|
||||
'make_msgid', 'BadHeaderError', 'forbid_multi_line_headers',
|
||||
'get_connection', 'send_mail', 'send_mass_mail', 'mail_admins',
|
||||
'mail_managers',
|
||||
]
|
||||
|
||||
|
||||
def get_connection(backend=None, fail_silently=False, **kwds):
|
||||
"""Load an email backend and return an instance of it.
|
||||
|
||||
If backend is None (default), use settings.EMAIL_BACKEND.
|
||||
|
||||
Both fail_silently and other keyword arguments are used in the
|
||||
constructor of the backend.
|
||||
"""
|
||||
klass = import_string(backend or settings.EMAIL_BACKEND)
|
||||
return klass(fail_silently=fail_silently, **kwds)
|
||||
|
||||
|
||||
def send_mail(subject, message, from_email, recipient_list,
|
||||
fail_silently=False, auth_user=None, auth_password=None,
|
||||
connection=None, html_message=None):
|
||||
"""
|
||||
Easy wrapper for sending a single message to a recipient list. All members
|
||||
of the recipient list will see the other recipients in the 'To' field.
|
||||
|
||||
If auth_user is None, use the EMAIL_HOST_USER setting.
|
||||
If auth_password is None, use the EMAIL_HOST_PASSWORD setting.
|
||||
|
||||
Note: The API for this method is frozen. New code wanting to extend the
|
||||
functionality should use the EmailMessage class directly.
|
||||
"""
|
||||
connection = connection or get_connection(
|
||||
username=auth_user,
|
||||
password=auth_password,
|
||||
fail_silently=fail_silently,
|
||||
)
|
||||
mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, connection=connection)
|
||||
if html_message:
|
||||
mail.attach_alternative(html_message, 'text/html')
|
||||
|
||||
return mail.send()
|
||||
|
||||
|
||||
def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
|
||||
auth_password=None, connection=None):
|
||||
"""
|
||||
Given a datatuple of (subject, message, from_email, recipient_list), send
|
||||
each message to each recipient list. Return the number of emails sent.
|
||||
|
||||
If from_email is None, use the DEFAULT_FROM_EMAIL setting.
|
||||
If auth_user and auth_password are set, use them to log in.
|
||||
If auth_user is None, use the EMAIL_HOST_USER setting.
|
||||
If auth_password is None, use the EMAIL_HOST_PASSWORD setting.
|
||||
|
||||
Note: The API for this method is frozen. New code wanting to extend the
|
||||
functionality should use the EmailMessage class directly.
|
||||
"""
|
||||
connection = connection or get_connection(
|
||||
username=auth_user,
|
||||
password=auth_password,
|
||||
fail_silently=fail_silently,
|
||||
)
|
||||
messages = [
|
||||
EmailMessage(subject, message, sender, recipient, connection=connection)
|
||||
for subject, message, sender, recipient in datatuple
|
||||
]
|
||||
return connection.send_messages(messages)
|
||||
|
||||
|
||||
def mail_admins(subject, message, fail_silently=False, connection=None,
|
||||
html_message=None):
|
||||
"""Send a message to the admins, as defined by the ADMINS setting."""
|
||||
if not settings.ADMINS:
|
||||
return
|
||||
if not all(isinstance(a, (list, tuple)) and len(a) == 2 for a in settings.ADMINS):
|
||||
raise ValueError('The ADMINS setting must be a list of 2-tuples.')
|
||||
mail = EmailMultiAlternatives(
|
||||
'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message,
|
||||
settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS],
|
||||
connection=connection,
|
||||
)
|
||||
if html_message:
|
||||
mail.attach_alternative(html_message, 'text/html')
|
||||
mail.send(fail_silently=fail_silently)
|
||||
|
||||
|
||||
def mail_managers(subject, message, fail_silently=False, connection=None,
|
||||
html_message=None):
|
||||
"""Send a message to the managers, as defined by the MANAGERS setting."""
|
||||
if not settings.MANAGERS:
|
||||
return
|
||||
if not all(isinstance(a, (list, tuple)) and len(a) == 2 for a in settings.MANAGERS):
|
||||
raise ValueError('The MANAGERS setting must be a list of 2-tuples.')
|
||||
mail = EmailMultiAlternatives(
|
||||
'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message,
|
||||
settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS],
|
||||
connection=connection,
|
||||
)
|
||||
if html_message:
|
||||
mail.attach_alternative(html_message, 'text/html')
|
||||
mail.send(fail_silently=fail_silently)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
# Mail backends shipped with Django.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
"""Base email backend class."""
|
||||
|
||||
|
||||
class BaseEmailBackend:
|
||||
"""
|
||||
Base class for email backend implementations.
|
||||
|
||||
Subclasses must at least overwrite send_messages().
|
||||
|
||||
open() and close() can be called indirectly by using a backend object as a
|
||||
context manager:
|
||||
|
||||
with backend as connection:
|
||||
# do something with connection
|
||||
pass
|
||||
"""
|
||||
def __init__(self, fail_silently=False, **kwargs):
|
||||
self.fail_silently = fail_silently
|
||||
|
||||
def open(self):
|
||||
"""
|
||||
Open a network connection.
|
||||
|
||||
This method can be overwritten by backend implementations to
|
||||
open a network connection.
|
||||
|
||||
It's up to the backend implementation to track the status of
|
||||
a network connection if it's needed by the backend.
|
||||
|
||||
This method can be called by applications to force a single
|
||||
network connection to be used when sending mails. See the
|
||||
send_messages() method of the SMTP backend for a reference
|
||||
implementation.
|
||||
|
||||
The default implementation does nothing.
|
||||
"""
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""Close a network connection."""
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
try:
|
||||
self.open()
|
||||
except Exception:
|
||||
self.close()
|
||||
raise
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def send_messages(self, email_messages):
|
||||
"""
|
||||
Send one or more EmailMessage objects and return the number of email
|
||||
messages sent.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseEmailBackend must override send_messages() method')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user