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.
Files changed (159) hide show
  1. django_multisite2-3.0.0/PKG-INFO +408 -0
  2. django_multisite2-3.0.0/README.rst +380 -0
  3. django_multisite2-3.0.0/pyproject.toml +114 -0
  4. django_multisite2-3.0.0/pyproject.toml.orig +106 -0
  5. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/__init__.py +2 -0
  6. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/__init__.py +2 -0
  7. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/multisite_model_admin.py +5 -1
  8. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/apps.py +9 -2
  9. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/exceptions.py +4 -0
  10. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/hacks.py +1 -1
  11. django_multisite2-3.0.0/src/multisite/middleware/__init__.py +5 -0
  12. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/middleware/cookie_domain_middleware.py +2 -2
  13. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/middleware/dynamic_site_middleware.py +26 -16
  14. django_multisite2-3.0.0/src/multisite/middleware/dynamic_site_timezone_middleware.py +27 -0
  15. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/migrations/0001_initial.py +3 -1
  16. django_multisite2-3.0.0/src/multisite/migrations/0004_alter_alias_managers.py +31 -0
  17. django_multisite2-3.0.0/src/multisite/migrations/0005_alter_alias_is_canonical.py +26 -0
  18. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/__init__.py +8 -0
  19. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/alias.py +3 -3
  20. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/managers.py +6 -5
  21. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/models/signals.py +1 -1
  22. django_multisite2-3.0.0/src/multisite/models/validators.py +22 -0
  23. django_multisite2-3.0.0/src/multisite/system_checks.py +73 -0
  24. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/threadlocals.py +16 -10
  25. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/utils.py +59 -11
  26. django-multisite2-2.1.2/.coveragerc +0 -3
  27. django-multisite2-2.1.2/.github/workflows/build.yml +0 -74
  28. django-multisite2-2.1.2/.gitignore +0 -25
  29. django-multisite2-2.1.2/.pre-commit-config.yaml +0 -49
  30. django-multisite2-2.1.2/.yamllint +0 -13
  31. django-multisite2-2.1.2/CHANGELOG.rst +0 -129
  32. django-multisite2-2.1.2/MANIFEST.in +0 -3
  33. django-multisite2-2.1.2/PKG-INFO +0 -246
  34. django-multisite2-2.1.2/README.rst +0 -219
  35. django-multisite2-2.1.2/django_multisite2.egg-info/PKG-INFO +0 -246
  36. django-multisite2-2.1.2/django_multisite2.egg-info/SOURCES.txt +0 -148
  37. django-multisite2-2.1.2/django_multisite2.egg-info/dependency_links.txt +0 -1
  38. django-multisite2-2.1.2/django_multisite2.egg-info/not-zip-safe +0 -1
  39. django-multisite2-2.1.2/django_multisite2.egg-info/requires.txt +0 -1
  40. django-multisite2-2.1.2/django_multisite2.egg-info/top_level.txt +0 -2
  41. django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-310.pyc +0 -0
  42. django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-311.pyc +0 -0
  43. django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-312.pyc +0 -0
  44. django-multisite2-2.1.2/multisite/__pycache__/__init__.cpython-39.pyc +0 -0
  45. django-multisite2-2.1.2/multisite/__pycache__/apps.cpython-312.pyc +0 -0
  46. django-multisite2-2.1.2/multisite/__pycache__/exceptions.cpython-312.pyc +0 -0
  47. django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-310.pyc +0 -0
  48. django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-311.pyc +0 -0
  49. django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-312.pyc +0 -0
  50. django-multisite2-2.1.2/multisite/__pycache__/forms.cpython-39.pyc +0 -0
  51. django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-310.pyc +0 -0
  52. django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-311.pyc +0 -0
  53. django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-312.pyc +0 -0
  54. django-multisite2-2.1.2/multisite/__pycache__/hacks.cpython-39.pyc +0 -0
  55. django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-310-pytest-6.2.5.pyc +0 -0
  56. django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-311-pytest-8.1.1.pyc +0 -0
  57. django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-312-pytest-7.4.4.pyc +0 -0
  58. django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-312-pytest-8.1.1.pyc +0 -0
  59. django-multisite2-2.1.2/multisite/__pycache__/test_settings.cpython-39-pytest-6.2.5.pyc +0 -0
  60. django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-310-pytest-6.2.5.pyc +0 -0
  61. django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-311-pytest-8.1.1.pyc +0 -0
  62. django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-312-pytest-7.4.4.pyc +0 -0
  63. django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-312-pytest-8.1.1.pyc +0 -0
  64. django-multisite2-2.1.2/multisite/__pycache__/tests.cpython-39-pytest-6.2.5.pyc +0 -0
  65. django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-310.pyc +0 -0
  66. django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-311.pyc +0 -0
  67. django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-312.pyc +0 -0
  68. django-multisite2-2.1.2/multisite/__pycache__/threadlocals.cpython-39.pyc +0 -0
  69. django-multisite2-2.1.2/multisite/__pycache__/utils.cpython-312.pyc +0 -0
  70. django-multisite2-2.1.2/multisite/admin/__pycache__/__init__.cpython-312.pyc +0 -0
  71. django-multisite2-2.1.2/multisite/admin/__pycache__/alias_admin.cpython-312.pyc +0 -0
  72. django-multisite2-2.1.2/multisite/admin/__pycache__/multisite_changelist.cpython-312.pyc +0 -0
  73. django-multisite2-2.1.2/multisite/admin/__pycache__/multisite_model_admin.cpython-312.pyc +0 -0
  74. django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-310.pyc +0 -0
  75. django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-311.pyc +0 -0
  76. django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-312.pyc +0 -0
  77. django-multisite2-2.1.2/multisite/management/__pycache__/__init__.cpython-39.pyc +0 -0
  78. django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-310.pyc +0 -0
  79. django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-311.pyc +0 -0
  80. django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-312.pyc +0 -0
  81. django-multisite2-2.1.2/multisite/management/commands/__pycache__/__init__.cpython-39.pyc +0 -0
  82. django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-310.pyc +0 -0
  83. django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-311.pyc +0 -0
  84. django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-312.pyc +0 -0
  85. django-multisite2-2.1.2/multisite/management/commands/__pycache__/update_public_suffix_list.cpython-39.pyc +0 -0
  86. django-multisite2-2.1.2/multisite/middleware/__init__.py +0 -4
  87. django-multisite2-2.1.2/multisite/middleware/__pycache__/__init__.cpython-312.pyc +0 -0
  88. django-multisite2-2.1.2/multisite/middleware/__pycache__/cookie_domain_middleware.cpython-312.pyc +0 -0
  89. django-multisite2-2.1.2/multisite/middleware/__pycache__/dynamic_site_middleware.cpython-312.pyc +0 -0
  90. django-multisite2-2.1.2/multisite/migrations/0004_alter_alias_managers.py +0 -22
  91. django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-310.pyc +0 -0
  92. django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-311.pyc +0 -0
  93. django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  94. django-multisite2-2.1.2/multisite/migrations/__pycache__/0001_initial.cpython-39.pyc +0 -0
  95. django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-310.pyc +0 -0
  96. django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-311.pyc +0 -0
  97. django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-312.pyc +0 -0
  98. django-multisite2-2.1.2/multisite/migrations/__pycache__/0002_alter_alias_id_alter_alias_is_canonical.cpython-39.pyc +0 -0
  99. django-multisite2-2.1.2/multisite/migrations/__pycache__/0003_alias_multisite_alias_canon_site_uniq.cpython-311.pyc +0 -0
  100. django-multisite2-2.1.2/multisite/migrations/__pycache__/0003_alter_alias_options_alter_alias_unique_together_and_more.cpython-312.pyc +0 -0
  101. django-multisite2-2.1.2/multisite/migrations/__pycache__/0004_alter_alias_managers.cpython-312.pyc +0 -0
  102. django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-310.pyc +0 -0
  103. django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  104. django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  105. django-multisite2-2.1.2/multisite/migrations/__pycache__/__init__.cpython-39.pyc +0 -0
  106. django-multisite2-2.1.2/multisite/models/__pycache__/__init__.cpython-312.pyc +0 -0
  107. django-multisite2-2.1.2/multisite/models/__pycache__/alias.cpython-312.pyc +0 -0
  108. django-multisite2-2.1.2/multisite/models/__pycache__/managers.cpython-312.pyc +0 -0
  109. django-multisite2-2.1.2/multisite/models/__pycache__/signals.cpython-312.pyc +0 -0
  110. django-multisite2-2.1.2/multisite/models/__pycache__/validators.cpython-312.pyc +0 -0
  111. django-multisite2-2.1.2/multisite/models/validators.py +0 -14
  112. django-multisite2-2.1.2/multisite/tests/__init__.py +0 -0
  113. django-multisite2-2.1.2/multisite/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  114. django-multisite2-2.1.2/multisite/tests/__pycache__/get_test_allowed_hosts.cpython-312.pyc +0 -0
  115. django-multisite2-2.1.2/multisite/tests/__pycache__/get_test_http_response.cpython-312.pyc +0 -0
  116. django-multisite2-2.1.2/multisite/tests/__pycache__/test_settings.cpython-312-pytest-8.1.1.pyc +0 -0
  117. django-multisite2-2.1.2/multisite/tests/__pycache__/test_settings.cpython-312.pyc +0 -0
  118. django-multisite2-2.1.2/multisite/tests/__pycache__/tests.cpython-312-pytest-8.1.1.pyc +0 -0
  119. django-multisite2-2.1.2/multisite/tests/get_test_allowed_hosts.py +0 -14
  120. django-multisite2-2.1.2/multisite/tests/get_test_http_response.py +0 -13
  121. django-multisite2-2.1.2/multisite/tests/hosts.py +0 -46
  122. django-multisite2-2.1.2/multisite/tests/test_settings.py +0 -23
  123. django-multisite2-2.1.2/multisite/tests/tests/__init__.py +0 -0
  124. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  125. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/request_factory.cpython-312.pyc +0 -0
  126. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_alias.cpython-312.pyc +0 -0
  127. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_contrib_site.cpython-312.pyc +0 -0
  128. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_cookie_domain_middleware.cpython-312.pyc +0 -0
  129. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_dynamic_site_middleware.cpython-312.pyc +0 -0
  130. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_site_cache.cpython-312.pyc +0 -0
  131. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_site_domain.cpython-312.pyc +0 -0
  132. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_site_id.cpython-312.pyc +0 -0
  133. django-multisite2-2.1.2/multisite/tests/tests/__pycache__/test_template_loader.cpython-312.pyc +0 -0
  134. django-multisite2-2.1.2/multisite/tests/tests/request_factory.py +0 -12
  135. django-multisite2-2.1.2/multisite/tests/tests/test_alias.py +0 -271
  136. django-multisite2-2.1.2/multisite/tests/tests/test_contrib_site.py +0 -21
  137. django-multisite2-2.1.2/multisite/tests/tests/test_cookie_domain_middleware.py +0 -196
  138. django-multisite2-2.1.2/multisite/tests/tests/test_dynamic_site_middleware.py +0 -262
  139. django-multisite2-2.1.2/multisite/tests/tests/test_site_cache.py +0 -127
  140. django-multisite2-2.1.2/multisite/tests/tests/test_site_domain.py +0 -24
  141. django-multisite2-2.1.2/multisite/tests/tests/test_site_id.py +0 -76
  142. django-multisite2-2.1.2/multisite/tests/tests/test_template_loader.py +0 -49
  143. django-multisite2-2.1.2/multisite_app/__init__.py +0 -0
  144. django-multisite2-2.1.2/multisite_app/apps.py +0 -6
  145. django-multisite2-2.1.2/multisite_app/urls.py +0 -5
  146. django-multisite2-2.1.2/multisite_app/views.py +0 -8
  147. django-multisite2-2.1.2/pyproject.toml +0 -32
  148. django-multisite2-2.1.2/runtests.py +0 -18
  149. django-multisite2-2.1.2/setup.cfg +0 -59
  150. {django-multisite2-2.1.2 → django_multisite2-3.0.0}/LICENSE +0 -0
  151. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/alias_admin.py +0 -0
  152. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/admin/multisite_changelist.py +0 -0
  153. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/forms.py +0 -0
  154. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/management/__init__.py +0 -0
  155. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/management/commands/__init__.py +0 -0
  156. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/management/commands/update_public_suffix_list.py +0 -0
  157. {django-multisite2-2.1.2 → django_multisite2-3.0.0/src}/multisite/migrations/0002_alter_alias_id_alter_alias_is_canonical.py +0 -0
  158. {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
  159. {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