From 0d920535a266c870f654ab2272e27dc754fd1842 Mon Sep 17 00:00:00 2001 From: Angelo Dini Date: Thu, 7 Mar 2024 14:45:49 +0100 Subject: [PATCH 1/3] restore previous version --- CHANGELOG.rst | 13 +- PKG-INFO | 30 +++ aldryn_config.py | 11 +- pyproject.toml | 29 +-- setup.cfg | 56 +---- setup.py | 49 +++++ src/django_multisite_plus/apps.py | 51 +++-- src/django_multisite_plus/cli.py | 192 +++++++++--------- src/django_multisite_plus/cms_urls.py | 2 - src/django_multisite_plus/conf.py | 11 +- src/django_multisite_plus/constants.py | 86 -------- .../fastrouter_lookup.py | 14 +- .../management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/multisite_plus_populate_sites.py | 15 -- .../multisite_plus_rewrite_domains.py | 20 -- src/django_multisite_plus/middlewares.py | 4 +- .../migrations/0001_initial.py | 2 +- src/django_multisite_plus/models.py | 43 ++-- src/django_multisite_plus/utils.py | 2 - tests/conftest.py | 16 -- tests/test_admin.py | 1 - tests/test_apps.py | 60 ------ tests/test_cli.py | 34 ---- tests/test_commands.py | 29 --- tests/test_conf.py | 1 - tests/test_fastrouter_lookup.py | 8 - tests/test_middleware.py | 1 - tests/test_models.py | 79 ------- tests/testproject/__init__.py | 0 tests/testproject/settings.py | 33 --- tests/testproject/urls.py | 1 - tox.ini | 23 +-- 33 files changed, 250 insertions(+), 666 deletions(-) create mode 100644 PKG-INFO create mode 100644 setup.py delete mode 100644 src/django_multisite_plus/management/__init__.py delete mode 100644 src/django_multisite_plus/management/commands/__init__.py delete mode 100644 src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py delete mode 100644 src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py delete mode 100644 src/django_multisite_plus/utils.py delete mode 100644 tests/conftest.py delete mode 100644 tests/test_admin.py delete mode 100644 tests/test_apps.py delete mode 100644 tests/test_cli.py delete mode 100644 tests/test_commands.py delete mode 100644 tests/test_conf.py delete mode 100644 tests/test_fastrouter_lookup.py delete mode 100644 tests/test_middleware.py delete mode 100644 tests/test_models.py delete mode 100644 tests/testproject/__init__.py delete mode 100644 tests/testproject/settings.py delete mode 100644 tests/testproject/urls.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 84fb01a..6beab3d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,19 +3,12 @@ Changelog ========= -0.7.1 (2022-06-03) -================== +Next version (unreleased) +========================= -* Add testing support via ``tox`` + ``docker``/``tox-docker`` + ``pytest``. +* Add testing support via tox + docker/tox-docker + pytest. * Move to ``src``-based project layout. * Add initial compatibility with Django 2.2, 3.0, and 4.0. -* Add linting checks for ``isort`` and ``flake8``. -* Move to PEP-517/PEP-518 distribution format. -* Use ``setuptools-scm`` for versioning. -* Remove auto updating functionality on application startup. Users have to set - ``DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES`` and ``DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS`` - to ``False`` in ``settings.py``, and use the ``multisite_plus_populate_sites`` - and ``multisite_plus_rewrite_domains`` management commands instead. 0.6.3 (2018-10-18) diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..bcb996d --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,30 @@ +Metadata-Version: 2.1 +Name: django-multisite-plus +Version: 0.7.1 +Summary: An extension to django-multisite that eases local development. +Home-page: https://github.com/divio/django-multisite-plus +Author: Divio AG +Author-email: info@divio.com +License: BSD +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules +License-File: LICENSE + +UNKNOWN + diff --git a/aldryn_config.py b/aldryn_config.py index eef99b1..29a8f82 100644 --- a/aldryn_config.py +++ b/aldryn_config.py @@ -48,8 +48,7 @@ def to_settings(self, data, settings): # createcachetable) we ignore multisite. # TODO: find solutions upstream in django-multisite to prevent this # awkward CACHE_URL "if" situation. - from aldryn_addons.utils import boolean_ish - from aldryn_addons.utils import djsenv as env + from aldryn_addons.utils import djsenv as env, boolean_ish DJANGO_MODE = env("DJANGO_MODE") if DJANGO_MODE == "build" and settings["CACHE_URL"] == "locmem://": @@ -115,18 +114,14 @@ def single_process_settings(self, env, settings): # multisite.middleware.DynamicSiteMiddleware must be before # cms.middleware.utils.ApphookReloadMiddleware MIDDLEWARE_CLASSES.insert( - MIDDLEWARE_CLASSES.index( - "cms.middleware.utils.ApphookReloadMiddleware" - ), + MIDDLEWARE_CLASSES.index("cms.middleware.utils.ApphookReloadMiddleware"), "django_multisite_plus.middlewares.DynamicSiteMiddleware", ) # djangocms_multisite.middleware.CMSMultiSiteMiddleware must be after # cms.middleware.utils.ApphookReloadMiddleware MIDDLEWARE_CLASSES.insert( - MIDDLEWARE_CLASSES.index( - "cms.middleware.utils.ApphookReloadMiddleware" - ) + MIDDLEWARE_CLASSES.index("cms.middleware.utils.ApphookReloadMiddleware") + 1, "django_multisite_plus.middlewares.CMSMultiSiteMiddleware", ) diff --git a/pyproject.toml b/pyproject.toml index cfe5f55..4279f76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,3 @@ -[build-system] -requires = [ - "setuptools>=42", - "setuptools_scm[toml]>=6.2", - "wheel", -] -build-backend = "setuptools.build_meta" - [tool.coverage.run] branch = true source = ["src/django_multisite_plus", "./tests"] @@ -19,7 +11,7 @@ source = [ [tool.coverage.report] precision = 1 -skip_covered = true +#skip_covered = true [tool.coverage.html] directory = ".artifacts/htmlcov" @@ -29,22 +21,3 @@ skip_covered = false [tool.pytest.ini_options] pythonpath = "./tests" addopts = "-rxs --import-mode=importlib --cov-report= --cov=django_multisite_plus --cov=tests --cov-context=test" - -[tool.black] -line-length = 79 - -[tool.isort] -profile = "black" -line_length = 79 -lines_after_imports = 2 -lines_between_types = 0 -include_trailing_comma = true -atomic = true -order_by_type = true -use_parentheses = true -multi_line_output = 3 -overwrite_in_place = true -known_django = ["django"] -sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] - -[tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index dd0dbf1..8bfd5a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,54 +1,4 @@ -[metadata] -name = django-multisite-plus -author = Divio AG -author_email = info@divio.com -license = BSD 3-Clause License -description = An extension to django-multisite that eases local development. -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/divio/django-multisite-plus -project_urls = - Bug Tracker = https://github.com/divio/django-multisite-plus/issues -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Framework :: Django - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Topic :: Internet :: WWW/HTTP - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: Software Development :: Libraries :: Application Frameworks - Topic :: Software Development :: Libraries :: Python Modules +[egg_info] +tag_build = +tag_date = 0 -[options] -zip_safe = False -include_package_data = True -package_dir = - = src -packages = find: -python_requires = >=3.6 -setup_requires = setuptools_scm[toml] -install_requires = - django>=2 - django-multisite>=1.4.0 - djangocms-multisite>=0.2.2 # Note: this is divio/djangocms-multisite, not nephila/djangocms-multisite - aldryn-django - aldryn_addons - click - psycopg2>=2.5 - yurl - -[options.entry_points] -console_scripts = - django-multisite-plus = django_multisite_plus.cli:main - -[options.packages.find] -where = src diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b2b2aaa --- /dev/null +++ b/setup.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +from setuptools import find_packages, setup + +setup( + name="django-multisite-plus", + version="0.7.1", + author="Divio AG", + author_email="info@divio.com", + url="https://github.com/divio/django-multisite-plus", + license="BSD", + description="An extension to django-multisite that eases local development.", + packages=find_packages(where="src"), + package_dir={"": "src"}, + include_package_data=True, + zip_safe=False, + install_requires=[ + "django>=2", + "django-multisite>=1.4.0", + "djangocms-multisite>=0.2.2", # Note: this is divio/djangocms-multisite, not nephila/djangocms-multisite + "aldryn_addons", + "click", + "psycopg2>=2.5", + "yurl", + ], + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + entry_points=""" + [console_scripts] + django-multisite-plus=django_multisite_plus.cli:main + """, +) diff --git a/src/django_multisite_plus/apps.py b/src/django_multisite_plus/apps.py index 7ae9867..33abe3f 100644 --- a/src/django_multisite_plus/apps.py +++ b/src/django_multisite_plus/apps.py @@ -8,34 +8,29 @@ class AppConfig(django.apps.AppConfig): def ready(self): from django.conf import settings - from django.core.exceptions import ImproperlyConfigured + from django.db.utils import ProgrammingError, OperationalError - from django_multisite_plus import constants + Site = self.get_model("Site") + try: + if getattr(settings, "DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES", True): + print("Syncing django_multisite_plus Sites based on settings") + Site.objects.auto_populate_sites() - auto_populate_sites = getattr( - settings, "DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES", None - ) - if auto_populate_sites is True: - raise ImproperlyConfigured( - constants.AUTO_POPULATE_EXPLICITLY_ENABLED_ERROR_MESSAGE - ) - elif auto_populate_sites is None: - raise ImproperlyConfigured( - constants.AUTO_POPULATE_DEFAULT_ENABLED_ERROR_MESSAGE - ) + if getattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", True): + print("Rewriting django.contrib.sites.Site based on local settings") + Site.objects.update_sites() + # Make sure django-multisite is synced. + # Sometimes (e.g when loading fixtures) the signals don't fire and the + # Aliases get out of sync. + print("Syncing multisite.Alias based on Sites") + from multisite.models import Alias - auto_rewrite_domains = getattr( - settings, "DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS", None - ) - if auto_rewrite_domains is True: - raise ImproperlyConfigured( - constants.AUTO_REWRITE_DOMAINS_EXPLICITLY_ENABLED_ERROR_MESSAGE - ) - elif auto_rewrite_domains is None: - rewrite_domains = getattr( - settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", True - ) - if rewrite_domains: - raise ImproperlyConfigured( - constants.AUTO_REWRITE_DOMAINS_NOT_DISABLED_ERROR_MESSAGE - ) + Alias.canonical.sync_all() + except (ProgrammingError, OperationalError): + # ProgrammingError: + # database is not ready yet. Maybe we're in the migrate + # management command. + # OperationalError: + # Many parallel processes might have created deadlocks, e.g when + # starting up uwsgi with many workers. + pass diff --git a/src/django_multisite_plus/cli.py b/src/django_multisite_plus/cli.py index 6849f4b..34f9ef0 100644 --- a/src/django_multisite_plus/cli.py +++ b/src/django_multisite_plus/cli.py @@ -1,22 +1,60 @@ import os import shutil import subprocess +import sys + +from aldryn_addons.utils import boolean_ish +from aldryn_django.cli import main, execute, web as single_process_web import click import psycopg2 import yurl -from aldryn_addons.utils import boolean_ish -from aldryn_django.cli import execute, main -from aldryn_django.cli import web as single_process_web - -from django_multisite_plus import conf -from django_multisite_plus.constants import ( - ALIAS_DOMAIN_MAPPING_QUERY, - UWSGI_ALIAS_SEPARATOR, - VASSALS_SQL_QUERY, + +from . import conf +from .constants import UWSGI_ALIAS_SEPARATOR + +BASE_DIR = os.getcwd() +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, BASE_DIR) + +# Below we use the domain hash in order to avoid filename length errors (uwsgi allows only 102 chars) +VASSALS_SQL_QUERY = " ".join( + [ + x.strip() + for x in """ +SELECT + MD5(ds.domain)||'.ini' ini_filename, + CONCAT_WS(E'\\n', + '[uwsgi]', + 'env = SITE_ID='||ds.id, + 'socket = {base_sockets_dir}'||MD5(ds.domain)||'.sock', + ms.extra_uwsgi_ini + ) ini_content, + EXTRACT('epoch' from ms.last_updated_at) last_updated +FROM + django_multisite_plus_site AS ms + INNER JOIN django_site as ds ON (ds.id = ms.site_id) +WHERE + ms.is_enabled = TRUE +GROUP BY + ds.id, ms.extra_uwsgi_ini, ms.last_updated_at; +""".format( + base_sockets_dir=conf.UWSGI_BASE_SOCKETS_DIR + ).splitlines() + ] ) +ALIAS_DOMAIN_MAPPING_QUERY = """ +SELECT + alias.domain AS origin_domain, + site.domain AS destination_domain +FROM + multisite_alias AS alias + INNER JOIN django_site AS site ON (alias.site_id = site.id) +""" + + @main.command() @click.pass_obj @click.pass_context @@ -79,101 +117,78 @@ def get_uwsgi_vassal_opts(settings, port): ] ) - serve_static = False - - if ( - yurl.URL(settings["STATIC_URL"]).host - or not settings["STATIC_URL_IS_ON_OTHER_DOMAIN"] - ): - # TODO: we're currently starting the static url hosting on all - # vassals if STATIC_HOST is set. We rely on the fallback for the - # unknown static domain to end up calling on the main vassal. We - # should rather do this only on the main vassal or create a custom - # vassal just for static files. - serve_static = True - vassal_opts.extend( - get_uwsgi_static_serving_opts( - settings["STATIC_URL"], - settings["STATIC_ROOT"], - settings["STATIC_HEADERS"], + if not settings["ENABLE_SYNCING"]: + serve_static = False + + if ( + yurl.URL(settings["STATIC_URL"]).host + or not settings["STATIC_URL_IS_ON_OTHER_DOMAIN"] + ): + # TODO: we're currently starting the static url hosting on all vassals if STATIC_HOST is set. + # We rely on the fallback for the unknown static domain to end up calling on the main vassal. + # We should rather do this only on the main vassal or create a custom vassal just for static files. + serve_static = True + vassal_opts.extend( + get_uwsgi_static_serving_opts( + settings["STATIC_URL"], + settings["STATIC_ROOT"], + settings["STATIC_HEADERS"], + ) ) - ) - if not settings["MEDIA_URL_IS_ON_OTHER_DOMAIN"]: - serve_static = True - vassal_opts.extend( - get_uwsgi_static_serving_opts( - settings["MEDIA_URL"], - settings["MEDIA_ROOT"], - settings["MEDIA_HEADERS"], + if not settings["MEDIA_URL_IS_ON_OTHER_DOMAIN"]: + serve_static = True + vassal_opts.extend( + get_uwsgi_static_serving_opts( + settings["MEDIA_URL"], + settings["MEDIA_ROOT"], + settings["MEDIA_HEADERS"], + ) ) - ) - if serve_static: - vassal_opts.extend( - [ - # Start 2 offloading threads for each worker - "offload-threads = 2", - "static-cache-paths = 86400", - "static-cache-paths-name = staticpaths", - "cache2 = name=staticpaths,items=5000,blocksize=1k,purge_lru,ignore_full", - # Serve .gz files if that version is available - "static-gzip-all = true", - ] - ) + if serve_static: + vassal_opts.extend( + [ + "offload-threads = 2", # Start 2 offloading threads for each worker + "static-cache-paths = 86400", + "static-cache-paths-name = staticpaths", + "cache2 = name=staticpaths,items=5000,blocksize=1k,purge_lru,ignore_full", + "static-gzip-all = true", # Serve .gz files if that version is available + ] + ) - return [ - "vassal-set = {}".format(opt.replace(" = ", "=", 1)) - for opt in vassal_opts - ] + return ["vassal-set = {}".format(opt.replace(" = ", "=", 1)) for opt in vassal_opts] def get_uwsgi_emperor_opts(settings, port): if settings["DATABASES"]["default"].get("PASSWORD"): - os.environ["DB_PASSWORD"] = settings["DATABASES"]["default"][ - "PASSWORD" - ] + os.environ["DB_PASSWORD"] = settings["DATABASES"]["default"]["PASSWORD"] pg_str = "pg://host={HOST} port={PORT} user={USER} password=$(DB_PASSWORD) dbname={NAME};{query}" else: - pg_str = ( - "pg://host={HOST} port={PORT} user={USER} dbname={NAME};{query}" - ) + pg_str = "pg://host={HOST} port={PORT} user={USER} dbname={NAME};{query}" return get_uwsgi_regular_opts(settings, port) + [ "http = 0.0.0.0:{}".format(port or settings.get("PORT")), - # Fastrouter speaks the uwsgi protocol. - # We need to expose http via http-to. - "http-to = /tmp/fastrouter.sock", + "http-to = /tmp/fastrouter.sock", # Fastrouter speaks the uwsgi protocol. We need to expose http via http-to "fastrouter = /tmp/fastrouter.sock", "fastrouter-use-code-string = 0:{}:get".format( - os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "fastrouter_lookup.py", - ) + os.path.join(HERE, "fastrouter_lookup.py") ), "emperor = {}".format( - pg_str.format( - query=VASSALS_SQL_QUERY.format( - base_sockets_dir=conf.UWSGI_BASE_SOCKETS_DIR - ), - **settings["DATABASES"]["default"] - ) + pg_str.format(query=VASSALS_SQL_QUERY, **settings["DATABASES"]["default"]) ), ] def assert_uwsgi_plugin_is_installed(plugin): - # Simply calling "uwsgi --need-plugins=" sounds tempting but does - # not work. For some reason (maybe uwsgi inside virtualenv) the above - # wouldn't find the plugin even if it's installed. + # Simply call "uwsgi --need-plugins=" sounds tempting but does not work. + # For some reason (maybe uwsgi inside virtualenv) the above wouldn't find the plugin even if it's installed. plugins = subprocess.check_output( ["uwsgi --plugins-list; exit 0"], shell=True, stderr=subprocess.STDOUT ) plugins = plugins.decode("utf-8").split("\n") plugins = plugins[: plugins.index("--- end of plugins list ---")] - plugins = list( - filter(lambda x: bool(x) and not (x.startswith("***")), plugins) - ) + plugins = list(filter(lambda x: bool(x) and not (x.startswith("***")), plugins)) plugins = [p.split()[-1] for p in plugins] assert plugin in plugins @@ -185,8 +200,7 @@ def _create_working_dirs(): conf.UWSGI_BASE_SOCKETS_DIR, conf.UWSGI_ALIAS_DOMAIN_MAPPING_DIR, ): - # Ensure tree is clean before creating the working dirs (in order - # to avoid orphan vassal sockets). + # Ensure tree is clean before creating the working dirs (in order to avoid orphan vassal sockets) try: shutil.rmtree(path) except OSError: @@ -198,8 +212,7 @@ def _create_working_dirs(): pass def _create_uwsgi_config_file(settings, port, uwsgi_config_file_path): - # We can't use the regular ini writer because it does not allow - # repeating keys and does not keep order of keys. + # We can't use the regular ini writer because it does not allow repeating keys and does not keep order of keys ini = ["[uwsgi]"] ini.extend(get_uwsgi_emperor_opts(settings, port)) ini.extend(get_uwsgi_vassal_opts(settings, port)) @@ -208,13 +221,13 @@ def _create_uwsgi_config_file(settings, port, uwsgi_config_file_path): cfg_file.write("\n".join(ini)) def _create_alias_domain_mapping_helper_files(settings): - # We are spawning 1 uwsgi process per SITE, not per DOMAIN/ALIAS, so we - # need to map aliases to a proper domain. Here we create a bunch of - # helper files with filename like "alias|domain" so we can use it as a - # lookup later. + # We are spawning 1 uwsgi process per SITE, not per DOMAIN/ALIAS, so we need to map aliases to a proper domain. + # Here we create a bunch of helper files with filename like "alias|domain" so we can use it as a lookup later. db_kwargs = settings["DATABASES"]["default"] - connection_string = "dbname='{NAME}' user='{USER}' host='{HOST}' password='{PASSWORD}'".format( - **db_kwargs + connection_string = ( + "dbname='{NAME}' user='{USER}' host='{HOST}' password='{PASSWORD}'".format( + **db_kwargs + ) ) with psycopg2.connect(connection_string) as connection: @@ -225,14 +238,11 @@ def _create_alias_domain_mapping_helper_files(settings): alias_domain_mapping_dir = conf.UWSGI_ALIAS_DOMAIN_MAPPING_DIR for alias, domain in alias_domain_mapping.items(): filepath = os.path.join( - alias_domain_mapping_dir, - UWSGI_ALIAS_SEPARATOR.join([alias, domain]), + alias_domain_mapping_dir, UWSGI_ALIAS_SEPARATOR.join([alias, domain]) ) open(filepath, "w").close() - uwsgi_config_file_path = os.path.join( - conf.UWSGI_BASE_CONFIG_DIR, "config.ini" - ) + uwsgi_config_file_path = os.path.join(conf.UWSGI_BASE_CONFIG_DIR, "config.ini") # see 'required uwsgi plugins' in README for details about the uwsgi plugin assert_uwsgi_plugin_is_installed("emperor_pg") diff --git a/src/django_multisite_plus/cms_urls.py b/src/django_multisite_plus/cms_urls.py index d24ce43..963a0b4 100644 --- a/src/django_multisite_plus/cms_urls.py +++ b/src/django_multisite_plus/cms_urls.py @@ -1,8 +1,6 @@ from django.conf.urls import include - from djangocms_multisite.urlresolvers import cms_multisite_url - urlpatterns = [ cms_multisite_url(r"^", include("cms.urls")), ] diff --git a/src/django_multisite_plus/conf.py b/src/django_multisite_plus/conf.py index 57398a0..b755b57 100644 --- a/src/django_multisite_plus/conf.py +++ b/src/django_multisite_plus/conf.py @@ -2,23 +2,18 @@ from aldryn_addons.utils import boolean_ish - env = os.environ.get UWSGI_DEFAULT_DOMAIN = env("DJANGO_MULTISITE_PLUS_UWSGI_DEFAULT_DOMAIN") UWSGI_BASE_SOCKETS_DIR = env( - "DJANGO_MULTISITE_PLUS_UWSGI_BASE_SOCKETS_DIR", - "/app/uwsgi/tmp/vassal-sockets/", + "DJANGO_MULTISITE_PLUS_UWSGI_BASE_SOCKETS_DIR", "/app/uwsgi/tmp/vassal-sockets/" ) UWSGI_BASE_CONFIG_DIR = env( "DJANGO_MULTISITE_PLUS_UWSGI_BASE_CONFIG_DIR", "/app/uwsgi/" ) UWSGI_ALIAS_DOMAIN_MAPPING_DIR = env( - "DJANGO_MULTISITE_PLUS_UWSGI_ALIAS_DOMAIN_MAPPING_DIR", - "/app/uwsgi/tmp/aliases/", + "DJANGO_MULTISITE_PLUS_UWSGI_ALIAS_DOMAIN_MAPPING_DIR", "/app/uwsgi/tmp/aliases/" ) -UWSGI_LOCAL_TEST_MODE = boolean_ish( - env("DJANGO_MULTISITE_PLUS_UWSGI_LOCAL_TEST_MODE") -) +UWSGI_LOCAL_TEST_MODE = boolean_ish(env("DJANGO_MULTISITE_PLUS_UWSGI_LOCAL_TEST_MODE")) UWSGI_LOCAL_TEST_KEY = env("DJANGO_MULTISITE_PLUS_UWSGI_LOCAL_TEST_KEY") diff --git a/src/django_multisite_plus/constants.py b/src/django_multisite_plus/constants.py index cf1a25a..f2c7583 100644 --- a/src/django_multisite_plus/constants.py +++ b/src/django_multisite_plus/constants.py @@ -1,89 +1,3 @@ -from .utils import clean_query - - # WARNING: ',' is more intuitive but doesn't work! # uWSGI fails internally with ','. UWSGI_ALIAS_SEPARATOR = "|" - - -# Below we use the domain hash in order to avoid filename length errors (uwsgi -# allows only 102 chars) -VASSALS_SQL_QUERY = clean_query( - """ - SELECT - MD5(ds.domain)||'.ini' ini_filename, - CONCAT_WS(E'\\n', - '[uwsgi]', - 'env = SITE_ID='||ds.id, - 'socket = {base_sockets_dir}'||MD5(ds.domain)||'.sock', - ms.extra_uwsgi_ini - ) ini_content, - EXTRACT('epoch' from ms.last_updated_at) last_updated - FROM - django_multisite_plus_site AS ms - INNER JOIN django_site as ds ON (ds.id = ms.site_id) - WHERE - ms.is_enabled = TRUE - GROUP BY - ds.id, ms.extra_uwsgi_ini, ms.last_updated_at; - """ -) - - -ALIAS_DOMAIN_MAPPING_QUERY = clean_query( - """ - SELECT - alias.domain AS origin_domain, - site.domain AS destination_domain - FROM - multisite_alias AS alias - INNER JOIN django_site AS site ON (alias.site_id = site.id) - """ -) - - -AUTO_POPULATE_EXPLICITLY_ENABLED_ERROR_MESSAGE = """ -DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES cannot be True - -You explicitly set the above setting to True and a configuration change is -necessary to proceed. This feature has been removed in a recent version of the -package because it was leading to race conditions when updating domains in -`multi-process` mode. You can now achieve the same effect by executing the -`multisite_plus_populate_sites` management command during deployment. -""".strip() - - -AUTO_POPULATE_DEFAULT_ENABLED_ERROR_MESSAGE = """ -DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES defaults to True - -While the above setting has not been explicitly enabled, a previous version -of this package was automatically populating sites on startup and a -configuration change is necessary. This feature has been removed in a recent -version of the package because it was leading to race conditions when updating -domains in `multi-process` mode. You can now achieve the same effect by -executing the `multisite_plus_populate_sites` management command during -deployment. -""".strip() - - -AUTO_REWRITE_DOMAINS_EXPLICITLY_ENABLED_ERROR_MESSAGE = """ -DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS cannot be True - -You explicitly set the above setting to True and a configuration change is -necessary to proceed. This feature has been removed in a recent version of the -package because it was leading to race conditions when updating domains in -`multi-process` mode. You can now achieve the same effect by executing the -`multisite_plus_rewrite_domains` management command during deployment. -""".strip() - - -AUTO_REWRITE_DOMAINS_NOT_DISABLED_ERROR_MESSAGE = """ -DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS is not False - -While the above setting has not been explicitly enabled, it defaults to the -value of the DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS setting, which is currently -set to True. This feature has been removed in a recent version of the package -because it was leading to race conditions when updating domains in -`multi-process` mode. You can now achieve the same effect by executing the -`multisite_plus_rewrite_domains` management command during deployment. -""".strip() diff --git a/src/django_multisite_plus/fastrouter_lookup.py b/src/django_multisite_plus/fastrouter_lookup.py index 494b750..41bb974 100644 --- a/src/django_multisite_plus/fastrouter_lookup.py +++ b/src/django_multisite_plus/fastrouter_lookup.py @@ -6,7 +6,6 @@ from django_multisite_plus import conf from django_multisite_plus.constants import UWSGI_ALIAS_SEPARATOR - logger = logging.getLogger(__name__) @@ -25,16 +24,15 @@ def _get_socket(key): ) ) elif len(sockets) == 1: - matched_domain = os.path.basename(sockets[0]).split( - UWSGI_ALIAS_SEPARATOR - )[1] + matched_domain = os.path.basename(sockets[0]).split(UWSGI_ALIAS_SEPARATOR)[ + 1 + ] # Below we use the domain hash in order to avoid filename length errors (uwsgi allows only 102 chars) matched_domain_hash = hashlib.md5( matched_domain.encode("utf-8") ).hexdigest() socket_path = os.path.join( - conf.UWSGI_BASE_SOCKETS_DIR, - "{}.sock".format(matched_domain_hash), + conf.UWSGI_BASE_SOCKETS_DIR, "{}.sock".format(matched_domain_hash) ) return socket_path.encode("utf-8") else: @@ -51,9 +49,7 @@ def _get_socket(key): ) ) else: - options = glob.glob( - os.path.join(conf.UWSGI_BASE_SOCKETS_DIR, "*.sock") - ) + options = glob.glob(os.path.join(conf.UWSGI_BASE_SOCKETS_DIR, "*.sock")) fake_sockets = { "localhost:8000": options[0], "127.0.0.1:8000": options[1], diff --git a/src/django_multisite_plus/management/__init__.py b/src/django_multisite_plus/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/django_multisite_plus/management/commands/__init__.py b/src/django_multisite_plus/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py b/src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py deleted file mode 100644 index ecb94c2..0000000 --- a/src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError - -from django_multisite_plus.models import Site - - -class Command(BaseCommand): - help = "Syncs django_multisite_plus sites based on settings.py" - - def handle(self, *args, **options): - self.stdout.write( - "Syncing django_multisite_plus Sites based on settings" - ) - Site.objects.auto_populate_sites() - - self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py b/src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py deleted file mode 100644 index df7ec8d..0000000 --- a/src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError - -from multisite.models import Alias - -from django_multisite_plus.models import Site - - -class Command(BaseCommand): - help = "Rewrites django.contrib.sites.Site based on settings.py" - - def handle(self, *args, **options): - self.stdout.write( - "Rewriting django.contrib.sites.Site based on local settings" - ) - Site.objects.update_sites() - - self.stdout.write("Syncing multisite.Alias based on Sites") - Alias.canonical.sync_all() - - self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/src/django_multisite_plus/middlewares.py b/src/django_multisite_plus/middlewares.py index 9e3b9da..3baeed1 100644 --- a/src/django_multisite_plus/middlewares.py +++ b/src/django_multisite_plus/middlewares.py @@ -1,12 +1,14 @@ import sys + from imp import reload from django.conf import settings import multisite.middleware -from djangocms_multisite import middleware from multisite.models import Alias +from djangocms_multisite import middleware + class CMSMultiSiteMiddleware(middleware.CMSMultiSiteMiddleware): def process_request(self, request): diff --git a/src/django_multisite_plus/migrations/0001_initial.py b/src/django_multisite_plus/migrations/0001_initial.py index 0e44fdb..9625e8a 100644 --- a/src/django_multisite_plus/migrations/0001_initial.py +++ b/src/django_multisite_plus/migrations/0001_initial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import django.contrib.sites.models from django.db import migrations, models +import django.contrib.sites.models class Migration(migrations.Migration): diff --git a/src/django_multisite_plus/models.py b/src/django_multisite_plus/models.py index 2f5169b..da572ba 100644 --- a/src/django_multisite_plus/models.py +++ b/src/django_multisite_plus/models.py @@ -1,9 +1,11 @@ -import django.contrib.sites.models -from django.conf import settings -from django.db import models, transaction from django.utils.encoding import force_str +from django.db import models, transaction + +from django.conf import settings from django.utils.translation import gettext_lazy as _ +import django.contrib.sites.models + def domain_for_slug(slug, domain_format=None): domain_format = domain_format or getattr( @@ -42,10 +44,8 @@ def auto_populate_sites(self): if site_id: # A site_id was given. Use it as the primary identifier. try: - contrib_site = ( - django.contrib.sites.models.Site.objects.get( - id=site_id - ) + contrib_site = django.contrib.sites.models.Site.objects.get( + id=site_id ) contrib_site_updated_fields = set() for key, value in contrib_site_defaults.items(): @@ -53,9 +53,7 @@ def auto_populate_sites(self): setattr(contrib_site, key, value) contrib_site_updated_fields.add(key) if contrib_site_updated_fields: - contrib_site.save( - update_fields=contrib_site_updated_fields - ) + contrib_site.save(update_fields=contrib_site_updated_fields) # We're not using update_or_create here to make sure # last_updated_at does not get bumped if nothing changed. # Otherwise uwsgi would keep reloading all wsgi processes @@ -76,12 +74,10 @@ def auto_populate_sites(self): except django.contrib.sites.models.Site.DoesNotExist: domain = domain_for_slug(slug) - contrib_site = ( - django.contrib.sites.models.Site.objects.create( - id=site_id, - domain=domain, - name=name or domain, - ) + contrib_site = django.contrib.sites.models.Site.objects.create( + id=site_id, + domain=domain, + name=name or domain, ) site = Site.objects.create( site=contrib_site, @@ -115,11 +111,9 @@ def auto_populate_sites(self): site.save(update_fields=contrib_site_updated_fields) else: domain = domain_for_slug(slug) - contrib_site = ( - django.contrib.sites.models.Site.objects.create( - domain=domain, - name=name or domain, - ) + contrib_site = django.contrib.sites.models.Site.objects.create( + domain=domain, + name=name or domain, ) site = Site.objects.create( site=contrib_site, @@ -168,8 +162,7 @@ class Site(models.Model): unique=True, ) is_enabled = models.BooleanField( - default=True, - help_text=_("Whether this site should be served and available."), + default=True, help_text=_("Whether this site should be served and available.") ) last_updated_at = models.DateTimeField(auto_now=True) extra_uwsgi_ini = models.TextField(blank=True, default="") @@ -184,9 +177,7 @@ def get_url(self): @property def domain(self): - rewrite = getattr( - settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", False - ) + rewrite = getattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", False) if rewrite: return domain_for_slug(self.slug) elif not self.real_domain: diff --git a/src/django_multisite_plus/utils.py b/src/django_multisite_plus/utils.py deleted file mode 100644 index c818bf5..0000000 --- a/src/django_multisite_plus/utils.py +++ /dev/null @@ -1,2 +0,0 @@ -def clean_query(sql): - return " ".join([x.strip() for x in sql.strip().splitlines()]) diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index b6881e6..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.contrib.sites.models import Site - -import pytest - -import django_multisite_plus -from django_multisite_plus import apps - - -@pytest.fixture -def djangosite(db): - return Site.objects.get_current() - - -@pytest.fixture -def appconfig(): - return apps.AppConfig("django_multisite_plus", django_multisite_plus) diff --git a/tests/test_admin.py b/tests/test_admin.py deleted file mode 100644 index 70e9322..0000000 --- a/tests/test_admin.py +++ /dev/null @@ -1 +0,0 @@ -from django_multisite_plus import admin # NOQA diff --git a/tests/test_apps.py b/tests/test_apps.py deleted file mode 100644 index 57fba8d..0000000 --- a/tests/test_apps.py +++ /dev/null @@ -1,60 +0,0 @@ -from django.core.exceptions import ImproperlyConfigured - -import pytest - - -@pytest.mark.parametrize( - "auto_populate,auto_rewrite,rewrite,should_raise", - [ - (None, None, None, True), - (None, None, False, True), - (None, None, True, True), - (None, True, None, True), - (None, True, False, True), - (None, True, True, True), - (None, False, None, True), - (None, False, False, True), - (None, False, True, True), - (False, None, None, True), - (False, None, False, False), - (False, None, True, True), - (False, True, None, True), - (False, True, False, True), - (False, True, True, True), - (False, False, None, False), - (False, False, False, False), - (False, False, True, False), - (True, None, None, True), - (True, None, False, True), - (True, None, True, True), - (True, True, None, True), - (True, True, False, True), - (True, True, True, True), - (True, False, None, True), - (True, False, False, True), - (True, False, True, True), - ], -) -def test_auto_populate_error( - settings, appconfig, auto_populate, auto_rewrite, rewrite, should_raise -): - if auto_populate is not None: - settings.DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES = auto_populate - else: - delattr(settings, "DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES") - - if auto_rewrite is not None: - settings.DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS = auto_rewrite - else: - delattr(settings, "DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS") - - if rewrite is not None: - settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS = rewrite - else: - delattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS") - - if should_raise: - with pytest.raises(ImproperlyConfigured): - appconfig.ready() - else: - appconfig.ready() diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index 73b6394..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,34 +0,0 @@ -from django_multisite_plus import cli - - -def test_get_uwsgi_regular_opts(): - settings = { - "DATABASES": { - "default": { - "HOST": "db-host", - "PORT": 9999, - "USER": "db-user", - "PASSWORD": "db-pwd", - "NAME": "db-name", - } - }, - "DJANGO_WEB_WORKERS": 2, - "DJANGO_WEB_MAX_REQUESTS": 17, - "DJANGO_WEB_TIMEOUT": 31, - } - - opts = cli.get_uwsgi_regular_opts(settings, 1000) - - assert opts == [ - "module = wsgi", - "master = true", - "workers = 2", - "max-requests = 17", - "harakiri = 31", - "lazy-apps = true", - "honour-range = true", - "enable-threads = true", - "ignore-sigpipe = true", - "ignore-write-errors = true", - "disable-write-exception = true", - ] diff --git a/tests/test_commands.py b/tests/test_commands.py deleted file mode 100644 index 5778f3b..0000000 --- a/tests/test_commands.py +++ /dev/null @@ -1,29 +0,0 @@ -from io import StringIO -from unittest.mock import patch - -from django.core.management import call_command - -from multisite.models import CanonicalAliasManager - -from django_multisite_plus.models import SiteManager - - -def test_populate_sites(db): - out = StringIO() - with patch.object(SiteManager, "auto_populate_sites") as method: - call_command("multisite_plus_populate_sites", stdout=out) - method.assert_called_once_with() - assert "Syncing django_multisite_plus" in out.getvalue() - assert "Done." in out.getvalue() - - -def test_rewrite_domains(db): - out = StringIO() - with patch.object(SiteManager, "update_sites") as update: - with patch.object(CanonicalAliasManager, "sync_all") as sync: - call_command("multisite_plus_rewrite_domains", stdout=out) - update.assert_called_once_with() - sync.assert_called_once_with() - assert "Rewriting django.contrib.sites.Site" in out.getvalue() - assert "Syncing multisite.Alias" in out.getvalue() - assert "Done." in out.getvalue() diff --git a/tests/test_conf.py b/tests/test_conf.py deleted file mode 100644 index b05d5d9..0000000 --- a/tests/test_conf.py +++ /dev/null @@ -1 +0,0 @@ -from django_multisite_plus import conf # NOQA diff --git a/tests/test_fastrouter_lookup.py b/tests/test_fastrouter_lookup.py deleted file mode 100644 index 7b97f86..0000000 --- a/tests/test_fastrouter_lookup.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest - -from django_multisite_plus import fastrouter_lookup - - -def test_get_nonexisting(): - with pytest.raises(RuntimeError): - fastrouter_lookup.get(b"test.com") diff --git a/tests/test_middleware.py b/tests/test_middleware.py deleted file mode 100644 index 4db0358..0000000 --- a/tests/test_middleware.py +++ /dev/null @@ -1 +0,0 @@ -from django_multisite_plus import middlewares # NOQA diff --git a/tests/test_models.py b/tests/test_models.py deleted file mode 100644 index 29e1aa3..0000000 --- a/tests/test_models.py +++ /dev/null @@ -1,79 +0,0 @@ -import pytest - -from django_multisite_plus import models - - -@pytest.mark.parametrize( - "format,slug,expected", - [ - ("{}.mydomain.com", "myslug", "myslug.mydomain.com"), - ("subdomain.{}.com", "otherslug", "subdomain.otherslug.com"), - ], -) -def test_domain_for_slug(settings, format, slug, expected): - assert models.domain_for_slug(slug, format) == expected - settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAIN_FORMAT = format - assert models.domain_for_slug(slug) == expected - - -@pytest.mark.parametrize( - "domain,rewrite,expected", - [ - ("test.com", None, "test.com"), - ("test.com", False, "test.com"), - ("test.com", True, "slug.rewritten.com"), - ("", None, "slug.rewritten.com"), - ("", False, "slug.rewritten.com"), - ("", True, "slug.rewritten.com"), - ], -) -def test_site_domain(settings, djangosite, domain, rewrite, expected): - settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAIN_FORMAT = "{}.rewritten.com" - if rewrite is None: - delattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS") - else: - settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS = rewrite - - site = models.Site(site=djangosite, real_domain=domain, slug="slug") - assert site.domain == expected - - -def test_site_update_site(djangosite): - site = models.Site(site=djangosite, real_domain="test.com", slug="test") - assert djangosite.domain == "example.com" - - site.update_site() - assert djangosite.domain == "test.com" - - # A second run shall have no effect - site.update_site() - assert djangosite.domain == "test.com" - - -def test_auto_populate_sites(db, settings): - assert not models.Site.objects.exists() - settings.DJANGO_MULTISITE_PLUS_SITES = { - "test-site-1": { - "id": 1, - "real_domain": "real-domain-for-site-1.com", - "name": "Test Site 1", - }, - "test-site-2": { - "id": 2, - "real_domain": "real-domain-for-site-2.com", - }, - } - - models.Site.objects.auto_populate_sites() - - assert models.Site.objects.count() == 2 - - site1 = models.Site.objects.get(pk=1) - assert site1.slug == "test-site-1" - assert site1.real_domain == "real-domain-for-site-1.com" - assert site1.site.name == "Test Site 1" - - site2 = models.Site.objects.get(pk=2) - assert site2.slug == "test-site-2" - assert site2.real_domain == "real-domain-for-site-2.com" - assert site2.site.name == "" diff --git a/tests/testproject/__init__.py b/tests/testproject/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/testproject/settings.py b/tests/testproject/settings.py deleted file mode 100644 index 6bac7e9..0000000 --- a/tests/testproject/settings.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import secrets - - -SECRET_KEY = secrets.token_urlsafe(48) - -INSTALLED_APPS = [ - "django.contrib.auth", - "django.contrib.admin", - "django.contrib.contenttypes", - "django.contrib.sites", - "multisite", - "django_multisite_plus", -] - -SITE_ID = 1 - -ROOT_URLCONF = "testproject.urls" - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", - "NAME": "postgres", - "USER": "postgres", - "HOST": os.environ["POSTGRES_HOST"], - "PORT": os.environ["POSTGRES_5432_TCP_PORT"], - } -} - -DJANGO_MULTISITE_PLUS_MODE = "single-process" - -DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES = False -DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS = False diff --git a/tests/testproject/urls.py b/tests/testproject/urls.py deleted file mode 100644 index 637600f..0000000 --- a/tests/testproject/urls.py +++ /dev/null @@ -1 +0,0 @@ -urlpatterns = [] diff --git a/tox.ini b/tox.ini index 931b8b4..26ee7d4 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ envlist = lint, manifest, coverage -isolated_build = true [testenv] depends = test: clean @@ -19,13 +18,13 @@ deps = test: pytest test: pytest-cov test: pytest-django - django22: django>=2.2,<2.3 - django32: django>=3.2,<3.3 - django40: django<=4.0,<4.1 -# django41: django>=4.1,<4.2 -# django42: django>=4.2,<4.3 + django22: django==2.2.0 + django32: django==3.2.0 + django40: django==4.0.0 +# django41: django==4.1.0 +# django42: django==4.2.0 commands = - test: pytest ./tests {posargs} + test: pytest ./tests setenv = COVERAGE_FILE = .artifacts/coverage.{env:TOX_ENV_NAME} DJANGO_SETTINGS_MODULE = testproject.settings @@ -36,14 +35,8 @@ commands = check-manifest skip_install = true [testenv:lint] -deps = - black - isort - flake8 -commands = - isort --check-only . - black --check src/django_multisite_plus - flake8 --ignore=E501,E722,E402,W503,E203 . +deps = black +commands = black --check src/django_multisite_plus skip_install = true [testenv:clean] From 324bad643af9d4fc0841b98a4cbb91c997eb09a9 Mon Sep 17 00:00:00 2001 From: Angelo Dini Date: Thu, 7 Mar 2024 14:49:11 +0100 Subject: [PATCH 2/3] add tests --- tests/conftest.py | 16 +++++++ tests/test_admin.py | 1 + tests/test_apps.py | 60 +++++++++++++++++++++++++ tests/test_cli.py | 34 ++++++++++++++ tests/test_commands.py | 29 ++++++++++++ tests/test_conf.py | 1 + tests/test_fastrouter_lookup.py | 8 ++++ tests/test_middleware.py | 1 + tests/test_models.py | 79 +++++++++++++++++++++++++++++++++ tests/testproject/__init__.py | 0 tests/testproject/settings.py | 33 ++++++++++++++ tests/testproject/urls.py | 1 + 12 files changed, 263 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_admin.py create mode 100644 tests/test_apps.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_commands.py create mode 100644 tests/test_conf.py create mode 100644 tests/test_fastrouter_lookup.py create mode 100644 tests/test_middleware.py create mode 100644 tests/test_models.py create mode 100644 tests/testproject/__init__.py create mode 100644 tests/testproject/settings.py create mode 100644 tests/testproject/urls.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b6881e6 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,16 @@ +from django.contrib.sites.models import Site + +import pytest + +import django_multisite_plus +from django_multisite_plus import apps + + +@pytest.fixture +def djangosite(db): + return Site.objects.get_current() + + +@pytest.fixture +def appconfig(): + return apps.AppConfig("django_multisite_plus", django_multisite_plus) diff --git a/tests/test_admin.py b/tests/test_admin.py new file mode 100644 index 0000000..70e9322 --- /dev/null +++ b/tests/test_admin.py @@ -0,0 +1 @@ +from django_multisite_plus import admin # NOQA diff --git a/tests/test_apps.py b/tests/test_apps.py new file mode 100644 index 0000000..57fba8d --- /dev/null +++ b/tests/test_apps.py @@ -0,0 +1,60 @@ +from django.core.exceptions import ImproperlyConfigured + +import pytest + + +@pytest.mark.parametrize( + "auto_populate,auto_rewrite,rewrite,should_raise", + [ + (None, None, None, True), + (None, None, False, True), + (None, None, True, True), + (None, True, None, True), + (None, True, False, True), + (None, True, True, True), + (None, False, None, True), + (None, False, False, True), + (None, False, True, True), + (False, None, None, True), + (False, None, False, False), + (False, None, True, True), + (False, True, None, True), + (False, True, False, True), + (False, True, True, True), + (False, False, None, False), + (False, False, False, False), + (False, False, True, False), + (True, None, None, True), + (True, None, False, True), + (True, None, True, True), + (True, True, None, True), + (True, True, False, True), + (True, True, True, True), + (True, False, None, True), + (True, False, False, True), + (True, False, True, True), + ], +) +def test_auto_populate_error( + settings, appconfig, auto_populate, auto_rewrite, rewrite, should_raise +): + if auto_populate is not None: + settings.DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES = auto_populate + else: + delattr(settings, "DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES") + + if auto_rewrite is not None: + settings.DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS = auto_rewrite + else: + delattr(settings, "DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS") + + if rewrite is not None: + settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS = rewrite + else: + delattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS") + + if should_raise: + with pytest.raises(ImproperlyConfigured): + appconfig.ready() + else: + appconfig.ready() diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..73b6394 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,34 @@ +from django_multisite_plus import cli + + +def test_get_uwsgi_regular_opts(): + settings = { + "DATABASES": { + "default": { + "HOST": "db-host", + "PORT": 9999, + "USER": "db-user", + "PASSWORD": "db-pwd", + "NAME": "db-name", + } + }, + "DJANGO_WEB_WORKERS": 2, + "DJANGO_WEB_MAX_REQUESTS": 17, + "DJANGO_WEB_TIMEOUT": 31, + } + + opts = cli.get_uwsgi_regular_opts(settings, 1000) + + assert opts == [ + "module = wsgi", + "master = true", + "workers = 2", + "max-requests = 17", + "harakiri = 31", + "lazy-apps = true", + "honour-range = true", + "enable-threads = true", + "ignore-sigpipe = true", + "ignore-write-errors = true", + "disable-write-exception = true", + ] diff --git a/tests/test_commands.py b/tests/test_commands.py new file mode 100644 index 0000000..5778f3b --- /dev/null +++ b/tests/test_commands.py @@ -0,0 +1,29 @@ +from io import StringIO +from unittest.mock import patch + +from django.core.management import call_command + +from multisite.models import CanonicalAliasManager + +from django_multisite_plus.models import SiteManager + + +def test_populate_sites(db): + out = StringIO() + with patch.object(SiteManager, "auto_populate_sites") as method: + call_command("multisite_plus_populate_sites", stdout=out) + method.assert_called_once_with() + assert "Syncing django_multisite_plus" in out.getvalue() + assert "Done." in out.getvalue() + + +def test_rewrite_domains(db): + out = StringIO() + with patch.object(SiteManager, "update_sites") as update: + with patch.object(CanonicalAliasManager, "sync_all") as sync: + call_command("multisite_plus_rewrite_domains", stdout=out) + update.assert_called_once_with() + sync.assert_called_once_with() + assert "Rewriting django.contrib.sites.Site" in out.getvalue() + assert "Syncing multisite.Alias" in out.getvalue() + assert "Done." in out.getvalue() diff --git a/tests/test_conf.py b/tests/test_conf.py new file mode 100644 index 0000000..b05d5d9 --- /dev/null +++ b/tests/test_conf.py @@ -0,0 +1 @@ +from django_multisite_plus import conf # NOQA diff --git a/tests/test_fastrouter_lookup.py b/tests/test_fastrouter_lookup.py new file mode 100644 index 0000000..7b97f86 --- /dev/null +++ b/tests/test_fastrouter_lookup.py @@ -0,0 +1,8 @@ +import pytest + +from django_multisite_plus import fastrouter_lookup + + +def test_get_nonexisting(): + with pytest.raises(RuntimeError): + fastrouter_lookup.get(b"test.com") diff --git a/tests/test_middleware.py b/tests/test_middleware.py new file mode 100644 index 0000000..4db0358 --- /dev/null +++ b/tests/test_middleware.py @@ -0,0 +1 @@ +from django_multisite_plus import middlewares # NOQA diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..29e1aa3 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,79 @@ +import pytest + +from django_multisite_plus import models + + +@pytest.mark.parametrize( + "format,slug,expected", + [ + ("{}.mydomain.com", "myslug", "myslug.mydomain.com"), + ("subdomain.{}.com", "otherslug", "subdomain.otherslug.com"), + ], +) +def test_domain_for_slug(settings, format, slug, expected): + assert models.domain_for_slug(slug, format) == expected + settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAIN_FORMAT = format + assert models.domain_for_slug(slug) == expected + + +@pytest.mark.parametrize( + "domain,rewrite,expected", + [ + ("test.com", None, "test.com"), + ("test.com", False, "test.com"), + ("test.com", True, "slug.rewritten.com"), + ("", None, "slug.rewritten.com"), + ("", False, "slug.rewritten.com"), + ("", True, "slug.rewritten.com"), + ], +) +def test_site_domain(settings, djangosite, domain, rewrite, expected): + settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAIN_FORMAT = "{}.rewritten.com" + if rewrite is None: + delattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS") + else: + settings.DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS = rewrite + + site = models.Site(site=djangosite, real_domain=domain, slug="slug") + assert site.domain == expected + + +def test_site_update_site(djangosite): + site = models.Site(site=djangosite, real_domain="test.com", slug="test") + assert djangosite.domain == "example.com" + + site.update_site() + assert djangosite.domain == "test.com" + + # A second run shall have no effect + site.update_site() + assert djangosite.domain == "test.com" + + +def test_auto_populate_sites(db, settings): + assert not models.Site.objects.exists() + settings.DJANGO_MULTISITE_PLUS_SITES = { + "test-site-1": { + "id": 1, + "real_domain": "real-domain-for-site-1.com", + "name": "Test Site 1", + }, + "test-site-2": { + "id": 2, + "real_domain": "real-domain-for-site-2.com", + }, + } + + models.Site.objects.auto_populate_sites() + + assert models.Site.objects.count() == 2 + + site1 = models.Site.objects.get(pk=1) + assert site1.slug == "test-site-1" + assert site1.real_domain == "real-domain-for-site-1.com" + assert site1.site.name == "Test Site 1" + + site2 = models.Site.objects.get(pk=2) + assert site2.slug == "test-site-2" + assert site2.real_domain == "real-domain-for-site-2.com" + assert site2.site.name == "" diff --git a/tests/testproject/__init__.py b/tests/testproject/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/testproject/settings.py b/tests/testproject/settings.py new file mode 100644 index 0000000..6bac7e9 --- /dev/null +++ b/tests/testproject/settings.py @@ -0,0 +1,33 @@ +import os +import secrets + + +SECRET_KEY = secrets.token_urlsafe(48) + +INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.admin", + "django.contrib.contenttypes", + "django.contrib.sites", + "multisite", + "django_multisite_plus", +] + +SITE_ID = 1 + +ROOT_URLCONF = "testproject.urls" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "postgres", + "USER": "postgres", + "HOST": os.environ["POSTGRES_HOST"], + "PORT": os.environ["POSTGRES_5432_TCP_PORT"], + } +} + +DJANGO_MULTISITE_PLUS_MODE = "single-process" + +DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES = False +DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS = False diff --git a/tests/testproject/urls.py b/tests/testproject/urls.py new file mode 100644 index 0000000..637600f --- /dev/null +++ b/tests/testproject/urls.py @@ -0,0 +1 @@ +urlpatterns = [] From 8a9a83e0fe202ad413728c159fb1126e19f0a832 Mon Sep 17 00:00:00 2001 From: Angelo Dini Date: Thu, 7 Mar 2024 14:50:08 +0100 Subject: [PATCH 3/3] restore files --- CHANGELOG.rst | 16 +- PKG-INFO | 30 --- aldryn_config.py | 11 +- pyproject.toml | 29 ++- setup.cfg | 56 ++++- setup.py | 30 +-- src/django_multisite_plus/apps.py | 51 ++--- src/django_multisite_plus/cli.py | 192 +++++++++--------- src/django_multisite_plus/cms_urls.py | 2 + src/django_multisite_plus/conf.py | 11 +- src/django_multisite_plus/constants.py | 86 ++++++++ .../fastrouter_lookup.py | 14 +- .../management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/multisite_plus_populate_sites.py | 15 ++ .../multisite_plus_rewrite_domains.py | 20 ++ src/django_multisite_plus/middlewares.py | 4 +- .../migrations/0001_initial.py | 2 +- src/django_multisite_plus/models.py | 43 ++-- src/django_multisite_plus/utils.py | 2 + tox.ini | 23 ++- 21 files changed, 408 insertions(+), 229 deletions(-) delete mode 100644 PKG-INFO create mode 100644 src/django_multisite_plus/management/__init__.py create mode 100644 src/django_multisite_plus/management/commands/__init__.py create mode 100644 src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py create mode 100644 src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py create mode 100644 src/django_multisite_plus/utils.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6beab3d..f745dd5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,8 +3,20 @@ Changelog ========= -Next version (unreleased) -========================= +0.7.2 (unreleased) +================== + +* Add linting checks for ``isort`` and ``flake8``. +* Move to PEP-517/PEP-518 distribution format. +* Use ``setuptools-scm`` for versioning. +* Remove auto updating functionality on application startup. Users have to set + ``DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES`` and ``DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS`` + to ``False`` in ``settings.py``, and use the ``multisite_plus_populate_sites`` + and ``multisite_plus_rewrite_domains`` management commands instead. + + +0.7.1 (2022-06-03) +================== * Add testing support via tox + docker/tox-docker + pytest. * Move to ``src``-based project layout. diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index bcb996d..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,30 +0,0 @@ -Metadata-Version: 2.1 -Name: django-multisite-plus -Version: 0.7.1 -Summary: An extension to django-multisite that eases local development. -Home-page: https://github.com/divio/django-multisite-plus -Author: Divio AG -Author-email: info@divio.com -License: BSD -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Framework :: Django -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Classifier: Topic :: Software Development :: Libraries :: Python Modules -License-File: LICENSE - -UNKNOWN - diff --git a/aldryn_config.py b/aldryn_config.py index 29a8f82..eef99b1 100644 --- a/aldryn_config.py +++ b/aldryn_config.py @@ -48,7 +48,8 @@ def to_settings(self, data, settings): # createcachetable) we ignore multisite. # TODO: find solutions upstream in django-multisite to prevent this # awkward CACHE_URL "if" situation. - from aldryn_addons.utils import djsenv as env, boolean_ish + from aldryn_addons.utils import boolean_ish + from aldryn_addons.utils import djsenv as env DJANGO_MODE = env("DJANGO_MODE") if DJANGO_MODE == "build" and settings["CACHE_URL"] == "locmem://": @@ -114,14 +115,18 @@ def single_process_settings(self, env, settings): # multisite.middleware.DynamicSiteMiddleware must be before # cms.middleware.utils.ApphookReloadMiddleware MIDDLEWARE_CLASSES.insert( - MIDDLEWARE_CLASSES.index("cms.middleware.utils.ApphookReloadMiddleware"), + MIDDLEWARE_CLASSES.index( + "cms.middleware.utils.ApphookReloadMiddleware" + ), "django_multisite_plus.middlewares.DynamicSiteMiddleware", ) # djangocms_multisite.middleware.CMSMultiSiteMiddleware must be after # cms.middleware.utils.ApphookReloadMiddleware MIDDLEWARE_CLASSES.insert( - MIDDLEWARE_CLASSES.index("cms.middleware.utils.ApphookReloadMiddleware") + MIDDLEWARE_CLASSES.index( + "cms.middleware.utils.ApphookReloadMiddleware" + ) + 1, "django_multisite_plus.middlewares.CMSMultiSiteMiddleware", ) diff --git a/pyproject.toml b/pyproject.toml index 4279f76..cfe5f55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,11 @@ +[build-system] +requires = [ + "setuptools>=42", + "setuptools_scm[toml]>=6.2", + "wheel", +] +build-backend = "setuptools.build_meta" + [tool.coverage.run] branch = true source = ["src/django_multisite_plus", "./tests"] @@ -11,7 +19,7 @@ source = [ [tool.coverage.report] precision = 1 -#skip_covered = true +skip_covered = true [tool.coverage.html] directory = ".artifacts/htmlcov" @@ -21,3 +29,22 @@ skip_covered = false [tool.pytest.ini_options] pythonpath = "./tests" addopts = "-rxs --import-mode=importlib --cov-report= --cov=django_multisite_plus --cov=tests --cov-context=test" + +[tool.black] +line-length = 79 + +[tool.isort] +profile = "black" +line_length = 79 +lines_after_imports = 2 +lines_between_types = 0 +include_trailing_comma = true +atomic = true +order_by_type = true +use_parentheses = true +multi_line_output = 3 +overwrite_in_place = true +known_django = ["django"] +sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] + +[tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index 8bfd5a1..dd0dbf1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,54 @@ -[egg_info] -tag_build = -tag_date = 0 +[metadata] +name = django-multisite-plus +author = Divio AG +author_email = info@divio.com +license = BSD 3-Clause License +description = An extension to django-multisite that eases local development. +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/divio/django-multisite-plus +project_urls = + Bug Tracker = https://github.com/divio/django-multisite-plus/issues +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Web Environment + Framework :: Django + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Topic :: Internet :: WWW/HTTP + Topic :: Internet :: WWW/HTTP :: Dynamic Content + Topic :: Software Development :: Libraries :: Application Frameworks + Topic :: Software Development :: Libraries :: Python Modules +[options] +zip_safe = False +include_package_data = True +package_dir = + = src +packages = find: +python_requires = >=3.6 +setup_requires = setuptools_scm[toml] +install_requires = + django>=2 + django-multisite>=1.4.0 + djangocms-multisite>=0.2.2 # Note: this is divio/djangocms-multisite, not nephila/djangocms-multisite + aldryn-django + aldryn_addons + click + psycopg2>=2.5 + yurl + +[options.entry_points] +console_scripts = + django-multisite-plus = django_multisite_plus.cli:main + +[options.packages.find] +where = src diff --git a/setup.py b/setup.py index b2b2aaa..d271144 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="django-multisite-plus", - version="0.7.1", + version="0.7.2", author="Divio AG", author_email="info@divio.com", url="https://github.com/divio/django-multisite-plus", @@ -14,34 +14,6 @@ package_dir={"": "src"}, include_package_data=True, zip_safe=False, - install_requires=[ - "django>=2", - "django-multisite>=1.4.0", - "djangocms-multisite>=0.2.2", # Note: this is divio/djangocms-multisite, not nephila/djangocms-multisite - "aldryn_addons", - "click", - "psycopg2>=2.5", - "yurl", - ], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Web Environment", - "Framework :: Django", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content", - "Topic :: Software Development :: Libraries :: Application Frameworks", - "Topic :: Software Development :: Libraries :: Python Modules", - ], entry_points=""" [console_scripts] django-multisite-plus=django_multisite_plus.cli:main diff --git a/src/django_multisite_plus/apps.py b/src/django_multisite_plus/apps.py index 33abe3f..7ae9867 100644 --- a/src/django_multisite_plus/apps.py +++ b/src/django_multisite_plus/apps.py @@ -8,29 +8,34 @@ class AppConfig(django.apps.AppConfig): def ready(self): from django.conf import settings - from django.db.utils import ProgrammingError, OperationalError + from django.core.exceptions import ImproperlyConfigured - Site = self.get_model("Site") - try: - if getattr(settings, "DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES", True): - print("Syncing django_multisite_plus Sites based on settings") - Site.objects.auto_populate_sites() + from django_multisite_plus import constants - if getattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", True): - print("Rewriting django.contrib.sites.Site based on local settings") - Site.objects.update_sites() - # Make sure django-multisite is synced. - # Sometimes (e.g when loading fixtures) the signals don't fire and the - # Aliases get out of sync. - print("Syncing multisite.Alias based on Sites") - from multisite.models import Alias + auto_populate_sites = getattr( + settings, "DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES", None + ) + if auto_populate_sites is True: + raise ImproperlyConfigured( + constants.AUTO_POPULATE_EXPLICITLY_ENABLED_ERROR_MESSAGE + ) + elif auto_populate_sites is None: + raise ImproperlyConfigured( + constants.AUTO_POPULATE_DEFAULT_ENABLED_ERROR_MESSAGE + ) - Alias.canonical.sync_all() - except (ProgrammingError, OperationalError): - # ProgrammingError: - # database is not ready yet. Maybe we're in the migrate - # management command. - # OperationalError: - # Many parallel processes might have created deadlocks, e.g when - # starting up uwsgi with many workers. - pass + auto_rewrite_domains = getattr( + settings, "DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS", None + ) + if auto_rewrite_domains is True: + raise ImproperlyConfigured( + constants.AUTO_REWRITE_DOMAINS_EXPLICITLY_ENABLED_ERROR_MESSAGE + ) + elif auto_rewrite_domains is None: + rewrite_domains = getattr( + settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", True + ) + if rewrite_domains: + raise ImproperlyConfigured( + constants.AUTO_REWRITE_DOMAINS_NOT_DISABLED_ERROR_MESSAGE + ) diff --git a/src/django_multisite_plus/cli.py b/src/django_multisite_plus/cli.py index 34f9ef0..6849f4b 100644 --- a/src/django_multisite_plus/cli.py +++ b/src/django_multisite_plus/cli.py @@ -1,60 +1,22 @@ import os import shutil import subprocess -import sys - -from aldryn_addons.utils import boolean_ish -from aldryn_django.cli import main, execute, web as single_process_web import click import psycopg2 import yurl - -from . import conf -from .constants import UWSGI_ALIAS_SEPARATOR - -BASE_DIR = os.getcwd() -HERE = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, BASE_DIR) - -# Below we use the domain hash in order to avoid filename length errors (uwsgi allows only 102 chars) -VASSALS_SQL_QUERY = " ".join( - [ - x.strip() - for x in """ -SELECT - MD5(ds.domain)||'.ini' ini_filename, - CONCAT_WS(E'\\n', - '[uwsgi]', - 'env = SITE_ID='||ds.id, - 'socket = {base_sockets_dir}'||MD5(ds.domain)||'.sock', - ms.extra_uwsgi_ini - ) ini_content, - EXTRACT('epoch' from ms.last_updated_at) last_updated -FROM - django_multisite_plus_site AS ms - INNER JOIN django_site as ds ON (ds.id = ms.site_id) -WHERE - ms.is_enabled = TRUE -GROUP BY - ds.id, ms.extra_uwsgi_ini, ms.last_updated_at; -""".format( - base_sockets_dir=conf.UWSGI_BASE_SOCKETS_DIR - ).splitlines() - ] +from aldryn_addons.utils import boolean_ish +from aldryn_django.cli import execute, main +from aldryn_django.cli import web as single_process_web + +from django_multisite_plus import conf +from django_multisite_plus.constants import ( + ALIAS_DOMAIN_MAPPING_QUERY, + UWSGI_ALIAS_SEPARATOR, + VASSALS_SQL_QUERY, ) -ALIAS_DOMAIN_MAPPING_QUERY = """ -SELECT - alias.domain AS origin_domain, - site.domain AS destination_domain -FROM - multisite_alias AS alias - INNER JOIN django_site AS site ON (alias.site_id = site.id) -""" - - @main.command() @click.pass_obj @click.pass_context @@ -117,78 +79,101 @@ def get_uwsgi_vassal_opts(settings, port): ] ) - if not settings["ENABLE_SYNCING"]: - serve_static = False - - if ( - yurl.URL(settings["STATIC_URL"]).host - or not settings["STATIC_URL_IS_ON_OTHER_DOMAIN"] - ): - # TODO: we're currently starting the static url hosting on all vassals if STATIC_HOST is set. - # We rely on the fallback for the unknown static domain to end up calling on the main vassal. - # We should rather do this only on the main vassal or create a custom vassal just for static files. - serve_static = True - vassal_opts.extend( - get_uwsgi_static_serving_opts( - settings["STATIC_URL"], - settings["STATIC_ROOT"], - settings["STATIC_HEADERS"], - ) + serve_static = False + + if ( + yurl.URL(settings["STATIC_URL"]).host + or not settings["STATIC_URL_IS_ON_OTHER_DOMAIN"] + ): + # TODO: we're currently starting the static url hosting on all + # vassals if STATIC_HOST is set. We rely on the fallback for the + # unknown static domain to end up calling on the main vassal. We + # should rather do this only on the main vassal or create a custom + # vassal just for static files. + serve_static = True + vassal_opts.extend( + get_uwsgi_static_serving_opts( + settings["STATIC_URL"], + settings["STATIC_ROOT"], + settings["STATIC_HEADERS"], ) + ) - if not settings["MEDIA_URL_IS_ON_OTHER_DOMAIN"]: - serve_static = True - vassal_opts.extend( - get_uwsgi_static_serving_opts( - settings["MEDIA_URL"], - settings["MEDIA_ROOT"], - settings["MEDIA_HEADERS"], - ) + if not settings["MEDIA_URL_IS_ON_OTHER_DOMAIN"]: + serve_static = True + vassal_opts.extend( + get_uwsgi_static_serving_opts( + settings["MEDIA_URL"], + settings["MEDIA_ROOT"], + settings["MEDIA_HEADERS"], ) + ) - if serve_static: - vassal_opts.extend( - [ - "offload-threads = 2", # Start 2 offloading threads for each worker - "static-cache-paths = 86400", - "static-cache-paths-name = staticpaths", - "cache2 = name=staticpaths,items=5000,blocksize=1k,purge_lru,ignore_full", - "static-gzip-all = true", # Serve .gz files if that version is available - ] - ) + if serve_static: + vassal_opts.extend( + [ + # Start 2 offloading threads for each worker + "offload-threads = 2", + "static-cache-paths = 86400", + "static-cache-paths-name = staticpaths", + "cache2 = name=staticpaths,items=5000,blocksize=1k,purge_lru,ignore_full", + # Serve .gz files if that version is available + "static-gzip-all = true", + ] + ) - return ["vassal-set = {}".format(opt.replace(" = ", "=", 1)) for opt in vassal_opts] + return [ + "vassal-set = {}".format(opt.replace(" = ", "=", 1)) + for opt in vassal_opts + ] def get_uwsgi_emperor_opts(settings, port): if settings["DATABASES"]["default"].get("PASSWORD"): - os.environ["DB_PASSWORD"] = settings["DATABASES"]["default"]["PASSWORD"] + os.environ["DB_PASSWORD"] = settings["DATABASES"]["default"][ + "PASSWORD" + ] pg_str = "pg://host={HOST} port={PORT} user={USER} password=$(DB_PASSWORD) dbname={NAME};{query}" else: - pg_str = "pg://host={HOST} port={PORT} user={USER} dbname={NAME};{query}" + pg_str = ( + "pg://host={HOST} port={PORT} user={USER} dbname={NAME};{query}" + ) return get_uwsgi_regular_opts(settings, port) + [ "http = 0.0.0.0:{}".format(port or settings.get("PORT")), - "http-to = /tmp/fastrouter.sock", # Fastrouter speaks the uwsgi protocol. We need to expose http via http-to + # Fastrouter speaks the uwsgi protocol. + # We need to expose http via http-to. + "http-to = /tmp/fastrouter.sock", "fastrouter = /tmp/fastrouter.sock", "fastrouter-use-code-string = 0:{}:get".format( - os.path.join(HERE, "fastrouter_lookup.py") + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "fastrouter_lookup.py", + ) ), "emperor = {}".format( - pg_str.format(query=VASSALS_SQL_QUERY, **settings["DATABASES"]["default"]) + pg_str.format( + query=VASSALS_SQL_QUERY.format( + base_sockets_dir=conf.UWSGI_BASE_SOCKETS_DIR + ), + **settings["DATABASES"]["default"] + ) ), ] def assert_uwsgi_plugin_is_installed(plugin): - # Simply call "uwsgi --need-plugins=" sounds tempting but does not work. - # For some reason (maybe uwsgi inside virtualenv) the above wouldn't find the plugin even if it's installed. + # Simply calling "uwsgi --need-plugins=" sounds tempting but does + # not work. For some reason (maybe uwsgi inside virtualenv) the above + # wouldn't find the plugin even if it's installed. plugins = subprocess.check_output( ["uwsgi --plugins-list; exit 0"], shell=True, stderr=subprocess.STDOUT ) plugins = plugins.decode("utf-8").split("\n") plugins = plugins[: plugins.index("--- end of plugins list ---")] - plugins = list(filter(lambda x: bool(x) and not (x.startswith("***")), plugins)) + plugins = list( + filter(lambda x: bool(x) and not (x.startswith("***")), plugins) + ) plugins = [p.split()[-1] for p in plugins] assert plugin in plugins @@ -200,7 +185,8 @@ def _create_working_dirs(): conf.UWSGI_BASE_SOCKETS_DIR, conf.UWSGI_ALIAS_DOMAIN_MAPPING_DIR, ): - # Ensure tree is clean before creating the working dirs (in order to avoid orphan vassal sockets) + # Ensure tree is clean before creating the working dirs (in order + # to avoid orphan vassal sockets). try: shutil.rmtree(path) except OSError: @@ -212,7 +198,8 @@ def _create_working_dirs(): pass def _create_uwsgi_config_file(settings, port, uwsgi_config_file_path): - # We can't use the regular ini writer because it does not allow repeating keys and does not keep order of keys + # We can't use the regular ini writer because it does not allow + # repeating keys and does not keep order of keys. ini = ["[uwsgi]"] ini.extend(get_uwsgi_emperor_opts(settings, port)) ini.extend(get_uwsgi_vassal_opts(settings, port)) @@ -221,13 +208,13 @@ def _create_uwsgi_config_file(settings, port, uwsgi_config_file_path): cfg_file.write("\n".join(ini)) def _create_alias_domain_mapping_helper_files(settings): - # We are spawning 1 uwsgi process per SITE, not per DOMAIN/ALIAS, so we need to map aliases to a proper domain. - # Here we create a bunch of helper files with filename like "alias|domain" so we can use it as a lookup later. + # We are spawning 1 uwsgi process per SITE, not per DOMAIN/ALIAS, so we + # need to map aliases to a proper domain. Here we create a bunch of + # helper files with filename like "alias|domain" so we can use it as a + # lookup later. db_kwargs = settings["DATABASES"]["default"] - connection_string = ( - "dbname='{NAME}' user='{USER}' host='{HOST}' password='{PASSWORD}'".format( - **db_kwargs - ) + connection_string = "dbname='{NAME}' user='{USER}' host='{HOST}' password='{PASSWORD}'".format( + **db_kwargs ) with psycopg2.connect(connection_string) as connection: @@ -238,11 +225,14 @@ def _create_alias_domain_mapping_helper_files(settings): alias_domain_mapping_dir = conf.UWSGI_ALIAS_DOMAIN_MAPPING_DIR for alias, domain in alias_domain_mapping.items(): filepath = os.path.join( - alias_domain_mapping_dir, UWSGI_ALIAS_SEPARATOR.join([alias, domain]) + alias_domain_mapping_dir, + UWSGI_ALIAS_SEPARATOR.join([alias, domain]), ) open(filepath, "w").close() - uwsgi_config_file_path = os.path.join(conf.UWSGI_BASE_CONFIG_DIR, "config.ini") + uwsgi_config_file_path = os.path.join( + conf.UWSGI_BASE_CONFIG_DIR, "config.ini" + ) # see 'required uwsgi plugins' in README for details about the uwsgi plugin assert_uwsgi_plugin_is_installed("emperor_pg") diff --git a/src/django_multisite_plus/cms_urls.py b/src/django_multisite_plus/cms_urls.py index 963a0b4..d24ce43 100644 --- a/src/django_multisite_plus/cms_urls.py +++ b/src/django_multisite_plus/cms_urls.py @@ -1,6 +1,8 @@ from django.conf.urls import include + from djangocms_multisite.urlresolvers import cms_multisite_url + urlpatterns = [ cms_multisite_url(r"^", include("cms.urls")), ] diff --git a/src/django_multisite_plus/conf.py b/src/django_multisite_plus/conf.py index b755b57..57398a0 100644 --- a/src/django_multisite_plus/conf.py +++ b/src/django_multisite_plus/conf.py @@ -2,18 +2,23 @@ from aldryn_addons.utils import boolean_ish + env = os.environ.get UWSGI_DEFAULT_DOMAIN = env("DJANGO_MULTISITE_PLUS_UWSGI_DEFAULT_DOMAIN") UWSGI_BASE_SOCKETS_DIR = env( - "DJANGO_MULTISITE_PLUS_UWSGI_BASE_SOCKETS_DIR", "/app/uwsgi/tmp/vassal-sockets/" + "DJANGO_MULTISITE_PLUS_UWSGI_BASE_SOCKETS_DIR", + "/app/uwsgi/tmp/vassal-sockets/", ) UWSGI_BASE_CONFIG_DIR = env( "DJANGO_MULTISITE_PLUS_UWSGI_BASE_CONFIG_DIR", "/app/uwsgi/" ) UWSGI_ALIAS_DOMAIN_MAPPING_DIR = env( - "DJANGO_MULTISITE_PLUS_UWSGI_ALIAS_DOMAIN_MAPPING_DIR", "/app/uwsgi/tmp/aliases/" + "DJANGO_MULTISITE_PLUS_UWSGI_ALIAS_DOMAIN_MAPPING_DIR", + "/app/uwsgi/tmp/aliases/", ) -UWSGI_LOCAL_TEST_MODE = boolean_ish(env("DJANGO_MULTISITE_PLUS_UWSGI_LOCAL_TEST_MODE")) +UWSGI_LOCAL_TEST_MODE = boolean_ish( + env("DJANGO_MULTISITE_PLUS_UWSGI_LOCAL_TEST_MODE") +) UWSGI_LOCAL_TEST_KEY = env("DJANGO_MULTISITE_PLUS_UWSGI_LOCAL_TEST_KEY") diff --git a/src/django_multisite_plus/constants.py b/src/django_multisite_plus/constants.py index f2c7583..cf1a25a 100644 --- a/src/django_multisite_plus/constants.py +++ b/src/django_multisite_plus/constants.py @@ -1,3 +1,89 @@ +from .utils import clean_query + + # WARNING: ',' is more intuitive but doesn't work! # uWSGI fails internally with ','. UWSGI_ALIAS_SEPARATOR = "|" + + +# Below we use the domain hash in order to avoid filename length errors (uwsgi +# allows only 102 chars) +VASSALS_SQL_QUERY = clean_query( + """ + SELECT + MD5(ds.domain)||'.ini' ini_filename, + CONCAT_WS(E'\\n', + '[uwsgi]', + 'env = SITE_ID='||ds.id, + 'socket = {base_sockets_dir}'||MD5(ds.domain)||'.sock', + ms.extra_uwsgi_ini + ) ini_content, + EXTRACT('epoch' from ms.last_updated_at) last_updated + FROM + django_multisite_plus_site AS ms + INNER JOIN django_site as ds ON (ds.id = ms.site_id) + WHERE + ms.is_enabled = TRUE + GROUP BY + ds.id, ms.extra_uwsgi_ini, ms.last_updated_at; + """ +) + + +ALIAS_DOMAIN_MAPPING_QUERY = clean_query( + """ + SELECT + alias.domain AS origin_domain, + site.domain AS destination_domain + FROM + multisite_alias AS alias + INNER JOIN django_site AS site ON (alias.site_id = site.id) + """ +) + + +AUTO_POPULATE_EXPLICITLY_ENABLED_ERROR_MESSAGE = """ +DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES cannot be True + +You explicitly set the above setting to True and a configuration change is +necessary to proceed. This feature has been removed in a recent version of the +package because it was leading to race conditions when updating domains in +`multi-process` mode. You can now achieve the same effect by executing the +`multisite_plus_populate_sites` management command during deployment. +""".strip() + + +AUTO_POPULATE_DEFAULT_ENABLED_ERROR_MESSAGE = """ +DJANGO_MULTISITE_PLUS_AUTO_POPULATE_SITES defaults to True + +While the above setting has not been explicitly enabled, a previous version +of this package was automatically populating sites on startup and a +configuration change is necessary. This feature has been removed in a recent +version of the package because it was leading to race conditions when updating +domains in `multi-process` mode. You can now achieve the same effect by +executing the `multisite_plus_populate_sites` management command during +deployment. +""".strip() + + +AUTO_REWRITE_DOMAINS_EXPLICITLY_ENABLED_ERROR_MESSAGE = """ +DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS cannot be True + +You explicitly set the above setting to True and a configuration change is +necessary to proceed. This feature has been removed in a recent version of the +package because it was leading to race conditions when updating domains in +`multi-process` mode. You can now achieve the same effect by executing the +`multisite_plus_rewrite_domains` management command during deployment. +""".strip() + + +AUTO_REWRITE_DOMAINS_NOT_DISABLED_ERROR_MESSAGE = """ +DJANGO_MULTISITE_PLUS_AUTO_REWRITE_DOMAINS is not False + +While the above setting has not been explicitly enabled, it defaults to the +value of the DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS setting, which is currently +set to True. This feature has been removed in a recent version of the package +because it was leading to race conditions when updating domains in +`multi-process` mode. You can now achieve the same effect by executing the +`multisite_plus_rewrite_domains` management command during deployment. +""".strip() diff --git a/src/django_multisite_plus/fastrouter_lookup.py b/src/django_multisite_plus/fastrouter_lookup.py index 41bb974..494b750 100644 --- a/src/django_multisite_plus/fastrouter_lookup.py +++ b/src/django_multisite_plus/fastrouter_lookup.py @@ -6,6 +6,7 @@ from django_multisite_plus import conf from django_multisite_plus.constants import UWSGI_ALIAS_SEPARATOR + logger = logging.getLogger(__name__) @@ -24,15 +25,16 @@ def _get_socket(key): ) ) elif len(sockets) == 1: - matched_domain = os.path.basename(sockets[0]).split(UWSGI_ALIAS_SEPARATOR)[ - 1 - ] + matched_domain = os.path.basename(sockets[0]).split( + UWSGI_ALIAS_SEPARATOR + )[1] # Below we use the domain hash in order to avoid filename length errors (uwsgi allows only 102 chars) matched_domain_hash = hashlib.md5( matched_domain.encode("utf-8") ).hexdigest() socket_path = os.path.join( - conf.UWSGI_BASE_SOCKETS_DIR, "{}.sock".format(matched_domain_hash) + conf.UWSGI_BASE_SOCKETS_DIR, + "{}.sock".format(matched_domain_hash), ) return socket_path.encode("utf-8") else: @@ -49,7 +51,9 @@ def _get_socket(key): ) ) else: - options = glob.glob(os.path.join(conf.UWSGI_BASE_SOCKETS_DIR, "*.sock")) + options = glob.glob( + os.path.join(conf.UWSGI_BASE_SOCKETS_DIR, "*.sock") + ) fake_sockets = { "localhost:8000": options[0], "127.0.0.1:8000": options[1], diff --git a/src/django_multisite_plus/management/__init__.py b/src/django_multisite_plus/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/django_multisite_plus/management/commands/__init__.py b/src/django_multisite_plus/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py b/src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py new file mode 100644 index 0000000..ecb94c2 --- /dev/null +++ b/src/django_multisite_plus/management/commands/multisite_plus_populate_sites.py @@ -0,0 +1,15 @@ +from django.core.management.base import BaseCommand, CommandError + +from django_multisite_plus.models import Site + + +class Command(BaseCommand): + help = "Syncs django_multisite_plus sites based on settings.py" + + def handle(self, *args, **options): + self.stdout.write( + "Syncing django_multisite_plus Sites based on settings" + ) + Site.objects.auto_populate_sites() + + self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py b/src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py new file mode 100644 index 0000000..df7ec8d --- /dev/null +++ b/src/django_multisite_plus/management/commands/multisite_plus_rewrite_domains.py @@ -0,0 +1,20 @@ +from django.core.management.base import BaseCommand, CommandError + +from multisite.models import Alias + +from django_multisite_plus.models import Site + + +class Command(BaseCommand): + help = "Rewrites django.contrib.sites.Site based on settings.py" + + def handle(self, *args, **options): + self.stdout.write( + "Rewriting django.contrib.sites.Site based on local settings" + ) + Site.objects.update_sites() + + self.stdout.write("Syncing multisite.Alias based on Sites") + Alias.canonical.sync_all() + + self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/src/django_multisite_plus/middlewares.py b/src/django_multisite_plus/middlewares.py index 3baeed1..9e3b9da 100644 --- a/src/django_multisite_plus/middlewares.py +++ b/src/django_multisite_plus/middlewares.py @@ -1,13 +1,11 @@ import sys - from imp import reload from django.conf import settings import multisite.middleware -from multisite.models import Alias - from djangocms_multisite import middleware +from multisite.models import Alias class CMSMultiSiteMiddleware(middleware.CMSMultiSiteMiddleware): diff --git a/src/django_multisite_plus/migrations/0001_initial.py b/src/django_multisite_plus/migrations/0001_initial.py index 9625e8a..0e44fdb 100644 --- a/src/django_multisite_plus/migrations/0001_initial.py +++ b/src/django_multisite_plus/migrations/0001_initial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.contrib.sites.models +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/src/django_multisite_plus/models.py b/src/django_multisite_plus/models.py index da572ba..2f5169b 100644 --- a/src/django_multisite_plus/models.py +++ b/src/django_multisite_plus/models.py @@ -1,11 +1,9 @@ -from django.utils.encoding import force_str -from django.db import models, transaction - +import django.contrib.sites.models from django.conf import settings +from django.db import models, transaction +from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ -import django.contrib.sites.models - def domain_for_slug(slug, domain_format=None): domain_format = domain_format or getattr( @@ -44,8 +42,10 @@ def auto_populate_sites(self): if site_id: # A site_id was given. Use it as the primary identifier. try: - contrib_site = django.contrib.sites.models.Site.objects.get( - id=site_id + contrib_site = ( + django.contrib.sites.models.Site.objects.get( + id=site_id + ) ) contrib_site_updated_fields = set() for key, value in contrib_site_defaults.items(): @@ -53,7 +53,9 @@ def auto_populate_sites(self): setattr(contrib_site, key, value) contrib_site_updated_fields.add(key) if contrib_site_updated_fields: - contrib_site.save(update_fields=contrib_site_updated_fields) + contrib_site.save( + update_fields=contrib_site_updated_fields + ) # We're not using update_or_create here to make sure # last_updated_at does not get bumped if nothing changed. # Otherwise uwsgi would keep reloading all wsgi processes @@ -74,10 +76,12 @@ def auto_populate_sites(self): except django.contrib.sites.models.Site.DoesNotExist: domain = domain_for_slug(slug) - contrib_site = django.contrib.sites.models.Site.objects.create( - id=site_id, - domain=domain, - name=name or domain, + contrib_site = ( + django.contrib.sites.models.Site.objects.create( + id=site_id, + domain=domain, + name=name or domain, + ) ) site = Site.objects.create( site=contrib_site, @@ -111,9 +115,11 @@ def auto_populate_sites(self): site.save(update_fields=contrib_site_updated_fields) else: domain = domain_for_slug(slug) - contrib_site = django.contrib.sites.models.Site.objects.create( - domain=domain, - name=name or domain, + contrib_site = ( + django.contrib.sites.models.Site.objects.create( + domain=domain, + name=name or domain, + ) ) site = Site.objects.create( site=contrib_site, @@ -162,7 +168,8 @@ class Site(models.Model): unique=True, ) is_enabled = models.BooleanField( - default=True, help_text=_("Whether this site should be served and available.") + default=True, + help_text=_("Whether this site should be served and available."), ) last_updated_at = models.DateTimeField(auto_now=True) extra_uwsgi_ini = models.TextField(blank=True, default="") @@ -177,7 +184,9 @@ def get_url(self): @property def domain(self): - rewrite = getattr(settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", False) + rewrite = getattr( + settings, "DJANGO_MULTISITE_PLUS_REWRITE_DOMAINS", False + ) if rewrite: return domain_for_slug(self.slug) elif not self.real_domain: diff --git a/src/django_multisite_plus/utils.py b/src/django_multisite_plus/utils.py new file mode 100644 index 0000000..c818bf5 --- /dev/null +++ b/src/django_multisite_plus/utils.py @@ -0,0 +1,2 @@ +def clean_query(sql): + return " ".join([x.strip() for x in sql.strip().splitlines()]) diff --git a/tox.ini b/tox.ini index 26ee7d4..931b8b4 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ envlist = lint, manifest, coverage +isolated_build = true [testenv] depends = test: clean @@ -18,13 +19,13 @@ deps = test: pytest test: pytest-cov test: pytest-django - django22: django==2.2.0 - django32: django==3.2.0 - django40: django==4.0.0 -# django41: django==4.1.0 -# django42: django==4.2.0 + django22: django>=2.2,<2.3 + django32: django>=3.2,<3.3 + django40: django<=4.0,<4.1 +# django41: django>=4.1,<4.2 +# django42: django>=4.2,<4.3 commands = - test: pytest ./tests + test: pytest ./tests {posargs} setenv = COVERAGE_FILE = .artifacts/coverage.{env:TOX_ENV_NAME} DJANGO_SETTINGS_MODULE = testproject.settings @@ -35,8 +36,14 @@ commands = check-manifest skip_install = true [testenv:lint] -deps = black -commands = black --check src/django_multisite_plus +deps = + black + isort + flake8 +commands = + isort --check-only . + black --check src/django_multisite_plus + flake8 --ignore=E501,E722,E402,W503,E203 . skip_install = true [testenv:clean]