Refs #27656 -- Updated remaining docstring verbs according to PEP 257.
This commit is contained in:
parent
6ae1b04fb5
commit
86de930f41
|
@ -8,15 +8,13 @@ MODELS_MODULE_NAME = 'models'
|
||||||
|
|
||||||
|
|
||||||
class AppConfig:
|
class AppConfig:
|
||||||
"""
|
"""Class representing a Django application and its configuration."""
|
||||||
Class representing a Django application and its configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app_name, app_module):
|
def __init__(self, app_name, app_module):
|
||||||
# Full Python path to the application eg. 'django.contrib.admin'.
|
# Full Python path to the application e.g. 'django.contrib.admin'.
|
||||||
self.name = app_name
|
self.name = app_name
|
||||||
|
|
||||||
# Root module for the application eg. <module 'django.contrib.admin'
|
# Root module for the application e.g. <module 'django.contrib.admin'
|
||||||
# from 'django/contrib/admin/__init__.py'>.
|
# from 'django/contrib/admin/__init__.py'>.
|
||||||
self.module = app_module
|
self.module = app_module
|
||||||
|
|
||||||
|
@ -27,21 +25,21 @@ class AppConfig:
|
||||||
# The following attributes could be defined at the class level in a
|
# The following attributes could be defined at the class level in a
|
||||||
# subclass, hence the test-and-set pattern.
|
# subclass, hence the test-and-set pattern.
|
||||||
|
|
||||||
# Last component of the Python path to the application eg. 'admin'.
|
# Last component of the Python path to the application e.g. 'admin'.
|
||||||
# This value must be unique across a Django project.
|
# This value must be unique across a Django project.
|
||||||
if not hasattr(self, 'label'):
|
if not hasattr(self, 'label'):
|
||||||
self.label = app_name.rpartition(".")[2]
|
self.label = app_name.rpartition(".")[2]
|
||||||
|
|
||||||
# Human-readable name for the application eg. "Admin".
|
# Human-readable name for the application e.g. "Admin".
|
||||||
if not hasattr(self, 'verbose_name'):
|
if not hasattr(self, 'verbose_name'):
|
||||||
self.verbose_name = self.label.title()
|
self.verbose_name = self.label.title()
|
||||||
|
|
||||||
# Filesystem path to the application directory eg.
|
# Filesystem path to the application directory e.g.
|
||||||
# '/path/to/django/contrib/admin'.
|
# '/path/to/django/contrib/admin'.
|
||||||
if not hasattr(self, 'path'):
|
if not hasattr(self, 'path'):
|
||||||
self.path = self._path_from_module(app_module)
|
self.path = self._path_from_module(app_module)
|
||||||
|
|
||||||
# Module containing models eg. <module 'django.contrib.admin.models'
|
# Module containing models e.g. <module 'django.contrib.admin.models'
|
||||||
# from 'django/contrib/admin/models.py'>. Set by import_models().
|
# from 'django/contrib/admin/models.py'>. Set by import_models().
|
||||||
# None if the application doesn't have a models module.
|
# None if the application doesn't have a models module.
|
||||||
self.models_module = None
|
self.models_module = None
|
||||||
|
@ -155,9 +153,9 @@ class AppConfig:
|
||||||
|
|
||||||
def get_model(self, model_name, require_ready=True):
|
def get_model(self, model_name, require_ready=True):
|
||||||
"""
|
"""
|
||||||
Returns the model with the given case-insensitive model_name.
|
Return the model with the given case-insensitive model_name.
|
||||||
|
|
||||||
Raises LookupError if no model exists with this name.
|
Raise LookupError if no model exists with this name.
|
||||||
"""
|
"""
|
||||||
if require_ready:
|
if require_ready:
|
||||||
self.apps.check_models_ready()
|
self.apps.check_models_ready()
|
||||||
|
@ -171,7 +169,7 @@ class AppConfig:
|
||||||
|
|
||||||
def get_models(self, include_auto_created=False, include_swapped=False):
|
def get_models(self, include_auto_created=False, include_swapped=False):
|
||||||
"""
|
"""
|
||||||
Returns an iterable of models.
|
Return an iterable of models.
|
||||||
|
|
||||||
By default, the following models aren't included:
|
By default, the following models aren't included:
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Apps:
|
||||||
"""
|
"""
|
||||||
A registry that stores the configuration of installed applications.
|
A registry that stores the configuration of installed applications.
|
||||||
|
|
||||||
It also keeps track of models eg. to provide reverse-relations.
|
It also keeps track of models, e.g. to provide reverse relations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, installed_apps=()):
|
def __init__(self, installed_apps=()):
|
||||||
|
@ -58,11 +58,11 @@ class Apps:
|
||||||
|
|
||||||
def populate(self, installed_apps=None):
|
def populate(self, installed_apps=None):
|
||||||
"""
|
"""
|
||||||
Loads application configurations and models.
|
Load application configurations and models.
|
||||||
|
|
||||||
This method imports each application module and then each model module.
|
Import each application module and then each model module.
|
||||||
|
|
||||||
It is thread safe and idempotent, but not reentrant.
|
It is thread-safe and idempotent, but not reentrant.
|
||||||
"""
|
"""
|
||||||
if self.ready:
|
if self.ready:
|
||||||
return
|
return
|
||||||
|
@ -122,31 +122,25 @@ class Apps:
|
||||||
self.ready = True
|
self.ready = True
|
||||||
|
|
||||||
def check_apps_ready(self):
|
def check_apps_ready(self):
|
||||||
"""
|
"""Raise an exception if all apps haven't been imported yet."""
|
||||||
Raises an exception if all apps haven't been imported yet.
|
|
||||||
"""
|
|
||||||
if not self.apps_ready:
|
if not self.apps_ready:
|
||||||
raise AppRegistryNotReady("Apps aren't loaded yet.")
|
raise AppRegistryNotReady("Apps aren't loaded yet.")
|
||||||
|
|
||||||
def check_models_ready(self):
|
def check_models_ready(self):
|
||||||
"""
|
"""Raise an exception if all models haven't been imported yet."""
|
||||||
Raises an exception if all models haven't been imported yet.
|
|
||||||
"""
|
|
||||||
if not self.models_ready:
|
if not self.models_ready:
|
||||||
raise AppRegistryNotReady("Models aren't loaded yet.")
|
raise AppRegistryNotReady("Models aren't loaded yet.")
|
||||||
|
|
||||||
def get_app_configs(self):
|
def get_app_configs(self):
|
||||||
"""
|
"""Import applications and return an iterable of app configs."""
|
||||||
Imports applications and returns an iterable of app configs.
|
|
||||||
"""
|
|
||||||
self.check_apps_ready()
|
self.check_apps_ready()
|
||||||
return self.app_configs.values()
|
return self.app_configs.values()
|
||||||
|
|
||||||
def get_app_config(self, app_label):
|
def get_app_config(self, app_label):
|
||||||
"""
|
"""
|
||||||
Imports applications and returns an app config for the given label.
|
Import applications and returns an app config for the given label.
|
||||||
|
|
||||||
Raises LookupError if no application exists with this label.
|
Raise LookupError if no application exists with this label.
|
||||||
"""
|
"""
|
||||||
self.check_apps_ready()
|
self.check_apps_ready()
|
||||||
try:
|
try:
|
||||||
|
@ -163,7 +157,7 @@ class Apps:
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
def get_models(self, include_auto_created=False, include_swapped=False):
|
def get_models(self, include_auto_created=False, include_swapped=False):
|
||||||
"""
|
"""
|
||||||
Returns a list of all installed models.
|
Return a list of all installed models.
|
||||||
|
|
||||||
By default, the following models aren't included:
|
By default, the following models aren't included:
|
||||||
|
|
||||||
|
@ -182,15 +176,14 @@ class Apps:
|
||||||
|
|
||||||
def get_model(self, app_label, model_name=None, require_ready=True):
|
def get_model(self, app_label, model_name=None, require_ready=True):
|
||||||
"""
|
"""
|
||||||
Returns the model matching the given app_label and model_name.
|
Return the model matching the given app_label and model_name.
|
||||||
|
|
||||||
As a shortcut, this function also accepts a single argument in the
|
As a shortcut, app_label may be in the form <app_label>.<model_name>.
|
||||||
form <app_label>.<model_name>.
|
|
||||||
|
|
||||||
model_name is case-insensitive.
|
model_name is case-insensitive.
|
||||||
|
|
||||||
Raises LookupError if no application exists with this label, or no
|
Raise LookupError if no application exists with this label, or no
|
||||||
model exists with this name in the application. Raises ValueError if
|
model exists with this name in the application. Raise ValueError if
|
||||||
called with a single argument that doesn't contain exactly one dot.
|
called with a single argument that doesn't contain exactly one dot.
|
||||||
"""
|
"""
|
||||||
if require_ready:
|
if require_ready:
|
||||||
|
@ -232,9 +225,9 @@ class Apps:
|
||||||
|
|
||||||
def is_installed(self, app_name):
|
def is_installed(self, app_name):
|
||||||
"""
|
"""
|
||||||
Checks whether an application with this name exists in the registry.
|
Check whether an application with this name exists in the registry.
|
||||||
|
|
||||||
app_name is the full name of the app eg. 'django.contrib.admin'.
|
app_name is the full name of the app e.g. 'django.contrib.admin'.
|
||||||
"""
|
"""
|
||||||
self.check_apps_ready()
|
self.check_apps_ready()
|
||||||
return any(ac.name == app_name for ac in self.app_configs.values())
|
return any(ac.name == app_name for ac in self.app_configs.values())
|
||||||
|
@ -245,8 +238,8 @@ class Apps:
|
||||||
|
|
||||||
object_name is the dotted Python path to the object.
|
object_name is the dotted Python path to the object.
|
||||||
|
|
||||||
Returns the app config for the inner application in case of nesting.
|
Return the app config for the inner application in case of nesting.
|
||||||
Returns None if the object isn't in any registered app config.
|
Return None if the object isn't in any registered app config.
|
||||||
"""
|
"""
|
||||||
self.check_apps_ready()
|
self.check_apps_ready()
|
||||||
candidates = []
|
candidates = []
|
||||||
|
@ -296,7 +289,7 @@ class Apps:
|
||||||
|
|
||||||
def set_available_apps(self, available):
|
def set_available_apps(self, available):
|
||||||
"""
|
"""
|
||||||
Restricts the set of installed apps used by get_app_config[s].
|
Restrict the set of installed apps used by get_app_config[s].
|
||||||
|
|
||||||
available must be an iterable of application names.
|
available must be an iterable of application names.
|
||||||
|
|
||||||
|
@ -304,7 +297,7 @@ class Apps:
|
||||||
|
|
||||||
Primarily used for performance optimization in TransactionTestCase.
|
Primarily used for performance optimization in TransactionTestCase.
|
||||||
|
|
||||||
This method is safe is the sense that it doesn't trigger any imports.
|
This method is safe in the sense that it doesn't trigger any imports.
|
||||||
"""
|
"""
|
||||||
available = set(available)
|
available = set(available)
|
||||||
installed = set(app_config.name for app_config in self.get_app_configs())
|
installed = set(app_config.name for app_config in self.get_app_configs())
|
||||||
|
@ -322,15 +315,13 @@ class Apps:
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
|
|
||||||
def unset_available_apps(self):
|
def unset_available_apps(self):
|
||||||
"""
|
"""Cancel a previous call to set_available_apps()."""
|
||||||
Cancels a previous call to set_available_apps().
|
|
||||||
"""
|
|
||||||
self.app_configs = self.stored_app_configs.pop()
|
self.app_configs = self.stored_app_configs.pop()
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
|
|
||||||
def set_installed_apps(self, installed):
|
def set_installed_apps(self, installed):
|
||||||
"""
|
"""
|
||||||
Enables a different set of installed apps for get_app_config[s].
|
Enable a different set of installed apps for get_app_config[s].
|
||||||
|
|
||||||
installed must be an iterable in the same format as INSTALLED_APPS.
|
installed must be an iterable in the same format as INSTALLED_APPS.
|
||||||
|
|
||||||
|
@ -342,7 +333,7 @@ class Apps:
|
||||||
This method may trigger new imports, which may add new models to the
|
This method may trigger new imports, which may add new models to the
|
||||||
registry of all imported models. They will stay in the registry even
|
registry of all imported models. They will stay in the registry even
|
||||||
after unset_installed_apps(). Since it isn't possible to replay
|
after unset_installed_apps(). Since it isn't possible to replay
|
||||||
imports safely (eg. that could lead to registering listeners twice),
|
imports safely (e.g. that could lead to registering listeners twice),
|
||||||
models are registered when they're imported and never removed.
|
models are registered when they're imported and never removed.
|
||||||
"""
|
"""
|
||||||
if not self.ready:
|
if not self.ready:
|
||||||
|
@ -354,16 +345,14 @@ class Apps:
|
||||||
self.populate(installed)
|
self.populate(installed)
|
||||||
|
|
||||||
def unset_installed_apps(self):
|
def unset_installed_apps(self):
|
||||||
"""
|
"""Cancel a previous call to set_installed_apps()."""
|
||||||
Cancels a previous call to set_installed_apps().
|
|
||||||
"""
|
|
||||||
self.app_configs = self.stored_app_configs.pop()
|
self.app_configs = self.stored_app_configs.pop()
|
||||||
self.apps_ready = self.models_ready = self.ready = True
|
self.apps_ready = self.models_ready = self.ready = True
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
|
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
"""
|
"""
|
||||||
Clears all internal caches, for methods that alter the app registry.
|
Clear all internal caches, for methods that alter the app registry.
|
||||||
|
|
||||||
This is mostly used in tests.
|
This is mostly used in tests.
|
||||||
"""
|
"""
|
||||||
|
@ -419,7 +408,7 @@ class Apps:
|
||||||
def do_pending_operations(self, model):
|
def do_pending_operations(self, model):
|
||||||
"""
|
"""
|
||||||
Take a newly-prepared model and pass it to each function waiting for
|
Take a newly-prepared model and pass it to each function waiting for
|
||||||
it. This is called at the very end of `Apps.register_model()`.
|
it. This is called at the very end of Apps.register_model().
|
||||||
"""
|
"""
|
||||||
key = model._meta.app_label, model._meta.model_name
|
key = model._meta.app_label, model._meta.model_name
|
||||||
for function in self._pending_operations.pop(key, []):
|
for function in self._pending_operations.pop(key, []):
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Settings and configuration for Django.
|
Settings and configuration for Django.
|
||||||
|
|
||||||
Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment
|
Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
|
||||||
variable, and then from django.conf.global_settings; see the global settings file for
|
variable, and then from django.conf.global_settings; see the global_settings.py
|
||||||
a list of all possible variables.
|
for a list of all possible variables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
@ -28,8 +28,8 @@ class LazySettings(LazyObject):
|
||||||
def _setup(self, name=None):
|
def _setup(self, name=None):
|
||||||
"""
|
"""
|
||||||
Load the settings module pointed to by the environment variable. This
|
Load the settings module pointed to by the environment variable. This
|
||||||
is used the first time we need any settings at all, if the user has not
|
is used the first time settings are needed, if the user hasn't
|
||||||
previously configured the settings manually.
|
configured settings manually.
|
||||||
"""
|
"""
|
||||||
settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
|
settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
|
||||||
if not settings_module:
|
if not settings_module:
|
||||||
|
@ -51,9 +51,7 @@ class LazySettings(LazyObject):
|
||||||
}
|
}
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""
|
"""Return the value of a setting and cache it in self.__dict__."""
|
||||||
Return the value of a setting and cache it in self.__dict__.
|
|
||||||
"""
|
|
||||||
if self._wrapped is empty:
|
if self._wrapped is empty:
|
||||||
self._setup(name)
|
self._setup(name)
|
||||||
val = getattr(self._wrapped, name)
|
val = getattr(self._wrapped, name)
|
||||||
|
@ -72,9 +70,7 @@ class LazySettings(LazyObject):
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
def __delattr__(self, name):
|
def __delattr__(self, name):
|
||||||
"""
|
"""Delete a setting and clear it from cache if needed."""
|
||||||
Delete a setting and clear it from cache if needed.
|
|
||||||
"""
|
|
||||||
super().__delattr__(name)
|
super().__delattr__(name)
|
||||||
self.__dict__.pop(name, None)
|
self.__dict__.pop(name, None)
|
||||||
|
|
||||||
|
@ -93,9 +89,7 @@ class LazySettings(LazyObject):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def configured(self):
|
def configured(self):
|
||||||
"""
|
"""Return True if the settings have already been configured."""
|
||||||
Returns True if the settings have already been configured.
|
|
||||||
"""
|
|
||||||
return self._wrapped is not empty
|
return self._wrapped is not empty
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,9 +150,7 @@ class Settings:
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsHolder:
|
class UserSettingsHolder:
|
||||||
"""
|
"""Holder for user configured settings."""
|
||||||
Holder for user configured settings.
|
|
||||||
"""
|
|
||||||
# SETTINGS_MODULE doesn't make much sense in the manually configured
|
# SETTINGS_MODULE doesn't make much sense in the manually configured
|
||||||
# (standalone) case.
|
# (standalone) case.
|
||||||
SETTINGS_MODULE = None
|
SETTINGS_MODULE = None
|
||||||
|
|
|
@ -8,9 +8,8 @@ from django.views.i18n import set_language
|
||||||
|
|
||||||
def i18n_patterns(*urls, prefix_default_language=True):
|
def i18n_patterns(*urls, prefix_default_language=True):
|
||||||
"""
|
"""
|
||||||
Adds the language code prefix to every URL pattern within this
|
Add the language code prefix to every URL pattern within this function.
|
||||||
function. This may only be used in the root URLconf, not in an included
|
This may only be used in the root URLconf, not in an included URLconf.
|
||||||
URLconf.
|
|
||||||
"""
|
"""
|
||||||
if not settings.USE_I18N:
|
if not settings.USE_I18N:
|
||||||
return list(urls)
|
return list(urls)
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.views.static import serve
|
||||||
|
|
||||||
def static(prefix, view=serve, **kwargs):
|
def static(prefix, view=serve, **kwargs):
|
||||||
"""
|
"""
|
||||||
Helper function to return a URL pattern for serving files in debug mode.
|
Return a URL pattern for serving files in debug mode.
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
|
@ -7,9 +7,9 @@ class SessionStore(SessionBase):
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
We load the data from the key itself instead of fetching from
|
Load the data from the key itself instead of fetching from some
|
||||||
some external data store. Opposite of _get_session_key(),
|
external data store. Opposite of _get_session_key(), raise BadSignature
|
||||||
raises BadSignature if signature fails.
|
if signature fails.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return signing.loads(
|
return signing.loads(
|
||||||
|
|
|
@ -72,7 +72,7 @@ class UpdateCacheMiddleware(MiddlewareMixin):
|
||||||
return hasattr(request, '_cache_update_cache') and request._cache_update_cache
|
return hasattr(request, '_cache_update_cache') and request._cache_update_cache
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
"""Sets the cache, if needed."""
|
"""Set the cache, if needed."""
|
||||||
if not self._should_update_cache(request, response):
|
if not self._should_update_cache(request, response):
|
||||||
# We don't need to update the cache, just return.
|
# We don't need to update the cache, just return.
|
||||||
return response
|
return response
|
||||||
|
@ -122,7 +122,7 @@ class FetchFromCacheMiddleware(MiddlewareMixin):
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
"""
|
"""
|
||||||
Checks whether the page is already cached and returns the cached
|
Check whether the page is already cached and return the cached
|
||||||
version if available.
|
version if available.
|
||||||
"""
|
"""
|
||||||
if request.method not in ('GET', 'HEAD'):
|
if request.method not in ('GET', 'HEAD'):
|
||||||
|
|
|
@ -11,21 +11,15 @@ from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
class XFrameOptionsMiddleware(MiddlewareMixin):
|
class XFrameOptionsMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
Middleware that sets the X-Frame-Options HTTP header in HTTP responses.
|
Set the X-Frame-Options HTTP header in HTTP responses.
|
||||||
|
|
||||||
Does not set the header if it's already set or if the response contains
|
Do not set the header if it's already set or if the response contains
|
||||||
a xframe_options_exempt value set to True.
|
a xframe_options_exempt value set to True.
|
||||||
|
|
||||||
By default, sets the X-Frame-Options header to 'SAMEORIGIN', meaning the
|
By default, set the X-Frame-Options header to 'SAMEORIGIN', meaning the
|
||||||
response can only be loaded on a frame within the same site. To prevent the
|
response can only be loaded on a frame within the same site. To prevent the
|
||||||
response from being loaded in a frame in any site, set X_FRAME_OPTIONS in
|
response from being loaded in a frame in any site, set X_FRAME_OPTIONS in
|
||||||
your project's Django settings to 'DENY'.
|
your project's Django settings to 'DENY'.
|
||||||
|
|
||||||
Note: older browsers will quietly ignore this header, thus other
|
|
||||||
clickjacking protection techniques should be used if protection in those
|
|
||||||
browsers is required.
|
|
||||||
|
|
||||||
https://en.wikipedia.org/wiki/Clickjacking#Server_and_client
|
|
||||||
"""
|
"""
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
# Don't set it if it's already in the response
|
# Don't set it if it's already in the response
|
||||||
|
@ -42,10 +36,8 @@ class XFrameOptionsMiddleware(MiddlewareMixin):
|
||||||
|
|
||||||
def get_xframe_options_value(self, request, response):
|
def get_xframe_options_value(self, request, response):
|
||||||
"""
|
"""
|
||||||
Gets the value to set for the X_FRAME_OPTIONS header.
|
Get the value to set for the X_FRAME_OPTIONS header. Use the value from
|
||||||
|
the X_FRAME_OPTIONS setting, or 'SAMEORIGIN' if not set.
|
||||||
By default this uses the value from the X_FRAME_OPTIONS Django
|
|
||||||
settings. If not found in settings, defaults to 'SAMEORIGIN'.
|
|
||||||
|
|
||||||
This method can be overridden if needed, allowing it to vary based on
|
This method can be overridden if needed, allowing it to vary based on
|
||||||
the request or response.
|
the request or response.
|
||||||
|
|
|
@ -17,17 +17,16 @@ class CommonMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
"Common" middleware for taking care of some basic operations:
|
"Common" middleware for taking care of some basic operations:
|
||||||
|
|
||||||
- Forbids access to User-Agents in settings.DISALLOWED_USER_AGENTS
|
- Forbid access to User-Agents in settings.DISALLOWED_USER_AGENTS
|
||||||
|
|
||||||
- URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings,
|
- URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings,
|
||||||
this middleware appends missing slashes and/or prepends missing
|
append missing slashes and/or prepends missing "www."s.
|
||||||
"www."s.
|
|
||||||
|
|
||||||
- If APPEND_SLASH is set and the initial URL doesn't end with a
|
- If APPEND_SLASH is set and the initial URL doesn't end with a
|
||||||
slash, and it is not found in urlpatterns, a new URL is formed by
|
slash, and it is not found in urlpatterns, form a new URL by
|
||||||
appending a slash at the end. If this new URL is found in
|
appending a slash at the end. If this new URL is found in
|
||||||
urlpatterns, then an HTTP-redirect is returned to this new URL;
|
urlpatterns, return an HTTP redirect to this new URL; otherwise
|
||||||
otherwise the initial URL is processed as usual.
|
process the initial URL as usual.
|
||||||
|
|
||||||
This behavior can be customized by subclassing CommonMiddleware and
|
This behavior can be customized by subclassing CommonMiddleware and
|
||||||
overriding the response_redirect_class attribute.
|
overriding the response_redirect_class attribute.
|
||||||
|
@ -140,9 +139,7 @@ class CommonMiddleware(MiddlewareMixin):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def needs_etag(self, response):
|
def needs_etag(self, response):
|
||||||
"""
|
"""Return True if an ETag header should be added to response."""
|
||||||
Return True if an ETag header should be added to response.
|
|
||||||
"""
|
|
||||||
cache_control_headers = cc_delim_re.split(response.get('Cache-Control', ''))
|
cache_control_headers = cc_delim_re.split(response.get('Cache-Control', ''))
|
||||||
return all(header.lower() != 'no-store' for header in cache_control_headers)
|
return all(header.lower() != 'no-store' for header in cache_control_headers)
|
||||||
|
|
||||||
|
@ -150,9 +147,7 @@ class CommonMiddleware(MiddlewareMixin):
|
||||||
class BrokenLinkEmailsMiddleware(MiddlewareMixin):
|
class BrokenLinkEmailsMiddleware(MiddlewareMixin):
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
"""
|
"""Send broken link emails for relevant 404 NOT FOUND responses."""
|
||||||
Send broken link emails for relevant 404 NOT FOUND responses.
|
|
||||||
"""
|
|
||||||
if response.status_code == 404 and not settings.DEBUG:
|
if response.status_code == 404 and not settings.DEBUG:
|
||||||
domain = request.get_host()
|
domain = request.get_host()
|
||||||
path = request.get_full_path()
|
path = request.get_full_path()
|
||||||
|
@ -173,7 +168,8 @@ class BrokenLinkEmailsMiddleware(MiddlewareMixin):
|
||||||
|
|
||||||
def is_internal_request(self, domain, referer):
|
def is_internal_request(self, domain, referer):
|
||||||
"""
|
"""
|
||||||
Returns True if the referring URL is the same domain as the current request.
|
Return True if the referring URL is the same domain as the current
|
||||||
|
request.
|
||||||
"""
|
"""
|
||||||
# Different subdomains are treated as different domains.
|
# Different subdomains are treated as different domains.
|
||||||
return bool(re.match("^https?://%s/" % re.escape(domain), referer))
|
return bool(re.match("^https?://%s/" % re.escape(domain), referer))
|
||||||
|
|
|
@ -33,9 +33,7 @@ CSRF_SESSION_KEY = '_csrftoken'
|
||||||
|
|
||||||
|
|
||||||
def _get_failure_view():
|
def _get_failure_view():
|
||||||
"""
|
"""Return the view to be used for CSRF rejections."""
|
||||||
Returns the view to be used for CSRF rejections
|
|
||||||
"""
|
|
||||||
return get_callable(settings.CSRF_FAILURE_VIEW)
|
return get_callable(settings.CSRF_FAILURE_VIEW)
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +73,7 @@ def _get_new_csrf_token():
|
||||||
|
|
||||||
def get_token(request):
|
def get_token(request):
|
||||||
"""
|
"""
|
||||||
Returns the CSRF token required for a POST form. The token is an
|
Return the CSRF token required for a POST form. The token is an
|
||||||
alphanumeric value. A new token is created if one is not already set.
|
alphanumeric value. A new token is created if one is not already set.
|
||||||
|
|
||||||
A side effect of calling this function is to make the csrf_protect
|
A side effect of calling this function is to make the csrf_protect
|
||||||
|
@ -94,7 +92,7 @@ def get_token(request):
|
||||||
|
|
||||||
def rotate_token(request):
|
def rotate_token(request):
|
||||||
"""
|
"""
|
||||||
Changes the CSRF token in use for a request - should be done on login
|
Change the CSRF token in use for a request - should be done on login
|
||||||
for security purposes.
|
for security purposes.
|
||||||
"""
|
"""
|
||||||
request.META.update({
|
request.META.update({
|
||||||
|
@ -132,12 +130,11 @@ def _compare_salted_tokens(request_csrf_token, csrf_token):
|
||||||
|
|
||||||
class CsrfViewMiddleware(MiddlewareMixin):
|
class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
Middleware that requires a present and correct csrfmiddlewaretoken
|
Require a present and correct csrfmiddlewaretoken for POST requests that
|
||||||
for POST requests that have a CSRF cookie, and sets an outgoing
|
have a CSRF cookie, and set an outgoing CSRF cookie.
|
||||||
CSRF cookie.
|
|
||||||
|
|
||||||
This middleware should be used in conjunction with the csrf_token template
|
This middleware should be used in conjunction with the {% csrf_token %}
|
||||||
tag.
|
template tag.
|
||||||
"""
|
"""
|
||||||
# The _accept and _reject methods currently only exist for the sake of the
|
# The _accept and _reject methods currently only exist for the sake of the
|
||||||
# requires_csrf_token decorator.
|
# requires_csrf_token decorator.
|
||||||
|
|
|
@ -9,8 +9,8 @@ re_accepts_gzip = re.compile(r'\bgzip\b')
|
||||||
|
|
||||||
class GZipMiddleware(MiddlewareMixin):
|
class GZipMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
This middleware compresses content if the browser allows gzip compression.
|
Compress content if the browser allows gzip compression.
|
||||||
It sets the Vary header accordingly, so that caches will base their storage
|
Set the Vary header accordingly, so that caches will base their storage
|
||||||
on the Accept-Encoding header.
|
on the Accept-Encoding header.
|
||||||
"""
|
"""
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
|
|
|
@ -7,10 +7,9 @@ from django.utils.http import parse_http_date_safe
|
||||||
|
|
||||||
class ConditionalGetMiddleware(MiddlewareMixin):
|
class ConditionalGetMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
Handles conditional GET operations. If the response has an ETag or
|
Handle conditional GET operations. If the response has an ETag or
|
||||||
Last-Modified header, and the request has If-None-Match or
|
Last-Modified header and the request has If-None-Match or If-Modified-Since,
|
||||||
If-Modified-Since, the response is replaced by an HttpNotModified. An ETag
|
replace the response with HttpNotModified. Add an ETag header if needed.
|
||||||
header is added if needed.
|
|
||||||
"""
|
"""
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
# It's too late to prevent an unsafe request with a 412 response, and
|
# It's too late to prevent an unsafe request with a 412 response, and
|
||||||
|
@ -38,8 +37,6 @@ class ConditionalGetMiddleware(MiddlewareMixin):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def needs_etag(self, response):
|
def needs_etag(self, response):
|
||||||
"""
|
"""Return True if an ETag header should be added to response."""
|
||||||
Return True if an ETag header should be added to response.
|
|
||||||
"""
|
|
||||||
cache_control_headers = cc_delim_re.split(response.get('Cache-Control', ''))
|
cache_control_headers = cc_delim_re.split(response.get('Cache-Control', ''))
|
||||||
return all(header.lower() != 'no-store' for header in cache_control_headers)
|
return all(header.lower() != 'no-store' for header in cache_control_headers)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
"This is the locale selecting middleware that will look at accept headers"
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.i18n import is_language_prefix_patterns_used
|
from django.conf.urls.i18n import is_language_prefix_patterns_used
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
@ -11,11 +9,9 @@ from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
class LocaleMiddleware(MiddlewareMixin):
|
class LocaleMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
This is a very simple middleware that parses a request
|
Parse a request and decide what translation object to install in the
|
||||||
and decides what translation object to install in the current
|
current thread context. This allows pages to be dynamically translated to
|
||||||
thread context. This allows pages to be dynamically
|
the language the user desires (if the language is available, of course).
|
||||||
translated to the language the user desires (if the language
|
|
||||||
is available, of course).
|
|
||||||
"""
|
"""
|
||||||
response_redirect_class = HttpResponseRedirect
|
response_redirect_class = HttpResponseRedirect
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from django.utils.functional import Promise
|
||||||
|
|
||||||
def render_to_response(template_name, context=None, content_type=None, status=None, using=None):
|
def render_to_response(template_name, context=None, content_type=None, status=None, using=None):
|
||||||
"""
|
"""
|
||||||
Returns a HttpResponse whose content is filled with the result of calling
|
Return a HttpResponse whose content is filled with the result of calling
|
||||||
django.template.loader.render_to_string() with the passed arguments.
|
django.template.loader.render_to_string() with the passed arguments.
|
||||||
"""
|
"""
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
|
@ -31,7 +31,7 @@ def render_to_response(template_name, context=None, content_type=None, status=No
|
||||||
|
|
||||||
def render(request, template_name, context=None, content_type=None, status=None, using=None):
|
def render(request, template_name, context=None, content_type=None, status=None, using=None):
|
||||||
"""
|
"""
|
||||||
Returns a HttpResponse whose content is filled with the result of calling
|
Return a HttpResponse whose content is filled with the result of calling
|
||||||
django.template.loader.render_to_string() with the passed arguments.
|
django.template.loader.render_to_string() with the passed arguments.
|
||||||
"""
|
"""
|
||||||
content = loader.render_to_string(template_name, context, request, using=using)
|
content = loader.render_to_string(template_name, context, request, using=using)
|
||||||
|
@ -40,7 +40,7 @@ def render(request, template_name, context=None, content_type=None, status=None,
|
||||||
|
|
||||||
def redirect(to, *args, permanent=False, **kwargs):
|
def redirect(to, *args, permanent=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns an HttpResponseRedirect to the appropriate URL for the arguments
|
Return an HttpResponseRedirect to the appropriate URL for the arguments
|
||||||
passed.
|
passed.
|
||||||
|
|
||||||
The arguments could be:
|
The arguments could be:
|
||||||
|
@ -74,7 +74,7 @@ def _get_queryset(klass):
|
||||||
|
|
||||||
def get_object_or_404(klass, *args, **kwargs):
|
def get_object_or_404(klass, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Uses get() to return an object, or raises a Http404 exception if the object
|
Use get() to return an object, or raise a Http404 exception if the object
|
||||||
does not exist.
|
does not exist.
|
||||||
|
|
||||||
klass may be a Model, Manager, or QuerySet object. All other passed
|
klass may be a Model, Manager, or QuerySet object. All other passed
|
||||||
|
@ -98,7 +98,7 @@ def get_object_or_404(klass, *args, **kwargs):
|
||||||
|
|
||||||
def get_list_or_404(klass, *args, **kwargs):
|
def get_list_or_404(klass, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Uses filter() to return a list of objects, or raise a Http404 exception if
|
Use filter() to return a list of objects, or raise a Http404 exception if
|
||||||
the list is empty.
|
the list is empty.
|
||||||
|
|
||||||
klass may be a Model, Manager, or QuerySet object. All other passed
|
klass may be a Model, Manager, or QuerySet object. All other passed
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
"""
|
"""Django Unit Test framework."""
|
||||||
Django Unit Test and Doctest framework.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.test.client import Client, RequestFactory
|
from django.test.client import Client, RequestFactory
|
||||||
from django.test.testcases import (
|
from django.test.testcases import (
|
||||||
|
|
|
@ -36,9 +36,7 @@ JSON_CONTENT_TYPE_RE = re.compile(r'^application\/(vnd\..+\+)?json$')
|
||||||
|
|
||||||
|
|
||||||
class RedirectCycleError(Exception):
|
class RedirectCycleError(Exception):
|
||||||
"""
|
"""The test client has been asked to follow a redirect loop."""
|
||||||
The test client has been asked to follow a redirect loop.
|
|
||||||
"""
|
|
||||||
def __init__(self, message, last_response):
|
def __init__(self, message, last_response):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.last_response = last_response
|
self.last_response = last_response
|
||||||
|
@ -50,7 +48,7 @@ class FakePayload:
|
||||||
A wrapper around BytesIO that restricts what can be read since data from
|
A wrapper around BytesIO that restricts what can be read since data from
|
||||||
the network can't be seeked and cannot be read outside of its content
|
the network can't be seeked and cannot be read outside of its content
|
||||||
length. This makes sure that views can't do anything under the test client
|
length. This makes sure that views can't do anything under the test client
|
||||||
that wouldn't work in Real Life.
|
that wouldn't work in real life.
|
||||||
"""
|
"""
|
||||||
def __init__(self, content=None):
|
def __init__(self, content=None):
|
||||||
self.__content = BytesIO()
|
self.__content = BytesIO()
|
||||||
|
@ -93,7 +91,7 @@ def closing_iterator_wrapper(iterable, close):
|
||||||
def conditional_content_removal(request, response):
|
def conditional_content_removal(request, response):
|
||||||
"""
|
"""
|
||||||
Simulate the behavior of most Web servers by removing the content of
|
Simulate the behavior of most Web servers by removing the content of
|
||||||
responses for HEAD requests, 1xx, 204, and 304 responses. Ensures
|
responses for HEAD requests, 1xx, 204, and 304 responses. Ensure
|
||||||
compliance with RFC 7230, section 3.3.3.
|
compliance with RFC 7230, section 3.3.3.
|
||||||
"""
|
"""
|
||||||
if 100 <= response.status_code < 200 or response.status_code in (204, 304):
|
if 100 <= response.status_code < 200 or response.status_code in (204, 304):
|
||||||
|
@ -112,8 +110,8 @@ def conditional_content_removal(request, response):
|
||||||
|
|
||||||
class ClientHandler(BaseHandler):
|
class ClientHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
A HTTP Handler that can be used for testing purposes. Uses the WSGI
|
A HTTP Handler that can be used for testing purposes. Use the WSGI
|
||||||
interface to compose requests, but returns the raw HttpResponse object with
|
interface to compose requests, but return the raw HttpResponse object with
|
||||||
the originating WSGIRequest attached to its ``wsgi_request`` attribute.
|
the originating WSGIRequest attached to its ``wsgi_request`` attribute.
|
||||||
"""
|
"""
|
||||||
def __init__(self, enforce_csrf_checks=True, *args, **kwargs):
|
def __init__(self, enforce_csrf_checks=True, *args, **kwargs):
|
||||||
|
@ -146,8 +144,7 @@ class ClientHandler(BaseHandler):
|
||||||
# later retrieved.
|
# later retrieved.
|
||||||
response.wsgi_request = request
|
response.wsgi_request = request
|
||||||
|
|
||||||
# We're emulating a WSGI server; we must call the close method
|
# Emulate a WSGI server by calling the close method on completion.
|
||||||
# on completion.
|
|
||||||
if response.streaming:
|
if response.streaming:
|
||||||
response.streaming_content = closing_iterator_wrapper(
|
response.streaming_content = closing_iterator_wrapper(
|
||||||
response.streaming_content, response.close)
|
response.streaming_content, response.close)
|
||||||
|
@ -161,7 +158,7 @@ class ClientHandler(BaseHandler):
|
||||||
|
|
||||||
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
|
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
|
||||||
"""
|
"""
|
||||||
Stores templates and contexts that are rendered.
|
Store templates and contexts that are rendered.
|
||||||
|
|
||||||
The context is copied so that it is an accurate representation at the time
|
The context is copied so that it is an accurate representation at the time
|
||||||
of rendering.
|
of rendering.
|
||||||
|
@ -174,7 +171,7 @@ def store_rendered_templates(store, signal, sender, template, context, **kwargs)
|
||||||
|
|
||||||
def encode_multipart(boundary, data):
|
def encode_multipart(boundary, data):
|
||||||
"""
|
"""
|
||||||
Encodes multipart POST data from a dictionary of form values.
|
Encode multipart POST data from a dictionary of form values.
|
||||||
|
|
||||||
The key will be used as the form data name; the value will be transmitted
|
The key will be used as the form data name; the value will be transmitted
|
||||||
as content. If the value is a file, the contents of the file will be sent
|
as content. If the value is a file, the contents of the file will be sent
|
||||||
|
@ -326,8 +323,7 @@ class RequestFactory:
|
||||||
return path.decode('iso-8859-1')
|
return path.decode('iso-8859-1')
|
||||||
|
|
||||||
def get(self, path, data=None, secure=False, **extra):
|
def get(self, path, data=None, secure=False, **extra):
|
||||||
"Construct a GET request."
|
"""Construct a GET request."""
|
||||||
|
|
||||||
data = {} if data is None else data
|
data = {} if data is None else data
|
||||||
r = {
|
r = {
|
||||||
'QUERY_STRING': urlencode(data, doseq=True),
|
'QUERY_STRING': urlencode(data, doseq=True),
|
||||||
|
@ -337,8 +333,7 @@ class RequestFactory:
|
||||||
|
|
||||||
def post(self, path, data=None, content_type=MULTIPART_CONTENT,
|
def post(self, path, data=None, content_type=MULTIPART_CONTENT,
|
||||||
secure=False, **extra):
|
secure=False, **extra):
|
||||||
"Construct a POST request."
|
"""Construct a POST request."""
|
||||||
|
|
||||||
data = {} if data is None else data
|
data = {} if data is None else data
|
||||||
post_data = self._encode_data(data, content_type)
|
post_data = self._encode_data(data, content_type)
|
||||||
|
|
||||||
|
@ -346,8 +341,7 @@ class RequestFactory:
|
||||||
secure=secure, **extra)
|
secure=secure, **extra)
|
||||||
|
|
||||||
def head(self, path, data=None, secure=False, **extra):
|
def head(self, path, data=None, secure=False, **extra):
|
||||||
"Construct a HEAD request."
|
"""Construct a HEAD request."""
|
||||||
|
|
||||||
data = {} if data is None else data
|
data = {} if data is None else data
|
||||||
r = {
|
r = {
|
||||||
'QUERY_STRING': urlencode(data, doseq=True),
|
'QUERY_STRING': urlencode(data, doseq=True),
|
||||||
|
@ -356,7 +350,7 @@ class RequestFactory:
|
||||||
return self.generic('HEAD', path, secure=secure, **r)
|
return self.generic('HEAD', path, secure=secure, **r)
|
||||||
|
|
||||||
def trace(self, path, secure=False, **extra):
|
def trace(self, path, secure=False, **extra):
|
||||||
"Construct a TRACE request."
|
"""Construct a TRACE request."""
|
||||||
return self.generic('TRACE', path, secure=secure, **extra)
|
return self.generic('TRACE', path, secure=secure, **extra)
|
||||||
|
|
||||||
def options(self, path, data='', content_type='application/octet-stream',
|
def options(self, path, data='', content_type='application/octet-stream',
|
||||||
|
@ -367,26 +361,26 @@ class RequestFactory:
|
||||||
|
|
||||||
def put(self, path, data='', content_type='application/octet-stream',
|
def put(self, path, data='', content_type='application/octet-stream',
|
||||||
secure=False, **extra):
|
secure=False, **extra):
|
||||||
"Construct a PUT request."
|
"""Construct a PUT request."""
|
||||||
return self.generic('PUT', path, data, content_type,
|
return self.generic('PUT', path, data, content_type,
|
||||||
secure=secure, **extra)
|
secure=secure, **extra)
|
||||||
|
|
||||||
def patch(self, path, data='', content_type='application/octet-stream',
|
def patch(self, path, data='', content_type='application/octet-stream',
|
||||||
secure=False, **extra):
|
secure=False, **extra):
|
||||||
"Construct a PATCH request."
|
"""Construct a PATCH request."""
|
||||||
return self.generic('PATCH', path, data, content_type,
|
return self.generic('PATCH', path, data, content_type,
|
||||||
secure=secure, **extra)
|
secure=secure, **extra)
|
||||||
|
|
||||||
def delete(self, path, data='', content_type='application/octet-stream',
|
def delete(self, path, data='', content_type='application/octet-stream',
|
||||||
secure=False, **extra):
|
secure=False, **extra):
|
||||||
"Construct a DELETE request."
|
"""Construct a DELETE request."""
|
||||||
return self.generic('DELETE', path, data, content_type,
|
return self.generic('DELETE', path, data, content_type,
|
||||||
secure=secure, **extra)
|
secure=secure, **extra)
|
||||||
|
|
||||||
def generic(self, method, path, data='',
|
def generic(self, method, path, data='',
|
||||||
content_type='application/octet-stream', secure=False,
|
content_type='application/octet-stream', secure=False,
|
||||||
**extra):
|
**extra):
|
||||||
"""Constructs an arbitrary HTTP request."""
|
"""Construct an arbitrary HTTP request."""
|
||||||
parsed = urlparse(str(path)) # path can be lazy
|
parsed = urlparse(str(path)) # path can be lazy
|
||||||
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
||||||
r = {
|
r = {
|
||||||
|
@ -434,16 +428,12 @@ class Client(RequestFactory):
|
||||||
self.exc_info = None
|
self.exc_info = None
|
||||||
|
|
||||||
def store_exc_info(self, **kwargs):
|
def store_exc_info(self, **kwargs):
|
||||||
"""
|
"""Store exceptions when they are generated by a view."""
|
||||||
Stores exceptions when they are generated by a view.
|
|
||||||
"""
|
|
||||||
self.exc_info = sys.exc_info()
|
self.exc_info = sys.exc_info()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self):
|
def session(self):
|
||||||
"""
|
"""Return the current session variables."""
|
||||||
Obtains the current session variables.
|
|
||||||
"""
|
|
||||||
engine = import_module(settings.SESSION_ENGINE)
|
engine = import_module(settings.SESSION_ENGINE)
|
||||||
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME)
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME)
|
||||||
if cookie:
|
if cookie:
|
||||||
|
@ -456,10 +446,10 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def request(self, **request):
|
def request(self, **request):
|
||||||
"""
|
"""
|
||||||
The master request method. Composes the environment dictionary
|
The master request method. Compose the environment dictionary and pass
|
||||||
and passes to the handler, returning the result of the handler.
|
to the handler, return the result of the handler. Assume defaults for
|
||||||
Assumes defaults for the query environment, which can be overridden
|
the query environment, which can be overridden using the arguments to
|
||||||
using the arguments to the request.
|
the request.
|
||||||
"""
|
"""
|
||||||
environ = self._base_environ(**request)
|
environ = self._base_environ(**request)
|
||||||
|
|
||||||
|
@ -523,9 +513,7 @@ class Client(RequestFactory):
|
||||||
got_request_exception.disconnect(dispatch_uid=exception_uid)
|
got_request_exception.disconnect(dispatch_uid=exception_uid)
|
||||||
|
|
||||||
def get(self, path, data=None, follow=False, secure=False, **extra):
|
def get(self, path, data=None, follow=False, secure=False, **extra):
|
||||||
"""
|
"""Request a response from the server using GET."""
|
||||||
Requests a response from the server using GET.
|
|
||||||
"""
|
|
||||||
response = super().get(path, data=data, secure=secure, **extra)
|
response = super().get(path, data=data, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
|
@ -533,18 +521,14 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def post(self, path, data=None, content_type=MULTIPART_CONTENT,
|
def post(self, path, data=None, content_type=MULTIPART_CONTENT,
|
||||||
follow=False, secure=False, **extra):
|
follow=False, secure=False, **extra):
|
||||||
"""
|
"""Request a response from the server using POST."""
|
||||||
Requests a response from the server using POST.
|
|
||||||
"""
|
|
||||||
response = super().post(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().post(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def head(self, path, data=None, follow=False, secure=False, **extra):
|
def head(self, path, data=None, follow=False, secure=False, **extra):
|
||||||
"""
|
"""Request a response from the server using HEAD."""
|
||||||
Request a response from the server using HEAD.
|
|
||||||
"""
|
|
||||||
response = super().head(path, data=data, secure=secure, **extra)
|
response = super().head(path, data=data, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
|
@ -552,9 +536,7 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def options(self, path, data='', content_type='application/octet-stream',
|
def options(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, secure=False, **extra):
|
follow=False, secure=False, **extra):
|
||||||
"""
|
"""Request a response from the server using OPTIONS."""
|
||||||
Request a response from the server using OPTIONS.
|
|
||||||
"""
|
|
||||||
response = super().options(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().options(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
|
@ -562,9 +544,7 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def put(self, path, data='', content_type='application/octet-stream',
|
def put(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, secure=False, **extra):
|
follow=False, secure=False, **extra):
|
||||||
"""
|
"""Send a resource to the server using PUT."""
|
||||||
Send a resource to the server using PUT.
|
|
||||||
"""
|
|
||||||
response = super().put(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().put(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
|
@ -572,9 +552,7 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def patch(self, path, data='', content_type='application/octet-stream',
|
def patch(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, secure=False, **extra):
|
follow=False, secure=False, **extra):
|
||||||
"""
|
"""Send a resource to the server using PATCH."""
|
||||||
Send a resource to the server using PATCH.
|
|
||||||
"""
|
|
||||||
response = super().patch(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().patch(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
|
@ -582,18 +560,14 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def delete(self, path, data='', content_type='application/octet-stream',
|
def delete(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, secure=False, **extra):
|
follow=False, secure=False, **extra):
|
||||||
"""
|
"""Send a DELETE request to the server."""
|
||||||
Send a DELETE request to the server.
|
|
||||||
"""
|
|
||||||
response = super().delete(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().delete(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def trace(self, path, data='', follow=False, secure=False, **extra):
|
def trace(self, path, data='', follow=False, secure=False, **extra):
|
||||||
"""
|
"""Send a TRACE request to the server."""
|
||||||
Send a TRACE request to the server.
|
|
||||||
"""
|
|
||||||
response = super().trace(path, data=data, secure=secure, **extra)
|
response = super().trace(path, data=data, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
|
@ -601,9 +575,9 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
def login(self, **credentials):
|
def login(self, **credentials):
|
||||||
"""
|
"""
|
||||||
Sets the Factory to appear as if it has successfully logged into a site.
|
Set the Factory to appear as if it has successfully logged into a site.
|
||||||
|
|
||||||
Returns True if login is possible; False if the provided credentials
|
Return True if login is possible; False if the provided credentials
|
||||||
are incorrect.
|
are incorrect.
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
|
@ -655,11 +629,7 @@ class Client(RequestFactory):
|
||||||
self.cookies[session_cookie].update(cookie_data)
|
self.cookies[session_cookie].update(cookie_data)
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
"""
|
"""Log out the user by removing the cookies and session object."""
|
||||||
Removes the authenticated user's cookies and session object.
|
|
||||||
|
|
||||||
Causes the authenticated user to be logged out.
|
|
||||||
"""
|
|
||||||
from django.contrib.auth import get_user, logout
|
from django.contrib.auth import get_user, logout
|
||||||
|
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
|
@ -683,8 +653,9 @@ class Client(RequestFactory):
|
||||||
return response._json
|
return response._json
|
||||||
|
|
||||||
def _handle_redirects(self, response, **extra):
|
def _handle_redirects(self, response, **extra):
|
||||||
"Follows any redirects by requesting responses from the server using GET."
|
"""
|
||||||
|
Follow any redirects by requesting responses from the server using GET.
|
||||||
|
"""
|
||||||
response.redirect_chain = []
|
response.redirect_chain = []
|
||||||
while response.status_code in (301, 302, 303, 307):
|
while response.status_code in (301, 302, 303, 307):
|
||||||
response_url = response.url
|
response_url = response.url
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
"""
|
"""Compare two HTML documents."""
|
||||||
Comparing two html documents.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -214,7 +212,7 @@ class Parser(HTMLParser):
|
||||||
|
|
||||||
def parse_html(html):
|
def parse_html(html):
|
||||||
"""
|
"""
|
||||||
Takes a string that contains *valid* HTML and turns it into a Python object
|
Take a string that contains *valid* HTML and turn it into a Python object
|
||||||
structure that can be easily compared against other HTML on semantic
|
structure that can be easily compared against other HTML on semantic
|
||||||
equivalence. Syntactical differences like which quotation is used on
|
equivalence. Syntactical differences like which quotation is used on
|
||||||
arguments will be ignored.
|
arguments will be ignored.
|
||||||
|
|
|
@ -253,9 +253,7 @@ class RemoteTestRunner:
|
||||||
|
|
||||||
|
|
||||||
def default_test_processes():
|
def default_test_processes():
|
||||||
"""
|
"""Default number of test processes when using the --parallel option."""
|
||||||
Default number of test processes when using the --parallel option.
|
|
||||||
"""
|
|
||||||
# The current implementation of the parallel test runner requires
|
# The current implementation of the parallel test runner requires
|
||||||
# multiprocessing to start subprocesses with fork().
|
# multiprocessing to start subprocesses with fork().
|
||||||
if multiprocessing.get_start_method() != 'fork':
|
if multiprocessing.get_start_method() != 'fork':
|
||||||
|
@ -389,9 +387,7 @@ class ParallelTestSuite(unittest.TestSuite):
|
||||||
|
|
||||||
|
|
||||||
class DiscoverRunner:
|
class DiscoverRunner:
|
||||||
"""
|
"""A Django test runner that uses unittest2 test discovery."""
|
||||||
A Django test runner that uses unittest2 test discovery.
|
|
||||||
"""
|
|
||||||
|
|
||||||
test_suite = unittest.TestSuite
|
test_suite = unittest.TestSuite
|
||||||
parallel_test_suite = ParallelTestSuite
|
parallel_test_suite = ParallelTestSuite
|
||||||
|
@ -566,9 +562,7 @@ class DiscoverRunner:
|
||||||
return runner.run(suite)
|
return runner.run(suite)
|
||||||
|
|
||||||
def teardown_databases(self, old_config, **kwargs):
|
def teardown_databases(self, old_config, **kwargs):
|
||||||
"""
|
"""Destroy all the non-mirror databases."""
|
||||||
Destroys all the non-mirror databases.
|
|
||||||
"""
|
|
||||||
_teardown_databases(
|
_teardown_databases(
|
||||||
old_config,
|
old_config,
|
||||||
verbosity=self.verbosity,
|
verbosity=self.verbosity,
|
||||||
|
@ -593,7 +587,7 @@ class DiscoverRunner:
|
||||||
A list of 'extra' tests may also be provided; these tests
|
A list of 'extra' tests may also be provided; these tests
|
||||||
will be added to the test suite.
|
will be added to the test suite.
|
||||||
|
|
||||||
Returns the number of tests that failed.
|
Return the number of tests that failed.
|
||||||
"""
|
"""
|
||||||
self.setup_test_environment()
|
self.setup_test_environment()
|
||||||
suite = self.build_suite(test_labels, extra_tests)
|
suite = self.build_suite(test_labels, extra_tests)
|
||||||
|
@ -623,15 +617,15 @@ def is_discoverable(label):
|
||||||
|
|
||||||
def reorder_suite(suite, classes, reverse=False):
|
def reorder_suite(suite, classes, reverse=False):
|
||||||
"""
|
"""
|
||||||
Reorders a test suite by test type.
|
Reorder a test suite by test type.
|
||||||
|
|
||||||
`classes` is a sequence of types
|
`classes` is a sequence of types
|
||||||
|
|
||||||
All tests of type classes[0] are placed first, then tests of type
|
All tests of type classes[0] are placed first, then tests of type
|
||||||
classes[1], etc. Tests with no match in classes are placed last.
|
classes[1], etc. Tests with no match in classes are placed last.
|
||||||
|
|
||||||
If `reverse` is True, tests within classes are sorted in opposite order,
|
If `reverse` is True, sort tests within classes in opposite order but
|
||||||
but test classes are not reversed.
|
don't reverse test classes.
|
||||||
"""
|
"""
|
||||||
class_count = len(classes)
|
class_count = len(classes)
|
||||||
suite_class = type(suite)
|
suite_class = type(suite)
|
||||||
|
@ -645,7 +639,7 @@ def reorder_suite(suite, classes, reverse=False):
|
||||||
|
|
||||||
def partition_suite_by_type(suite, classes, bins, reverse=False):
|
def partition_suite_by_type(suite, classes, bins, reverse=False):
|
||||||
"""
|
"""
|
||||||
Partitions a test suite by test type. Also prevents duplicated tests.
|
Partition a test suite by test type. Also prevent duplicated tests.
|
||||||
|
|
||||||
classes is a sequence of types
|
classes is a sequence of types
|
||||||
bins is a sequence of TestSuites, one more than classes
|
bins is a sequence of TestSuites, one more than classes
|
||||||
|
@ -670,9 +664,7 @@ def partition_suite_by_type(suite, classes, bins, reverse=False):
|
||||||
|
|
||||||
|
|
||||||
def partition_suite_by_case(suite):
|
def partition_suite_by_case(suite):
|
||||||
"""
|
"""Partition a test suite by test case, preserving the order of tests."""
|
||||||
Partitions a test suite by test case, preserving the order of tests.
|
|
||||||
"""
|
|
||||||
groups = []
|
groups = []
|
||||||
suite_class = type(suite)
|
suite_class = type(suite)
|
||||||
for test_type, test_group in itertools.groupby(suite, type):
|
for test_type, test_group in itertools.groupby(suite, type):
|
||||||
|
|
|
@ -73,7 +73,7 @@ class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def disable_implicit_wait(self):
|
def disable_implicit_wait(self):
|
||||||
"""Context manager that disables the default implicit wait."""
|
"""Disable the default implicit wait."""
|
||||||
self.selenium.implicitly_wait(0)
|
self.selenium.implicitly_wait(0)
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
|
|
@ -43,8 +43,8 @@ __all__ = ('TestCase', 'TransactionTestCase',
|
||||||
|
|
||||||
def to_list(value):
|
def to_list(value):
|
||||||
"""
|
"""
|
||||||
Puts value into a list if it's not already one.
|
Put value into a list if it's not already one. Return an empty list if
|
||||||
Returns an empty list if value is None.
|
value is None.
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
value = []
|
value = []
|
||||||
|
@ -212,21 +212,22 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
return
|
return
|
||||||
|
|
||||||
def _pre_setup(self):
|
def _pre_setup(self):
|
||||||
"""Performs any pre-test setup. This includes:
|
"""
|
||||||
|
Perform pre-test setup:
|
||||||
* Creating a test client.
|
* Create a test client.
|
||||||
* Clearing the mail test outbox.
|
* Clear the mail test outbox.
|
||||||
"""
|
"""
|
||||||
self.client = self.client_class()
|
self.client = self.client_class()
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
def _post_teardown(self):
|
def _post_teardown(self):
|
||||||
"""Perform any post-test things."""
|
"""Perform post-test things."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def settings(self, **kwargs):
|
def settings(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
A context manager that temporarily sets a setting and reverts to the original value when exiting the context.
|
A context manager that temporarily sets a setting and reverts to the
|
||||||
|
original value when exiting the context.
|
||||||
"""
|
"""
|
||||||
return override_settings(**kwargs)
|
return override_settings(**kwargs)
|
||||||
|
|
||||||
|
@ -240,12 +241,13 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
def assertRedirects(self, response, expected_url, status_code=302,
|
def assertRedirects(self, response, expected_url, status_code=302,
|
||||||
target_status_code=200, msg_prefix='',
|
target_status_code=200, msg_prefix='',
|
||||||
fetch_redirect_response=True):
|
fetch_redirect_response=True):
|
||||||
"""Asserts that a response redirected to a specific URL, and that the
|
"""
|
||||||
|
Assert that a response redirected to a specific URL and that the
|
||||||
redirect URL can be loaded.
|
redirect URL can be loaded.
|
||||||
|
|
||||||
Note that assertRedirects won't work for external links since it uses
|
Won't work for external links since it uses TestClient to do a request
|
||||||
TestClient to do a request (use fetch_redirect_response=False to check
|
(use fetch_redirect_response=False to check such links without fetching
|
||||||
such links without fetching them).
|
them).
|
||||||
"""
|
"""
|
||||||
if msg_prefix:
|
if msg_prefix:
|
||||||
msg_prefix += ": "
|
msg_prefix += ": "
|
||||||
|
@ -349,8 +351,8 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertContains(self, response, text, count=None, status_code=200, msg_prefix='', html=False):
|
def assertContains(self, response, text, count=None, status_code=200, msg_prefix='', html=False):
|
||||||
"""
|
"""
|
||||||
Asserts that a response indicates that some content was retrieved
|
Assert that a response indicates that some content was retrieved
|
||||||
successfully, (i.e., the HTTP status code was as expected), and that
|
successfully, (i.e., the HTTP status code was as expected) and that
|
||||||
``text`` occurs ``count`` times in the content of the response.
|
``text`` occurs ``count`` times in the content of the response.
|
||||||
If ``count`` is None, the count doesn't matter - the assertion is true
|
If ``count`` is None, the count doesn't matter - the assertion is true
|
||||||
if the text occurs at least once in the response.
|
if the text occurs at least once in the response.
|
||||||
|
@ -368,8 +370,8 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertNotContains(self, response, text, status_code=200, msg_prefix='', html=False):
|
def assertNotContains(self, response, text, status_code=200, msg_prefix='', html=False):
|
||||||
"""
|
"""
|
||||||
Asserts that a response indicates that some content was retrieved
|
Assert that a response indicates that some content was retrieved
|
||||||
successfully, (i.e., the HTTP status code was as expected), and that
|
successfully, (i.e., the HTTP status code was as expected) and that
|
||||||
``text`` doesn't occurs in the content of the response.
|
``text`` doesn't occurs in the content of the response.
|
||||||
"""
|
"""
|
||||||
text_repr, real_count, msg_prefix = self._assert_contains(
|
text_repr, real_count, msg_prefix = self._assert_contains(
|
||||||
|
@ -379,7 +381,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertFormError(self, response, form, field, errors, msg_prefix=''):
|
def assertFormError(self, response, form, field, errors, msg_prefix=''):
|
||||||
"""
|
"""
|
||||||
Asserts that a form used to render the response has a specific field
|
Assert that a form used to render the response has a specific field
|
||||||
error.
|
error.
|
||||||
"""
|
"""
|
||||||
if msg_prefix:
|
if msg_prefix:
|
||||||
|
@ -435,7 +437,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
def assertFormsetError(self, response, formset, form_index, field, errors,
|
def assertFormsetError(self, response, formset, form_index, field, errors,
|
||||||
msg_prefix=''):
|
msg_prefix=''):
|
||||||
"""
|
"""
|
||||||
Asserts that a formset used to render the response has a specific error.
|
Assert that a formset used to render the response has a specific error.
|
||||||
|
|
||||||
For field errors, specify the ``form_index`` and the ``field``.
|
For field errors, specify the ``form_index`` and the ``field``.
|
||||||
For non-field errors, specify the ``form_index`` and the ``field`` as
|
For non-field errors, specify the ``form_index`` and the ``field`` as
|
||||||
|
@ -538,7 +540,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertTemplateUsed(self, response=None, template_name=None, msg_prefix='', count=None):
|
def assertTemplateUsed(self, response=None, template_name=None, msg_prefix='', count=None):
|
||||||
"""
|
"""
|
||||||
Asserts that the template with the provided name was used in rendering
|
Assert that the template with the provided name was used in rendering
|
||||||
the response. Also usable as context manager.
|
the response. Also usable as context manager.
|
||||||
"""
|
"""
|
||||||
context_mgr_template, template_names, msg_prefix = self._assert_template_used(
|
context_mgr_template, template_names, msg_prefix = self._assert_template_used(
|
||||||
|
@ -567,7 +569,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertTemplateNotUsed(self, response=None, template_name=None, msg_prefix=''):
|
def assertTemplateNotUsed(self, response=None, template_name=None, msg_prefix=''):
|
||||||
"""
|
"""
|
||||||
Asserts that the template with the provided name was NOT used in
|
Assert that the template with the provided name was NOT used in
|
||||||
rendering the response. Also usable as context manager.
|
rendering the response. Also usable as context manager.
|
||||||
"""
|
"""
|
||||||
context_mgr_template, template_names, msg_prefix = self._assert_template_used(
|
context_mgr_template, template_names, msg_prefix = self._assert_template_used(
|
||||||
|
@ -590,7 +592,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertRaisesMessage(self, expected_exception, expected_message, *args, **kwargs):
|
def assertRaisesMessage(self, expected_exception, expected_message, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Asserts that expected_message is found in the the message of a raised
|
Assert that expected_message is found in the the message of a raised
|
||||||
exception.
|
exception.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -615,7 +617,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
|
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
|
||||||
field_kwargs=None, empty_value=''):
|
field_kwargs=None, empty_value=''):
|
||||||
"""
|
"""
|
||||||
Asserts that a form field behaves correctly with various inputs.
|
Assert that a form field behaves correctly with various inputs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
fieldclass: the class of the field to be tested.
|
fieldclass: the class of the field to be tested.
|
||||||
|
@ -660,9 +662,9 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertHTMLEqual(self, html1, html2, msg=None):
|
def assertHTMLEqual(self, html1, html2, msg=None):
|
||||||
"""
|
"""
|
||||||
Asserts that two HTML snippets are semantically the same.
|
Assert that two HTML snippets are semantically the same.
|
||||||
Whitespace in most cases is ignored, and attribute ordering is not
|
Whitespace in most cases is ignored, and attribute ordering is not
|
||||||
significant. The passed-in arguments must be valid HTML.
|
significant. The arguments must be valid HTML.
|
||||||
"""
|
"""
|
||||||
dom1 = assert_and_parse_html(self, html1, msg, 'First argument is not valid HTML:')
|
dom1 = assert_and_parse_html(self, html1, msg, 'First argument is not valid HTML:')
|
||||||
dom2 = assert_and_parse_html(self, html2, msg, 'Second argument is not valid HTML:')
|
dom2 = assert_and_parse_html(self, html2, msg, 'Second argument is not valid HTML:')
|
||||||
|
@ -677,7 +679,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
self.fail(self._formatMessage(msg, standardMsg))
|
self.fail(self._formatMessage(msg, standardMsg))
|
||||||
|
|
||||||
def assertHTMLNotEqual(self, html1, html2, msg=None):
|
def assertHTMLNotEqual(self, html1, html2, msg=None):
|
||||||
"""Asserts that two HTML snippets are not semantically equivalent."""
|
"""Assert that two HTML snippets are not semantically equivalent."""
|
||||||
dom1 = assert_and_parse_html(self, html1, msg, 'First argument is not valid HTML:')
|
dom1 = assert_and_parse_html(self, html1, msg, 'First argument is not valid HTML:')
|
||||||
dom2 = assert_and_parse_html(self, html2, msg, 'Second argument is not valid HTML:')
|
dom2 = assert_and_parse_html(self, html2, msg, 'Second argument is not valid HTML:')
|
||||||
|
|
||||||
|
@ -700,7 +702,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertJSONEqual(self, raw, expected_data, msg=None):
|
def assertJSONEqual(self, raw, expected_data, msg=None):
|
||||||
"""
|
"""
|
||||||
Asserts that the JSON fragments raw and expected_data are equal.
|
Assert that the JSON fragments raw and expected_data are equal.
|
||||||
Usual JSON non-significant whitespace rules apply as the heavyweight
|
Usual JSON non-significant whitespace rules apply as the heavyweight
|
||||||
is delegated to the json library.
|
is delegated to the json library.
|
||||||
"""
|
"""
|
||||||
|
@ -717,7 +719,7 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertJSONNotEqual(self, raw, expected_data, msg=None):
|
def assertJSONNotEqual(self, raw, expected_data, msg=None):
|
||||||
"""
|
"""
|
||||||
Asserts that the JSON fragments raw and expected_data are not equal.
|
Assert that the JSON fragments raw and expected_data are not equal.
|
||||||
Usual JSON non-significant whitespace rules apply as the heavyweight
|
Usual JSON non-significant whitespace rules apply as the heavyweight
|
||||||
is delegated to the json library.
|
is delegated to the json library.
|
||||||
"""
|
"""
|
||||||
|
@ -734,9 +736,9 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertXMLEqual(self, xml1, xml2, msg=None):
|
def assertXMLEqual(self, xml1, xml2, msg=None):
|
||||||
"""
|
"""
|
||||||
Asserts that two XML snippets are semantically the same.
|
Assert that two XML snippets are semantically the same.
|
||||||
Whitespace in most cases is ignored, and attribute ordering is not
|
Whitespace in most cases is ignored and attribute ordering is not
|
||||||
significant. The passed-in arguments must be valid XML.
|
significant. The arguments must be valid XML.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result = compare_xml(xml1, xml2)
|
result = compare_xml(xml1, xml2)
|
||||||
|
@ -754,9 +756,9 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def assertXMLNotEqual(self, xml1, xml2, msg=None):
|
def assertXMLNotEqual(self, xml1, xml2, msg=None):
|
||||||
"""
|
"""
|
||||||
Asserts that two XML snippets are not semantically equivalent.
|
Assert that two XML snippets are not semantically equivalent.
|
||||||
Whitespace in most cases is ignored, and attribute ordering is not
|
Whitespace in most cases is ignored and attribute ordering is not
|
||||||
significant. The passed-in arguments must be valid XML.
|
significant. The arguments must be valid XML.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result = compare_xml(xml1, xml2)
|
result = compare_xml(xml1, xml2)
|
||||||
|
@ -795,12 +797,12 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
allow_database_queries = True
|
allow_database_queries = True
|
||||||
|
|
||||||
def _pre_setup(self):
|
def _pre_setup(self):
|
||||||
"""Performs any pre-test setup. This includes:
|
"""
|
||||||
|
Perform pre-test setup:
|
||||||
* If the class has an 'available_apps' attribute, restricting the app
|
* If the class has an 'available_apps' attribute, restrict the app
|
||||||
registry to these applications, then firing post_migrate -- it must
|
registry to these applications, then fire the post_migrate signal --
|
||||||
run with the correct set of applications for the test case.
|
it must run with the correct set of applications for the test case.
|
||||||
* If the class has a 'fixtures' attribute, installing these fixtures.
|
* If the class has a 'fixtures' attribute, install those fixtures.
|
||||||
"""
|
"""
|
||||||
super()._pre_setup()
|
super()._pre_setup()
|
||||||
if self.available_apps is not None:
|
if self.available_apps is not None:
|
||||||
|
@ -876,11 +878,11 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _post_teardown(self):
|
def _post_teardown(self):
|
||||||
"""Performs any post-test things. This includes:
|
"""
|
||||||
|
Perform post-test things:
|
||||||
* Flushing the contents of the database, to leave a clean slate. If
|
* Flush the contents of the database to leave a clean slate. If the
|
||||||
the class has an 'available_apps' attribute, post_migrate isn't fired.
|
class has an 'available_apps' attribute, don't fire post_migrate.
|
||||||
* Force-closing the connection, so the next test gets a clean cursor.
|
* Force-close the connection so the next test gets a clean cursor.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._fixture_teardown()
|
self._fixture_teardown()
|
||||||
|
@ -944,16 +946,13 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
|
|
||||||
|
|
||||||
def connections_support_transactions():
|
def connections_support_transactions():
|
||||||
"""
|
"""Return True if all connections support transactions."""
|
||||||
Returns True if all connections support transactions.
|
return all(conn.features.supports_transactions for conn in connections.all())
|
||||||
"""
|
|
||||||
return all(conn.features.supports_transactions
|
|
||||||
for conn in connections.all())
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(TransactionTestCase):
|
class TestCase(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
Similar to TransactionTestCase, but uses `transaction.atomic()` to achieve
|
Similar to TransactionTestCase, but use `transaction.atomic()` to achieve
|
||||||
test isolation.
|
test isolation.
|
||||||
|
|
||||||
In most situations, TestCase should be preferred to TransactionTestCase as
|
In most situations, TestCase should be preferred to TransactionTestCase as
|
||||||
|
@ -966,7 +965,7 @@ class TestCase(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def _enter_atomics(cls):
|
def _enter_atomics(cls):
|
||||||
"""Helper method to open atomic blocks for multiple databases"""
|
"""Open atomic blocks for multiple databases."""
|
||||||
atomics = {}
|
atomics = {}
|
||||||
for db_name in cls._databases_names():
|
for db_name in cls._databases_names():
|
||||||
atomics[db_name] = transaction.atomic(using=db_name)
|
atomics[db_name] = transaction.atomic(using=db_name)
|
||||||
|
@ -975,7 +974,7 @@ class TestCase(TransactionTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _rollback_atomics(cls, atomics):
|
def _rollback_atomics(cls, atomics):
|
||||||
"""Rollback atomic blocks opened through the previous method"""
|
"""Rollback atomic blocks opened by the previous method."""
|
||||||
for db_name in reversed(cls._databases_names()):
|
for db_name in reversed(cls._databases_names()):
|
||||||
transaction.set_rollback(True, using=db_name)
|
transaction.set_rollback(True, using=db_name)
|
||||||
atomics[db_name].__exit__(None, None, None)
|
atomics[db_name].__exit__(None, None, None)
|
||||||
|
@ -1014,7 +1013,7 @@ class TestCase(TransactionTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
"""Load initial data for the TestCase"""
|
"""Load initial data for the TestCase."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _should_reload_connections(self):
|
def _should_reload_connections(self):
|
||||||
|
@ -1050,7 +1049,7 @@ class TestCase(TransactionTestCase):
|
||||||
|
|
||||||
|
|
||||||
class CheckCondition:
|
class CheckCondition:
|
||||||
"""Descriptor class for deferred condition checking"""
|
"""Descriptor class for deferred condition checking."""
|
||||||
def __init__(self, *conditions):
|
def __init__(self, *conditions):
|
||||||
self.conditions = conditions
|
self.conditions = conditions
|
||||||
|
|
||||||
|
@ -1095,9 +1094,7 @@ def _deferredSkip(condition, reason):
|
||||||
|
|
||||||
|
|
||||||
def skipIfDBFeature(*features):
|
def skipIfDBFeature(*features):
|
||||||
"""
|
"""Skip a test if a database has at least one of the named features."""
|
||||||
Skip a test if a database has at least one of the named features.
|
|
||||||
"""
|
|
||||||
return _deferredSkip(
|
return _deferredSkip(
|
||||||
lambda: any(getattr(connection.features, feature, False) for feature in features),
|
lambda: any(getattr(connection.features, feature, False) for feature in features),
|
||||||
"Database has feature(s) %s" % ", ".join(features)
|
"Database has feature(s) %s" % ", ".join(features)
|
||||||
|
@ -1105,9 +1102,7 @@ def skipIfDBFeature(*features):
|
||||||
|
|
||||||
|
|
||||||
def skipUnlessDBFeature(*features):
|
def skipUnlessDBFeature(*features):
|
||||||
"""
|
"""Skip a test unless a database has all the named features."""
|
||||||
Skip a test unless a database has all the named features.
|
|
||||||
"""
|
|
||||||
return _deferredSkip(
|
return _deferredSkip(
|
||||||
lambda: not all(getattr(connection.features, feature, False) for feature in features),
|
lambda: not all(getattr(connection.features, feature, False) for feature in features),
|
||||||
"Database doesn't support feature(s): %s" % ", ".join(features)
|
"Database doesn't support feature(s): %s" % ", ".join(features)
|
||||||
|
@ -1115,9 +1110,7 @@ def skipUnlessDBFeature(*features):
|
||||||
|
|
||||||
|
|
||||||
def skipUnlessAnyDBFeature(*features):
|
def skipUnlessAnyDBFeature(*features):
|
||||||
"""
|
"""Skip a test unless a database has any of the named features."""
|
||||||
Skip a test unless a database has any of the named features.
|
|
||||||
"""
|
|
||||||
return _deferredSkip(
|
return _deferredSkip(
|
||||||
lambda: not any(getattr(connection.features, feature, False) for feature in features),
|
lambda: not any(getattr(connection.features, feature, False) for feature in features),
|
||||||
"Database doesn't support any of the feature(s): %s" % ", ".join(features)
|
"Database doesn't support any of the feature(s): %s" % ", ".join(features)
|
||||||
|
@ -1126,11 +1119,9 @@ def skipUnlessAnyDBFeature(*features):
|
||||||
|
|
||||||
class QuietWSGIRequestHandler(WSGIRequestHandler):
|
class QuietWSGIRequestHandler(WSGIRequestHandler):
|
||||||
"""
|
"""
|
||||||
Just a regular WSGIRequestHandler except it doesn't log to the standard
|
A WSGIRequestHandler that doesn't log to standard output any of the
|
||||||
output any of the requests received, so as to not clutter the output for
|
requests received, so as to not clutter the test result output.
|
||||||
the tests' results.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def log_message(*args):
|
def log_message(*args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1147,17 +1138,14 @@ class FSFilesHandler(WSGIHandler):
|
||||||
|
|
||||||
def _should_handle(self, path):
|
def _should_handle(self, path):
|
||||||
"""
|
"""
|
||||||
Checks if the path should be handled. Ignores the path if:
|
Check if the path should be handled. Ignore the path if:
|
||||||
|
|
||||||
* the host is provided as part of the base_url
|
* the host is provided as part of the base_url
|
||||||
* the request's path isn't under the media path (or equal)
|
* the request's path isn't under the media path (or equal)
|
||||||
"""
|
"""
|
||||||
return path.startswith(self.base_url[2]) and not self.base_url[1]
|
return path.startswith(self.base_url[2]) and not self.base_url[1]
|
||||||
|
|
||||||
def file_path(self, url):
|
def file_path(self, url):
|
||||||
"""
|
"""Return the relative path to the file on disk for the given URL."""
|
||||||
Returns the relative path to the file on disk for the given URL.
|
|
||||||
"""
|
|
||||||
relative_url = url[len(self.base_url[2]):]
|
relative_url = url[len(self.base_url[2]):]
|
||||||
return url2pathname(relative_url)
|
return url2pathname(relative_url)
|
||||||
|
|
||||||
|
@ -1191,7 +1179,6 @@ class _StaticFilesHandler(FSFilesHandler):
|
||||||
Handler for serving static files. A private class that is meant to be used
|
Handler for serving static files. A private class that is meant to be used
|
||||||
solely as a convenience by LiveServerThread.
|
solely as a convenience by LiveServerThread.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_base_dir(self):
|
def get_base_dir(self):
|
||||||
return settings.STATIC_ROOT
|
return settings.STATIC_ROOT
|
||||||
|
|
||||||
|
@ -1204,7 +1191,6 @@ class _MediaFilesHandler(FSFilesHandler):
|
||||||
Handler for serving the media files. A private class that is meant to be
|
Handler for serving the media files. A private class that is meant to be
|
||||||
used solely as a convenience by LiveServerThread.
|
used solely as a convenience by LiveServerThread.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_base_dir(self):
|
def get_base_dir(self):
|
||||||
return settings.MEDIA_ROOT
|
return settings.MEDIA_ROOT
|
||||||
|
|
||||||
|
@ -1213,9 +1199,7 @@ class _MediaFilesHandler(FSFilesHandler):
|
||||||
|
|
||||||
|
|
||||||
class LiveServerThread(threading.Thread):
|
class LiveServerThread(threading.Thread):
|
||||||
"""
|
"""Thread for running a live http server while the tests are running."""
|
||||||
Thread for running a live http server while the tests are running.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, host, static_handler, connections_override=None):
|
def __init__(self, host, static_handler, connections_override=None):
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -1228,8 +1212,8 @@ class LiveServerThread(threading.Thread):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
Sets up the live server and databases, and then loops over handling
|
Set up the live server and databases, and then loop over handling
|
||||||
http requests.
|
HTTP requests.
|
||||||
"""
|
"""
|
||||||
if self.connections_override:
|
if self.connections_override:
|
||||||
# Override this thread's database connections with the ones
|
# Override this thread's database connections with the ones
|
||||||
|
@ -1263,14 +1247,14 @@ class LiveServerThread(threading.Thread):
|
||||||
|
|
||||||
class LiveServerTestCase(TransactionTestCase):
|
class LiveServerTestCase(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
Does basically the same as TransactionTestCase but also launches a live
|
Do basically the same as TransactionTestCase but also launch a live HTTP
|
||||||
http server in a separate thread so that the tests may use another testing
|
server in a separate thread so that the tests may use another testing
|
||||||
framework, such as Selenium for example, instead of the built-in dummy
|
framework, such as Selenium for example, instead of the built-in dummy
|
||||||
client.
|
client.
|
||||||
Note that it inherits from TransactionTestCase instead of TestCase because
|
It inherits from TransactionTestCase instead of TestCase because the
|
||||||
the threads do not share the same transactions (unless if using in-memory
|
threads don't share the same transactions (unless if using in-memory sqlite)
|
||||||
sqlite) and each thread needs to commit all their transactions so that the
|
and each thread needs to commit all their transactions so that the other
|
||||||
other thread can see the changes.
|
thread can see the changes.
|
||||||
"""
|
"""
|
||||||
host = 'localhost'
|
host = 'localhost'
|
||||||
server_thread_class = LiveServerThread
|
server_thread_class = LiveServerThread
|
||||||
|
@ -1338,14 +1322,13 @@ class LiveServerTestCase(TransactionTestCase):
|
||||||
|
|
||||||
class SerializeMixin:
|
class SerializeMixin:
|
||||||
"""
|
"""
|
||||||
Mixin to enforce serialization of TestCases that share a common resource.
|
Enforce serialization of TestCases that share a common resource.
|
||||||
|
|
||||||
Define a common 'lockfile' for each set of TestCases to serialize. This
|
Define a common 'lockfile' for each set of TestCases to serialize. This
|
||||||
file must exist on the filesystem.
|
file must exist on the filesystem.
|
||||||
|
|
||||||
Place it early in the MRO in order to isolate setUpClass / tearDownClass.
|
Place it early in the MRO in order to isolate setUpClass()/tearDownClass().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lockfile = None
|
lockfile = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -55,7 +55,8 @@ class Approximate:
|
||||||
|
|
||||||
|
|
||||||
class ContextList(list):
|
class ContextList(list):
|
||||||
"""A wrapper that provides direct key access to context items contained
|
"""
|
||||||
|
A wrapper that provides direct key access to context items contained
|
||||||
in a list of context objects.
|
in a list of context objects.
|
||||||
"""
|
"""
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
@ -93,8 +94,8 @@ class ContextList(list):
|
||||||
|
|
||||||
def instrumented_test_render(self, context):
|
def instrumented_test_render(self, context):
|
||||||
"""
|
"""
|
||||||
An instrumented Template render method, providing a signal
|
An instrumented Template render method, providing a signal that can be
|
||||||
that can be intercepted by the test system Client
|
intercepted by the test Client.
|
||||||
"""
|
"""
|
||||||
template_rendered.send(sender=self, template=self, context=context)
|
template_rendered.send(sender=self, template=self, context=context)
|
||||||
return self.nodelist.render(context)
|
return self.nodelist.render(context)
|
||||||
|
@ -157,9 +158,7 @@ def teardown_test_environment():
|
||||||
|
|
||||||
|
|
||||||
def setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs):
|
def setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs):
|
||||||
"""
|
"""Create the test databases."""
|
||||||
Create the test databases.
|
|
||||||
"""
|
|
||||||
test_databases, mirrored_aliases = get_unique_databases_and_mirrors()
|
test_databases, mirrored_aliases = get_unique_databases_and_mirrors()
|
||||||
|
|
||||||
old_names = []
|
old_names = []
|
||||||
|
@ -290,9 +289,7 @@ def get_unique_databases_and_mirrors():
|
||||||
|
|
||||||
|
|
||||||
def teardown_databases(old_config, verbosity, parallel=0, keepdb=False):
|
def teardown_databases(old_config, verbosity, parallel=0, keepdb=False):
|
||||||
"""
|
"""Destroy all the non-mirror databases."""
|
||||||
Destroy all the non-mirror databases.
|
|
||||||
"""
|
|
||||||
for connection, old_name, destroy in old_config:
|
for connection, old_name, destroy in old_config:
|
||||||
if destroy:
|
if destroy:
|
||||||
if parallel > 1:
|
if parallel > 1:
|
||||||
|
@ -387,10 +384,10 @@ class TestContextDecorator:
|
||||||
|
|
||||||
class override_settings(TestContextDecorator):
|
class override_settings(TestContextDecorator):
|
||||||
"""
|
"""
|
||||||
Acts as either a decorator or a context manager. If it's a decorator it
|
Act as either a decorator or a context manager. If it's a decorator, take a
|
||||||
takes a function and returns a wrapped function. If it's a contextmanager
|
function and return a wrapped function. If it's a contextmanager, use it
|
||||||
it's used with the ``with`` statement. In either event entering/exiting
|
with the ``with`` statement. In either event, entering/exiting are called
|
||||||
are called before and after, respectively, the function/block is executed.
|
before and after, respectively, the function/block is executed.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.options = kwargs
|
self.options = kwargs
|
||||||
|
@ -444,7 +441,7 @@ class override_settings(TestContextDecorator):
|
||||||
|
|
||||||
class modify_settings(override_settings):
|
class modify_settings(override_settings):
|
||||||
"""
|
"""
|
||||||
Like override_settings, but makes it possible to append, prepend or remove
|
Like override_settings, but makes it possible to append, prepend, or remove
|
||||||
items instead of redefining the entire list.
|
items instead of redefining the entire list.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -492,7 +489,7 @@ class modify_settings(override_settings):
|
||||||
|
|
||||||
class override_system_checks(TestContextDecorator):
|
class override_system_checks(TestContextDecorator):
|
||||||
"""
|
"""
|
||||||
Acts as a decorator. Overrides list of registered system checks.
|
Act as a decorator. Override list of registered system checks.
|
||||||
Useful when you override `INSTALLED_APPS`, e.g. if you exclude `auth` app,
|
Useful when you override `INSTALLED_APPS`, e.g. if you exclude `auth` app,
|
||||||
you also need to exclude its system checks.
|
you also need to exclude its system checks.
|
||||||
"""
|
"""
|
||||||
|
@ -516,10 +513,10 @@ class override_system_checks(TestContextDecorator):
|
||||||
|
|
||||||
|
|
||||||
def compare_xml(want, got):
|
def compare_xml(want, got):
|
||||||
"""Tries to do a 'xml-comparison' of want and got. Plain string
|
"""
|
||||||
comparison doesn't always work because, for example, attribute
|
Try to do a 'xml-comparison' of want and got. Plain string comparison
|
||||||
ordering should not be important. Comment nodes are not considered in the
|
doesn't always work because, for example, attribute ordering should not be
|
||||||
comparison. Leading and trailing whitespace is ignored on both chunks.
|
important. Ignore comment nodes and leading and trailing whitespace.
|
||||||
|
|
||||||
Based on https://github.com/lxml/lxml/blob/master/src/lxml/doctestcompare.py
|
Based on https://github.com/lxml/lxml/blob/master/src/lxml/doctestcompare.py
|
||||||
"""
|
"""
|
||||||
|
@ -794,9 +791,7 @@ def require_jinja2(test_func):
|
||||||
|
|
||||||
|
|
||||||
class override_script_prefix(TestContextDecorator):
|
class override_script_prefix(TestContextDecorator):
|
||||||
"""
|
"""Decorator or context manager to temporary override the script prefix."""
|
||||||
Decorator or context manager to temporary override the script prefix.
|
|
||||||
"""
|
|
||||||
def __init__(self, prefix):
|
def __init__(self, prefix):
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -840,7 +835,6 @@ class isolate_apps(TestContextDecorator):
|
||||||
`kwarg_name`: keyword argument passing the isolated registry if used as a
|
`kwarg_name`: keyword argument passing the isolated registry if used as a
|
||||||
function decorator.
|
function decorator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *installed_apps, **kwargs):
|
def __init__(self, *installed_apps, **kwargs):
|
||||||
self.installed_apps = installed_apps
|
self.installed_apps = installed_apps
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -856,9 +850,7 @@ class isolate_apps(TestContextDecorator):
|
||||||
|
|
||||||
|
|
||||||
def tag(*tags):
|
def tag(*tags):
|
||||||
"""
|
"""Decorator to add tags to a test class or method."""
|
||||||
Decorator to add tags to a test class or method.
|
|
||||||
"""
|
|
||||||
def decorator(obj):
|
def decorator(obj):
|
||||||
setattr(obj, 'tags', set(tags))
|
setattr(obj, 'tags', set(tags))
|
||||||
return obj
|
return obj
|
||||||
|
|
Loading…
Reference in New Issue