django-multisite2 2.1.2__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_multisite2-3.0.0/PKG-INFO +408 -0
- django_multisite2-3.0.0/README.rst +380 -0
- django_multisite2-3.0.0/pyproject.toml +114 -0
- django_multisite2-3.0.0/pyproject.toml.orig +106 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/__init__.py +2 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/__init__.py +2 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/multisite_model_admin.py +5 -1
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/apps.py +9 -2
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/exceptions.py +4 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/hacks.py +1 -1
- django_multisite2-3.0.0/src/multisite/middleware/__init__.py +5 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/middleware/cookie_domain_middleware.py +2 -2
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/middleware/dynamic_site_middleware.py +26 -16
- django_multisite2-3.0.0/src/multisite/middleware/dynamic_site_timezone_middleware.py +27 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/migrations/0001_initial.py +3 -1
- django_multisite2-3.0.0/src/multisite/migrations/0004_alter_alias_managers.py +31 -0
- django_multisite2-3.0.0/src/multisite/migrations/0005_alter_alias_is_canonical.py +26 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/__init__.py +8 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/alias.py +3 -3
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/managers.py +6 -5
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/signals.py +1 -1
- django_multisite2-3.0.0/src/multisite/models/validators.py +22 -0
- django_multisite2-3.0.0/src/multisite/system_checks.py +73 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/threadlocals.py +16 -10
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/utils.py +59 -11
- django-multisite2-2.1.2/.coveragerc +0 -3
- django-multisite2-2.1.2/.github/workflows/build.yml +0 -74
- django-multisite2-2.1.2/.gitignore +0 -25
- django-multisite2-2.1.2/.pre-commit-config.yaml +0 -49
- django-multisite2-2.1.2/.yamllint +0 -13
- django-multisite2-2.1.2/CHANGELOG.rst +0 -129
- django-multisite2-2.1.2/MANIFEST.in +0 -3
- django-multisite2-2.1.2/PKG-INFO +0 -246
- django-multisite2-2.1.2/README.rst +0 -219
- django-multisite2-2.1.2/django_multisite2.egg-info/PKG-INFO +0 -246
- django-multisite2-2.1.2/django_multisite2.egg-info/SOURCES.txt +0 -148
- django-multisite2-2.1.2/django_multisite2.egg-info/dependency_links.txt +0 -1
- django-multisite2-2.1.2/django_multisite2.egg-info/not-zip-safe +0 -1
- django-multisite2-2.1.2/django_multisite2.egg-info/requires.txt +0 -1
- django-multisite2-2.1.2/django_multisite2.egg-info/top_level.txt +0 -2
- django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/apps.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/exceptions.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-310-pytest-6.2.5.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-311-pytest-8.1.1.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-312-pytest-7.4.4.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-312-pytest-8.1.1.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-39-pytest-6.2.5.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-310-pytest-6.2.5.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-311-pytest-8.1.1.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-312-pytest-7.4.4.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-312-pytest-8.1.1.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-39-pytest-6.2.5.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/__pycache__/utils.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/admin/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/admin/__pycache__/alias_admin.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/admin/__pycache__/multisite_changelist.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/admin/__pycache__/multisite_model_admin.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/middleware/__init__.py +0 -4
- django-multisite2-2.1.2/multisite/middleware/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/middleware/__pycache__/cookie_domain_middleware.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/middleware/__pycache__/dynamic_site_middleware.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/0004_alter_alias_managers.py +0 -22
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0003_alias_multisite_alias_canon_site_uniq.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0003_alter_alias_options_alter_alias_unique_together_and_more.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/0004_alter_alias_managers.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-310.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-39.pyc +0 -0
- django-multisite2-2.1.2/multisite/models/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/models/__pycache__/alias.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/models/__pycache__/managers.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/models/__pycache__/signals.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/models/__pycache__/validators.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/models/validators.py +0 -14
- django-multisite2-2.1.2/multisite/tests/__init__.py +0 -0
- django-multisite2-2.1.2/multisite/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/__pycache__/get_test_allowed_hosts.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/__pycache__/get_test_http_response.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/__pycache__/test_settings.cpython-312-pytest-8.1.1.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/__pycache__/test_settings.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/__pycache__/tests.cpython-312-pytest-8.1.1.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/get_test_allowed_hosts.py +0 -14
- django-multisite2-2.1.2/multisite/tests/get_test_http_response.py +0 -13
- django-multisite2-2.1.2/multisite/tests/hosts.py +0 -46
- django-multisite2-2.1.2/multisite/tests/test_settings.py +0 -23
- django-multisite2-2.1.2/multisite/tests/tests/__init__.py +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/request_factory.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_alias.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_contrib_site.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_cookie_domain_middleware.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_dynamic_site_middleware.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_site_cache.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_site_domain.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_site_id.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_template_loader.cpython-312.pyc +0 -0
- django-multisite2-2.1.2/multisite/tests/tests/request_factory.py +0 -12
- django-multisite2-2.1.2/multisite/tests/tests/test_alias.py +0 -271
- django-multisite2-2.1.2/multisite/tests/tests/test_contrib_site.py +0 -21
- django-multisite2-2.1.2/multisite/tests/tests/test_cookie_domain_middleware.py +0 -196
- django-multisite2-2.1.2/multisite/tests/tests/test_dynamic_site_middleware.py +0 -262
- django-multisite2-2.1.2/multisite/tests/tests/test_site_cache.py +0 -127
- django-multisite2-2.1.2/multisite/tests/tests/test_site_domain.py +0 -24
- django-multisite2-2.1.2/multisite/tests/tests/test_site_id.py +0 -76
- django-multisite2-2.1.2/multisite/tests/tests/test_template_loader.py +0 -49
- django-multisite2-2.1.2/multisite_app/__init__.py +0 -0
- django-multisite2-2.1.2/multisite_app/apps.py +0 -6
- django-multisite2-2.1.2/multisite_app/urls.py +0 -5
- django-multisite2-2.1.2/multisite_app/views.py +0 -8
- django-multisite2-2.1.2/pyproject.toml +0 -32
- django-multisite2-2.1.2/runtests.py +0 -18
- django-multisite2-2.1.2/setup.cfg +0 -59
- {django-multisite2-2.1.2 → django_multisite2-3.0.0}/LICENSE +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/alias_admin.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/multisite_changelist.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/forms.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/management/__init__.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/management/commands/__init__.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/management/commands/update_public_suffix_list.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/migrations/0002_alter_alias_id_alter_alias_is_canonical.py +0 -0
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/migrations/0003_alter_alias_options_alter_alias_unique_together_and_more.py +2 -2
- {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/migrations/__init__.py +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-multisite2
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Serve multiple sites from a single Django application
|
|
5
|
+
Keywords: django,nginx,edc,clinical trials,research,data management,gunicorn,deployment
|
|
6
|
+
Author: Leonid S Shestera
|
|
7
|
+
Author-email: Leonid S Shestera <leonid@shestera.ru>
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 5.2
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Dist: tldextract
|
|
20
|
+
Maintainer: Erik van Widenfelt
|
|
21
|
+
Maintainer-email: Erik van Widenfelt <ew2789@gmail.com>
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Project-URL: Homepage, http://github.com/erikvw/django-multisite2
|
|
24
|
+
Project-URL: Documentation, http://github.com/erikvw/django-multisite2/blob/develop/README.rst
|
|
25
|
+
Project-URL: Repository, http://github.com/erikvw/django-multisite2.git
|
|
26
|
+
Project-URL: Changelog, http://github.com/erikvw/django-multisite2/blob/main/CHANGES
|
|
27
|
+
Description-Content-Type: text/x-rst
|
|
28
|
+
|
|
29
|
+
|pypi| |actions| |codecov| |downloads| |uv| |ruff|
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
django-multisite2
|
|
34
|
+
=================
|
|
35
|
+
|
|
36
|
+
With `django-multisite2`_ a single instance of a Django project can serve multiple sites using a single settings file (multi-tenant). The current ``SITE_ID`` is extracted from the URL.
|
|
37
|
+
|
|
38
|
+
``django-multisite2`` provides the module ``multisite``.
|
|
39
|
+
|
|
40
|
+
In ``settings``, the static ``SITE_ID`` is replaced with ``multisite`` dynamic ``SiteID``::
|
|
41
|
+
|
|
42
|
+
# settings.py
|
|
43
|
+
SITE_ID = SiteID(default=1)
|
|
44
|
+
|
|
45
|
+
the dynamic ``SiteID`` behaves like an integer. When combined with ``multisite`` middleware, ``SiteID`` will return the current ``SITE_ID`` based on the url. For example, each url below is an alias of the same server instance. With ``multisite`` you might have something like this::
|
|
46
|
+
|
|
47
|
+
# https://harare.example.com
|
|
48
|
+
>>> from django.conf import settings
|
|
49
|
+
>>> settings.SITE_ID
|
|
50
|
+
10
|
|
51
|
+
|
|
52
|
+
# https://kampala.example.com
|
|
53
|
+
>>> from django.conf import settings
|
|
54
|
+
>>> settings.SITE_ID
|
|
55
|
+
20
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Python 3.11+ Django 4.2+. New releases are cut from the ``main`` branch.
|
|
59
|
+
|
|
60
|
+
Older versions of Django are supported by the original `django-multisite`_ project.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Installation
|
|
64
|
+
============
|
|
65
|
+
|
|
66
|
+
Install with pip:
|
|
67
|
+
|
|
68
|
+
.. code-block::
|
|
69
|
+
|
|
70
|
+
pip install django-multisite2
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Replace your ``SITE_ID`` in ``settings.py`` to:
|
|
74
|
+
|
|
75
|
+
.. code-block::
|
|
76
|
+
|
|
77
|
+
from multisite import SiteID
|
|
78
|
+
SITE_ID = SiteID(default=1)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
add to INSTALLED_APPS:
|
|
82
|
+
|
|
83
|
+
.. code-block::
|
|
84
|
+
|
|
85
|
+
INSTALLED_APPS = [
|
|
86
|
+
...
|
|
87
|
+
'django.contrib.sites',
|
|
88
|
+
'multisite',
|
|
89
|
+
...
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
Edit settings.py MIDDLEWARE:
|
|
94
|
+
|
|
95
|
+
.. code-block::
|
|
96
|
+
|
|
97
|
+
MIDDLEWARE = (
|
|
98
|
+
...
|
|
99
|
+
'multisite.middleware.DynamicSiteMiddleware',
|
|
100
|
+
...
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
On Django 6.0 and earlier, you have to silence the system check ``sites.E101``:
|
|
104
|
+
|
|
105
|
+
.. code-block::
|
|
106
|
+
|
|
107
|
+
SILENCED_SYSTEM_CHECKS = ["sites.E101"]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
The Alias model
|
|
111
|
+
---------------
|
|
112
|
+
``Alias`` is the lookup table that maps a hostname to a ``Site``.
|
|
113
|
+
|
|
114
|
+
On each request ``DynamicSiteMiddleware`` takes the hostname from the ``Host`` header,
|
|
115
|
+
looks it up in ``Alias``, and sets ``SITE_ID`` to the matching ``Alias.site_id``. Django's
|
|
116
|
+
``Site.domain`` is not consulted for that lookup, so a ``Site`` is only reachable once it
|
|
117
|
+
has an ``Alias``.
|
|
118
|
+
|
|
119
|
+
Canonical aliases are created for you
|
|
120
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
121
|
+
Every ``Site`` that has a domain gets exactly one **canonical** ``Alias``, whose ``domain``
|
|
122
|
+
mirrors ``Site.domain``. You do not create these by hand. Multisite keeps them in step
|
|
123
|
+
through three hooks:
|
|
124
|
+
|
|
125
|
+
* a ``post_save`` signal on ``Site`` creates the canonical ``Alias`` for a new site
|
|
126
|
+
* a ``pre_save`` signal on ``Site`` updates it when ``Site.domain`` changes
|
|
127
|
+
* the ``post_migrate`` signal ``post_migrate_sync_alias`` reconciles every ``Site``, which
|
|
128
|
+
catches sites created before multisite was installed, or created in ways that bypass
|
|
129
|
+
signals such as ``loaddata``, ``bulk_create`` or raw SQL
|
|
130
|
+
|
|
131
|
+
In the normal case, creating a ``Site`` is all you need::
|
|
132
|
+
|
|
133
|
+
>>> site = Site.objects.create(domain="example.com", name="Example")
|
|
134
|
+
>>> site.aliases.get(is_canonical=1)
|
|
135
|
+
<Alias: example.com -> example.com>
|
|
136
|
+
|
|
137
|
+
>>> site.domain = "example.org"
|
|
138
|
+
>>> site.save()
|
|
139
|
+
>>> site.aliases.get(is_canonical=1)
|
|
140
|
+
<Alias: example.org -> example.org>
|
|
141
|
+
|
|
142
|
+
Extra hostnames are what you add yourself
|
|
143
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
144
|
+
Any further ``Alias`` rows for the same ``Site`` are **non-canonical**: additional
|
|
145
|
+
hostnames that resolve to the same site. These are the ones you create::
|
|
146
|
+
|
|
147
|
+
Alias.objects.create(site=site, domain="www.example.org")
|
|
148
|
+
Alias.objects.create(site=site, domain="*.example.org")
|
|
149
|
+
|
|
150
|
+
A non-canonical alias defaults to ``redirect_to_canonical=True``, so requests arriving on
|
|
151
|
+
it are redirected to the site's canonical domain. Set it to ``False`` to serve the site on
|
|
152
|
+
that hostname without redirecting.
|
|
153
|
+
|
|
154
|
+
``Alias.domain`` accepts wildcards. A hostname is matched from most to least specific, so
|
|
155
|
+
``shop.example.org`` tries ``shop.example.org``, then ``*.example.org``, then ``*.org``,
|
|
156
|
+
then ``*``, each with and without the request's port. An ``Alias`` with ``domain='*'``
|
|
157
|
+
therefore catches everything.
|
|
158
|
+
|
|
159
|
+
Populating aliases yourself
|
|
160
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
161
|
+
Sites created in a data migration use historical models, which do not fire the signals
|
|
162
|
+
above. Two helpers reconcile things, and both are idempotent::
|
|
163
|
+
|
|
164
|
+
from multisite.utils import (
|
|
165
|
+
create_or_sync_alias_from_site,
|
|
166
|
+
create_or_sync_canonical_from_all_sites,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
create_or_sync_alias_from_site(site=site) # one site
|
|
170
|
+
create_or_sync_canonical_from_all_sites() # every site
|
|
171
|
+
|
|
172
|
+
Both accept an ``apps`` argument so they can be called from a data migration against
|
|
173
|
+
historical models::
|
|
174
|
+
|
|
175
|
+
def forwards(apps, schema_editor):
|
|
176
|
+
create_or_sync_canonical_from_all_sites(apps=apps)
|
|
177
|
+
|
|
178
|
+
If a ``Site`` has a blank domain, its canonical ``Alias`` is removed instead, since there
|
|
179
|
+
is no hostname to resolve.
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
Using a custom cache
|
|
184
|
+
--------------------
|
|
185
|
+
Append to settings.py, in order to use a custom cache that can be
|
|
186
|
+
safely cleared::
|
|
187
|
+
|
|
188
|
+
# The cache connection to use for multisite.
|
|
189
|
+
# Default: 'default'
|
|
190
|
+
CACHE_MULTISITE_ALIAS = 'multisite'
|
|
191
|
+
|
|
192
|
+
# The cache key prefix that multisite should use.
|
|
193
|
+
# If not set, defaults to the KEY_PREFIX used in the defined
|
|
194
|
+
# CACHE_MULTISITE_ALIAS or the default cache (empty string if not set)
|
|
195
|
+
CACHE_MULTISITE_KEY_PREFIX = ''
|
|
196
|
+
|
|
197
|
+
If you have set CACHE\_MULTISITE\_ALIAS to a custom value, *e.g.*
|
|
198
|
+
``'multisite'``, add a separate backend to settings.py CACHES::
|
|
199
|
+
|
|
200
|
+
CACHES = {
|
|
201
|
+
'default': {
|
|
202
|
+
...
|
|
203
|
+
},
|
|
204
|
+
'multisite': {
|
|
205
|
+
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
|
206
|
+
'TIMEOUT': 60 * 60 * 24, # 24 hours
|
|
207
|
+
...
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
Domain fallbacks
|
|
213
|
+
----------------
|
|
214
|
+
|
|
215
|
+
By default, if the domain name is unknown, multisite will respond with
|
|
216
|
+
an HTTP 404 Not Found error. To change this behaviour, add to
|
|
217
|
+
settings.py::
|
|
218
|
+
|
|
219
|
+
# The view function or class-based view that multisite will
|
|
220
|
+
# use when it cannot match the hostname with a Site. This can be
|
|
221
|
+
# the name of the function or the function itself.
|
|
222
|
+
# Default: None
|
|
223
|
+
MULTISITE_FALLBACK = 'django.views.generic.base.RedirectView
|
|
224
|
+
|
|
225
|
+
# Keyword arguments for the MULTISITE_FALLBACK view.
|
|
226
|
+
# Default: {}
|
|
227
|
+
MULTISITE_FALLBACK_KWARGS = {'url': 'http://example.com/',
|
|
228
|
+
'permanent': False}
|
|
229
|
+
|
|
230
|
+
Templates
|
|
231
|
+
---------
|
|
232
|
+
|
|
233
|
+
This feature has been removed in version 2.0.0.
|
|
234
|
+
|
|
235
|
+
If required, create template subdirectories for domain level templates (in a
|
|
236
|
+
location specified in settings.TEMPLATES['DIRS'].
|
|
237
|
+
|
|
238
|
+
Multisite's template loader will look for templates in folders with the names of
|
|
239
|
+
domains, such as::
|
|
240
|
+
|
|
241
|
+
templates/example.com
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
The template loader will also look for templates in a folder specified by the
|
|
245
|
+
optional MULTISITE_DEFAULT_TEMPLATE_DIR setting, e.g.::
|
|
246
|
+
|
|
247
|
+
templates/multisite_templates
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
Cross-domain cookie support
|
|
251
|
+
---------------------------
|
|
252
|
+
|
|
253
|
+
In order to support `cross-domain cookies`_ , for purposes like single-sign-on, prepend the following to the top of
|
|
254
|
+
settings.py MIDDLEWARE (MIDDLEWARE_CLASSES for Django < 1.10)::
|
|
255
|
+
|
|
256
|
+
MIDDLEWARE = (
|
|
257
|
+
'multisite.middleware.CookieDomainMiddleware',
|
|
258
|
+
...
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
CookieDomainMiddleware will consult the `Public Suffix List`_
|
|
262
|
+
for effective top-level domains.
|
|
263
|
+
It caches this file
|
|
264
|
+
in the system's default temporary directory
|
|
265
|
+
as ``effective_tld_names.dat``.
|
|
266
|
+
To change this in settings.py::
|
|
267
|
+
|
|
268
|
+
MULTISITE_PUBLIC_SUFFIX_LIST_CACHE = '/path/to/multisite_tld.dat'
|
|
269
|
+
|
|
270
|
+
By default,
|
|
271
|
+
any cookies without a domain set
|
|
272
|
+
will be reset to allow \*.domain.tld.
|
|
273
|
+
To change this in settings.py::
|
|
274
|
+
|
|
275
|
+
MULTISITE_COOKIE_DOMAIN_DEPTH = 1 # Allow only *.subdomain.domain.tld
|
|
276
|
+
|
|
277
|
+
In order to fetch a new version of the list,
|
|
278
|
+
run::
|
|
279
|
+
|
|
280
|
+
manage.py update_public_suffix_list
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
Post-migrate signal: post_migrate_sync_alias
|
|
284
|
+
--------------------------------------------
|
|
285
|
+
The ``post-migrate`` signal ``post_migrate_sync_alias`` is registered in the ``apps.py``. ``post_migrate_sync_alias``
|
|
286
|
+
ensures the ``domain`` in multisite's ``Alias`` model is updated to match that of django's ``Site`` model. This signal must
|
|
287
|
+
run AFTER any ``post-migrate`` signals that manipulate Django's ``Site`` model. If you have an app that manipulates Django's
|
|
288
|
+
``Site`` model, place it before ``multisite`` in `settings. INSTALLED_APPS`. If this is not possible, you may configure ``multisite``
|
|
289
|
+
to not connect the ``post-migrate`` signal in ``apps.py`` so that you can do it somewhere else in your code.
|
|
290
|
+
|
|
291
|
+
To configure `multisite` to not connect the `post-post_migrate_sync_alias` in the `apps.py`, update your settings::
|
|
292
|
+
|
|
293
|
+
MULTISITE_REGISTER_POST_MIGRATE_SYNC_ALIAS = False
|
|
294
|
+
|
|
295
|
+
With the `settings` attribute set to `False`, it is your responsibility to connect the signal in your code. Note that if you do not sync the `Alias` and `Site`
|
|
296
|
+
models after the `Site` model has changed, multisite may not recognize the domain and switch to the fallback view or
|
|
297
|
+
raise a `Http404` error.
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
Per-site time zones
|
|
301
|
+
-------------------
|
|
302
|
+
``DynamicSiteTimezoneMiddleware`` activates the current site's time zone for the request
|
|
303
|
+
thread, so Django renders every datetime in local time for whichever site served the
|
|
304
|
+
request. It is the time zone equivalent of what ``SiteID`` does for ``SITE_ID``.
|
|
305
|
+
|
|
306
|
+
Map each site to a time zone in settings.py. Values may be an IANA key or a ``ZoneInfo``::
|
|
307
|
+
|
|
308
|
+
MULTISITE_TIME_ZONES = {
|
|
309
|
+
1: "Africa/Dar_es_Salaam",
|
|
310
|
+
2: "America/New_York",
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
Then add the middleware, which must come AFTER ``DynamicSiteMiddleware``, since that is
|
|
314
|
+
what resolves ``SITE_ID`` for the request:
|
|
315
|
+
|
|
316
|
+
.. code-block::
|
|
317
|
+
|
|
318
|
+
MIDDLEWARE = (
|
|
319
|
+
...
|
|
320
|
+
'multisite.middleware.DynamicSiteMiddleware',
|
|
321
|
+
'multisite.middleware.DynamicSiteTimezoneMiddleware',
|
|
322
|
+
...
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
Nothing else needs to change. ``django.utils.timezone.localtime()``, template rendering,
|
|
326
|
+
form widgets and the admin all follow the activated time zone.
|
|
327
|
+
|
|
328
|
+
The lookup itself is available directly::
|
|
329
|
+
|
|
330
|
+
from multisite.utils import get_multisite_timezone
|
|
331
|
+
|
|
332
|
+
``get_multisite_timezone()`` returns the IANA key of the time zone for the current
|
|
333
|
+
``SITE_ID``, always as a ``str``. It falls back to ``settings.TIME_ZONE`` and issues a
|
|
334
|
+
``RuntimeWarning`` if ``MULTISITE_TIME_ZONES`` is unset or has no entry for the current
|
|
335
|
+
site, and returns ``settings.TIME_ZONE`` without warning when ``SITE_ID`` is a plain
|
|
336
|
+
integer rather than a ``SiteID``.
|
|
337
|
+
|
|
338
|
+
Outside a request, in management commands, signal handlers or queue workers, no time zone
|
|
339
|
+
is activated and Django falls back to ``settings.TIME_ZONE``. Wrap the entry point as you
|
|
340
|
+
would with ``SiteID.override()``::
|
|
341
|
+
|
|
342
|
+
from django.utils import timezone
|
|
343
|
+
|
|
344
|
+
with timezone.override(get_multisite_timezone()):
|
|
345
|
+
...
|
|
346
|
+
|
|
347
|
+
Three system checks cover the configuration:
|
|
348
|
+
|
|
349
|
+
* ``multisite.E001`` if ``DynamicSiteTimezoneMiddleware`` is listed before ``DynamicSiteMiddleware``
|
|
350
|
+
* ``multisite.W001`` if ``DynamicSiteMiddleware`` is missing altogether
|
|
351
|
+
* ``multisite.W002`` (deploy only) if ``MULTISITE_TIME_ZONES`` is set but the middleware is not installed
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
Development Environments
|
|
355
|
+
------------------------
|
|
356
|
+
Multisite returns a valid Alias when in "development mode" (defaulting to the
|
|
357
|
+
alias associated with the default SiteID.
|
|
358
|
+
|
|
359
|
+
Development mode is either:
|
|
360
|
+
- Running tests, i.e. manage.py test
|
|
361
|
+
- Running locally in settings.DEBUG = True, where the hostname is a top-level name, i.e. localhost
|
|
362
|
+
|
|
363
|
+
In order to have multisite use aliases in local environments, add entries to
|
|
364
|
+
your local etc/hosts file to match aliases in your applications. E.g. ::
|
|
365
|
+
|
|
366
|
+
127.0.0.1 example.com
|
|
367
|
+
127.0.0.1 examplealias.com
|
|
368
|
+
|
|
369
|
+
And access your application at example.com:8000 or examplealias.com:8000 instead of
|
|
370
|
+
the usual localhost:8000.
|
|
371
|
+
|
|
372
|
+
Tests
|
|
373
|
+
-----
|
|
374
|
+
|
|
375
|
+
To run the tests:
|
|
376
|
+
|
|
377
|
+
.. code-block:: bash
|
|
378
|
+
|
|
379
|
+
uv run runtests.py
|
|
380
|
+
|
|
381
|
+
or
|
|
382
|
+
|
|
383
|
+
.. code-block:: bash
|
|
384
|
+
|
|
385
|
+
uv run tox
|
|
386
|
+
|
|
387
|
+
.. _django-multisite: https://github.com/ecometrica/django-multisite
|
|
388
|
+
.. _cross-domain cookies: http://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_Path
|
|
389
|
+
.. _Public Suffix List: http://publicsuffix.org/
|
|
390
|
+
|
|
391
|
+
.. |pypi| image:: https://img.shields.io/pypi/v/django-multisite2.svg
|
|
392
|
+
:target: https://pypi.python.org/pypi/django-multisite2
|
|
393
|
+
|
|
394
|
+
.. |actions| image:: https://github.com/erikvw/django-multisite2/actions/workflows/build.yml/badge.svg
|
|
395
|
+
:target: https://github.com/erikvw/django-multisite2/actions/workflows/build.yml
|
|
396
|
+
|
|
397
|
+
.. |codecov| image:: https://codecov.io/gh/erikvw/django-multisite2/branch/develop/graph/badge.svg
|
|
398
|
+
:target: https://codecov.io/gh/erikvw/django-multisite2
|
|
399
|
+
|
|
400
|
+
.. |downloads| image:: https://pepy.tech/badge/django-multisite2
|
|
401
|
+
:target: https://pepy.tech/project/django-multisite2
|
|
402
|
+
|
|
403
|
+
.. |uv| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json
|
|
404
|
+
:target: https://github.com/astral-sh/uv
|
|
405
|
+
|
|
406
|
+
.. |ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
407
|
+
:target: https://github.com/astral-sh/ruff
|
|
408
|
+
:alt: Ruff
|