diff --git a/docs/conf.py b/docs/conf.py index 222c47d6..3c12a0db 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -92,8 +92,8 @@ intersphinx_mapping = { 'django': ( - 'https://docs.djangoproject.com/en/1.11/', - 'https://docs.djangoproject.com/en/1.11/_objects/'), + 'https://docs.djangoproject.com/en/4.2/', + 'https://docs.djangoproject.com/en/4.2/_objects/'), } # -- Options for HTML output --------------------------------------------------- diff --git a/docs/install.rst b/docs/install.rst index 95098fd5..199c8e86 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -257,7 +257,7 @@ Or you can create a completely separate project for the main website. Caching ------- -To enable tenant aware caching you can set the `KEY_FUNCTION `_ setting to use the provided ``make_key`` helper function which +To enable tenant aware caching you can set the `KEY_FUNCTION `_ setting to use the provided ``make_key`` helper function which adds the tenants ``schema_name`` as the first key prefix. .. code-block:: python diff --git a/examples/tenant_tutorial/customers/views.py b/examples/tenant_tutorial/customers/views.py index 71dec1a0..3c5c613d 100644 --- a/examples/tenant_tutorial/customers/views.py +++ b/examples/tenant_tutorial/customers/views.py @@ -12,7 +12,7 @@ class TenantView(FormView): success_url = "/" def get_context_data(self, **kwargs): - context = super(TenantView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['tenants_list'] = Client.objects.all() context['users'] = User.objects.all() return context @@ -41,4 +41,4 @@ def form_valid(self, form): except DatabaseError: pass - return super(TenantView, self).form_valid(form) + return super().form_valid(form) diff --git a/examples/tenant_tutorial/tenant_tutorial/views.py b/examples/tenant_tutorial/tenant_tutorial/views.py index 451f3174..3e43499c 100644 --- a/examples/tenant_tutorial/tenant_tutorial/views.py +++ b/examples/tenant_tutorial/tenant_tutorial/views.py @@ -10,7 +10,7 @@ class HomeView(TemplateView): template_name = "index_public.html" def get_context_data(self, **kwargs): - context = super(HomeView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) hostname_without_port = remove_www(self.request.get_host().split(':')[0]) diff --git a/src/tenant_schemas/management/commands/__init__.py b/src/tenant_schemas/management/commands/__init__.py index c481a8b8..76c28b5d 100644 --- a/src/tenant_schemas/management/commands/__init__.py +++ b/src/tenant_schemas/management/commands/__init__.py @@ -21,7 +21,7 @@ def __new__(cls, *args, **kwargs): """ Sets option_list and help dynamically. """ - obj = super(BaseTenantCommand, cls).__new__(cls, *args, **kwargs) + obj = super().__new__(cls, *args, **kwargs) app_name = get_commands()[obj.COMMAND_NAME] if isinstance(app_name, BaseCommand): @@ -43,7 +43,7 @@ def __new__(cls, *args, **kwargs): return obj def add_arguments(self, parser): - super(BaseTenantCommand, self).add_arguments(parser) + super().add_arguments(parser) parser.add_argument("-s", "--schema", dest="schema_name") parser.add_argument( "-p", @@ -148,12 +148,12 @@ class variable COMMAND_NAME of the subclass. """ def __new__(cls, *args, **kwargs): - obj = super(TenantWrappedCommand, cls).__new__(cls, *args, **kwargs) + obj = super().__new__(cls, *args, **kwargs) obj.command_instance = obj.COMMAND() return obj def add_arguments(self, parser): - super(TenantWrappedCommand, self).add_arguments(parser) + super().add_arguments(parser) self.command_instance.add_arguments(parser) def handle(self, *args, **options): diff --git a/src/tenant_schemas/management/commands/migrate.py b/src/tenant_schemas/management/commands/migrate.py index 87d0493a..8acc618e 100644 --- a/src/tenant_schemas/management/commands/migrate.py +++ b/src/tenant_schemas/management/commands/migrate.py @@ -15,7 +15,7 @@ def handle(self, *args, **options): raise CommandError("migrate has been disabled, for database '{0}'. Use migrate_schemas " "instead. Please read the documentation if you don't know why you " "shouldn't call migrate directly!".format(database)) - super(Command, self).handle(*args, **options) + super().handle(*args, **options) if django_is_in_test_mode(): diff --git a/src/tenant_schemas/management/commands/migrate_schemas.py b/src/tenant_schemas/management/commands/migrate_schemas.py index b034bad4..46137fb8 100644 --- a/src/tenant_schemas/management/commands/migrate_schemas.py +++ b/src/tenant_schemas/management/commands/migrate_schemas.py @@ -18,12 +18,12 @@ class Command(SyncCommon): ) def add_arguments(self, parser): - super(Command, self).add_arguments(parser) + super().add_arguments(parser) command = MigrateCommand() command.add_arguments(parser) def handle(self, *args, **options): - super(Command, self).handle(*args, **options) + super().handle(*args, **options) self.PUBLIC_SCHEMA_NAME = get_public_schema_name() executor = get_executor(codename=self.executor)(self.args, self.options) diff --git a/src/tenant_schemas/models.py b/src/tenant_schemas/models.py index d51aa34c..b2a574ed 100644 --- a/src/tenant_schemas/models.py +++ b/src/tenant_schemas/models.py @@ -64,7 +64,7 @@ def save(self, verbosity=1, *args, **kwargs): "the public schema. Current schema is %s." % connection.schema_name) - super(TenantMixin, self).save(*args, **kwargs) + super().save(*args, **kwargs) if is_new and self.auto_create_schema: try: @@ -91,7 +91,7 @@ def delete(self, force_drop=False, *args, **kwargs): cursor = connection.cursor() cursor.execute('DROP SCHEMA IF EXISTS %s CASCADE' % self.schema_name) - return super(TenantMixin, self).delete(*args, **kwargs) + return super().delete(*args, **kwargs) def create_schema(self, check_if_exists=False, sync_schema=True, verbosity=1): diff --git a/src/tenant_schemas/postgresql_backend/base.py b/src/tenant_schemas/postgresql_backend/base.py index 42c63521..575969fa 100644 --- a/src/tenant_schemas/postgresql_backend/base.py +++ b/src/tenant_schemas/postgresql_backend/base.py @@ -53,7 +53,7 @@ class DatabaseWrapper(original_backend.DatabaseWrapper): include_public_schema = True def __init__(self, *args, **kwargs): - super(DatabaseWrapper, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Use a patched version of the DatabaseIntrospection that only returns the table list for the # currently selected schema. @@ -62,10 +62,10 @@ def __init__(self, *args, **kwargs): def close(self): self.search_path_set = False - super(DatabaseWrapper, self).close() + super().close() def rollback(self): - super(DatabaseWrapper, self).rollback() + super().rollback() # Django's rollback clears the search path so we have to set it again the next time. self.search_path_set = False @@ -121,10 +121,10 @@ def _cursor(self, name=None): must go through this to get the cursor handle. We change the path. """ if name: - # Only supported and required by Django 1.11 (server-side cursor) - cursor = super(DatabaseWrapper, self)._cursor(name=name) + # Create server-side cursor (supported across Django versions) + cursor = super()._cursor(name=name) else: - cursor = super(DatabaseWrapper, self)._cursor() + cursor = super()._cursor() # optionally limit the number of executions - under load, the execution # of `set search_path` can be quite time consuming diff --git a/src/tenant_schemas/postgresql_backend/introspection.py b/src/tenant_schemas/postgresql_backend/introspection.py index d6fb9105..a6a23a69 100644 --- a/src/tenant_schemas/postgresql_backend/introspection.py +++ b/src/tenant_schemas/postgresql_backend/introspection.py @@ -1,15 +1,9 @@ -from __future__ import unicode_literals - from collections import namedtuple from django.db.backends.base.introspection import ( BaseDatabaseIntrospection, FieldInfo, TableInfo, ) -try: - # Django >= 1.11 - from django.db.models.indexes import Index -except ImportError: - Index = None +from django.db.models.indexes import Index from django.utils.encoding import force_str fields = FieldInfo._fields @@ -175,7 +169,7 @@ class DatabaseSchemaIntrospection(BaseDatabaseIntrospection): """ def get_field_type(self, data_type, description): - field_type = super(DatabaseSchemaIntrospection, self).get_field_type(data_type, description) + field_type = super().get_field_type(data_type, description) if description.default and 'nextval' in description.default: if field_type == 'IntegerField': return 'AutoField' @@ -310,7 +304,7 @@ def get_constraints(self, cursor, table_name): "foreign_key": None, "check": False, "index": True, - "type": Index.suffix if type_ == 'btree' and Index else type_, + "type": Index.suffix if type_ == 'btree' else type_, "definition": definition, "options": options, } diff --git a/src/tenant_schemas/routers.py b/src/tenant_schemas/routers.py index 5702e81c..43fb2955 100644 --- a/src/tenant_schemas/routers.py +++ b/src/tenant_schemas/routers.py @@ -1,7 +1,10 @@ from django.conf import settings -from django.db.models.base import ModelBase +from django.db import connection from django.db.utils import load_backend +from tenant_schemas.postgresql_backend.base import DatabaseWrapper as TenantDbWrapper +from tenant_schemas.utils import app_labels, get_public_schema_name + class TenantSyncRouter(object): """ @@ -10,20 +13,12 @@ class TenantSyncRouter(object): """ def allow_migrate(self, db, app_label, model_name=None, **hints): - # the imports below need to be done here else django <1.5 goes crazy - # https://code.djangoproject.com/ticket/20704 - from django.db import connection - from tenant_schemas.utils import get_public_schema_name, app_labels - from tenant_schemas.postgresql_backend.base import DatabaseWrapper as TenantDbWrapper db_engine = settings.DATABASES[db]['ENGINE'] if not (db_engine == 'tenant_schemas.postgresql_backend' or issubclass(getattr(load_backend(db_engine), 'DatabaseWrapper'), TenantDbWrapper)): return None - if isinstance(app_label, ModelBase): - # In django <1.7 the `app_label` parameter is actually `model` - app_label = app_label._meta.app_label if connection.schema_name == get_public_schema_name(): if app_label not in app_labels(settings.SHARED_APPS): @@ -34,6 +29,3 @@ def allow_migrate(self, db, app_label, model_name=None, **hints): return None - def allow_syncdb(self, db, model): - # allow_syncdb was changed to allow_migrate in django 1.7 - return self.allow_migrate(db, model) diff --git a/src/tenant_schemas/template_loaders.py b/src/tenant_schemas/template_loaders.py index e8a96726..fdbbf102 100644 --- a/src/tenant_schemas/template_loaders.py +++ b/src/tenant_schemas/template_loaders.py @@ -13,7 +13,7 @@ class CachedLoader(cached.Loader): def cache_key(self, *args, **kwargs): - key = super(CachedLoader, self).cache_key(*args, **kwargs) + key = super().cache_key(*args, **kwargs) if not connection.tenant or isinstance(connection.tenant, FakeTenant): return key @@ -23,7 +23,7 @@ def cache_key(self, *args, **kwargs): class FilesystemLoader(filesystem.Loader): def get_dirs(self): - dirs = OrderedSet(super(FilesystemLoader, self).get_dirs()) + dirs = OrderedSet(super().get_dirs()) if connection.tenant and not isinstance(connection.tenant, FakeTenant): try: diff --git a/src/tenant_schemas/templatetags/tenant.py b/src/tenant_schemas/templatetags/tenant.py index d810e038..1d7fd038 100644 --- a/src/tenant_schemas/templatetags/tenant.py +++ b/src/tenant_schemas/templatetags/tenant.py @@ -7,10 +7,10 @@ class SchemaURLNode(URLNode): def __init__(self, url_node): - super(SchemaURLNode, self).__init__(url_node.view_name, url_node.args, url_node.kwargs, url_node.asvar) + super().__init__(url_node.view_name, url_node.args, url_node.kwargs, url_node.asvar) def render(self, context): - url = super(SchemaURLNode, self).render(context) + url = super().render(context) return clean_tenant_url(url) diff --git a/src/tenant_schemas/test/client.py b/src/tenant_schemas/test/client.py index a213ac84..cccf5f63 100644 --- a/src/tenant_schemas/test/client.py +++ b/src/tenant_schemas/test/client.py @@ -6,75 +6,75 @@ class TenantRequestFactory(RequestFactory): tm = TenantMiddleware(lambda r:r) def __init__(self, tenant, **defaults): - super(TenantRequestFactory, self).__init__(**defaults) + super().__init__(**defaults) self.tenant = tenant def get(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantRequestFactory, self).get(path, data, **extra) + return super().get(path, data, **extra) def post(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantRequestFactory, self).post(path, data, **extra) + return super().post(path, data, **extra) def patch(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantRequestFactory, self).patch(path, data, **extra) + return super().patch(path, data, **extra) def put(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantRequestFactory, self).put(path, data, **extra) + return super().put(path, data, **extra) def delete(self, path, data='', content_type='application/octet-stream', **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantRequestFactory, self).delete(path, data, **extra) + return super().delete(path, data, **extra) class TenantClient(Client): tm = TenantMiddleware(lambda r:r) def __init__(self, tenant, enforce_csrf_checks=False, **defaults): - super(TenantClient, self).__init__(enforce_csrf_checks, **defaults) + super().__init__(enforce_csrf_checks, **defaults) self.tenant = tenant def get(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantClient, self).get(path, data, **extra) + return super().get(path, data, **extra) def post(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantClient, self).post(path, data, **extra) + return super().post(path, data, **extra) def patch(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantClient, self).patch(path, data, **extra) + return super().patch(path, data, **extra) def put(self, path, data={}, **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantClient, self).put(path, data, **extra) + return super().put(path, data, **extra) def delete(self, path, data='', content_type='application/octet-stream', **extra): if 'HTTP_HOST' not in extra: extra['HTTP_HOST'] = self.tenant.domain_url - return super(TenantClient, self).delete(path, data, **extra) + return super().delete(path, data, **extra) diff --git a/src/tenant_schemas/tests/test_routes.py b/src/tenant_schemas/tests/test_routes.py index fa3b6acc..0a9b8b7c 100644 --- a/src/tenant_schemas/tests/test_routes.py +++ b/src/tenant_schemas/tests/test_routes.py @@ -21,7 +21,7 @@ class MissingDefaultTenantMiddleware(DefaultTenantMiddleware): class RoutesTestCase(BaseTestCase): @classmethod def setUpClass(cls): - super(RoutesTestCase, cls).setUpClass() + super().setUpClass() settings.SHARED_APPS = ("tenant_schemas",) settings.TENANT_APPS = ( "dts_test_app", @@ -36,7 +36,7 @@ def setUpClass(cls): cls.public_tenant.save(verbosity=BaseTestCase.get_verbosity()) def setUp(self): - super(RoutesTestCase, self).setUp() + super().setUp() self.factory = RequestFactory() self.tm = TenantMiddleware(lambda r: r) self.dtm = DefaultTenantMiddleware(lambda r: r) diff --git a/src/tenant_schemas/tests/test_tenants.py b/src/tenant_schemas/tests/test_tenants.py index a43b0f6a..b2e57cbc 100644 --- a/src/tenant_schemas/tests/test_tenants.py +++ b/src/tenant_schemas/tests/test_tenants.py @@ -14,12 +14,7 @@ tenant_context, ) -try: - # python 2 - from StringIO import StringIO -except ImportError: - # python 3 - from io import StringIO +from io import StringIO class TenantDataAndSettingsTest(BaseTestCase): @@ -30,7 +25,7 @@ class TenantDataAndSettingsTest(BaseTestCase): @classmethod def setUpClass(cls): - super(TenantDataAndSettingsTest, cls).setUpClass() + super().setUpClass() settings.SHARED_APPS = ("tenant_schemas",) settings.TENANT_APPS = ( "dts_test_app", @@ -308,7 +303,7 @@ def test_command(self): class SharedAuthTest(BaseTestCase): @classmethod def setUpClass(cls): - super(SharedAuthTest, cls).setUpClass() + super().setUpClass() settings.SHARED_APPS = ( "tenant_schemas", "django.contrib.auth", diff --git a/src/tenant_schemas/tests/test_utils.py b/src/tenant_schemas/tests/test_utils.py index a740240a..2cb33345 100644 --- a/src/tenant_schemas/tests/test_utils.py +++ b/src/tenant_schemas/tests/test_utils.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import sys import types @@ -30,8 +28,8 @@ def set_up_module(self, whole_name): def test_app_labels(self): """ - Verifies that app_labels handle Django 1.7+ AppConfigs properly. - https://docs.djangoproject.com/en/1.7/ref/applications/ + Verifies that app_labels handle Django AppConfigs properly. + https://docs.djangoproject.com/en/4.2/ref/applications/ """ self.set_up_module('example1') apps = self.set_up_module('example2.apps') diff --git a/src/tenant_schemas/tests/testcases.py b/src/tenant_schemas/tests/testcases.py index 964c76f2..2bcbf438 100644 --- a/src/tenant_schemas/tests/testcases.py +++ b/src/tenant_schemas/tests/testcases.py @@ -30,18 +30,18 @@ def setUpClass(cls): cursor = connection.cursor() cursor.execute('DROP SCHEMA IF EXISTS %s CASCADE; CREATE SCHEMA %s;' % (get_public_schema_name(), get_public_schema_name())) - super(BaseTestCase, cls).setUpClass() + super().setUpClass() @classmethod def tearDownClass(cls): - super(BaseTestCase, cls).tearDownClass() + super().tearDownClass() if '.test.com' in settings.ALLOWED_HOSTS: settings.ALLOWED_HOSTS.remove('.test.com') def setUp(self): connection.set_schema_to_public() - super(BaseTestCase, self).setUp() + super().setUp() @classmethod def get_verbosity(self): diff --git a/src/tenant_schemas/utils.py b/src/tenant_schemas/utils.py index 338ebc6a..809767ce 100644 --- a/src/tenant_schemas/utils.py +++ b/src/tenant_schemas/utils.py @@ -3,12 +3,8 @@ from django.conf import settings from django.db import connection -try: - from django.apps import apps, AppConfig - get_model = apps.get_model -except ImportError: - from django.db.models.loading import get_model - AppConfig = None +from django.apps import apps, AppConfig +get_model = apps.get_model from django.core import mail @@ -108,11 +104,8 @@ def schema_exists(schema_name): def app_labels(apps_list): """ - Returns a list of app labels of the given apps_list, now properly handles - new Django 1.7+ application registry. + Returns a list of app labels of the given apps_list using Django application registry. - https://docs.djangoproject.com/en/1.8/ref/applications/#django.apps.AppConfig.label + https://docs.djangoproject.com/en/4.2/ref/applications/#django.apps.AppConfig.label """ - if AppConfig is None: - return [app.split('.')[-1] for app in apps_list] return [AppConfig.create(app).label for app in apps_list]