django-multisite2 3.0.0__tar.gz → 3.1.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 (36) hide show
  1. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/PKG-INFO +10 -11
  2. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/README.rst +9 -10
  3. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/pyproject.toml +1 -1
  4. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/pyproject.toml.orig +1 -1
  5. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/apps.py +1 -1
  6. django_multisite2-3.1.0/src/multisite/constants.py +2 -0
  7. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/exceptions.py +4 -0
  8. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/middleware/dynamic_site_timezone_middleware.py +8 -0
  9. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/system_checks.py +17 -24
  10. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/utils.py +38 -30
  11. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/LICENSE +0 -0
  12. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/__init__.py +0 -0
  13. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/admin/__init__.py +0 -0
  14. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/admin/alias_admin.py +0 -0
  15. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/admin/multisite_changelist.py +0 -0
  16. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/admin/multisite_model_admin.py +0 -0
  17. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/forms.py +0 -0
  18. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/hacks.py +0 -0
  19. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/management/__init__.py +0 -0
  20. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/management/commands/__init__.py +0 -0
  21. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/management/commands/update_public_suffix_list.py +0 -0
  22. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/middleware/__init__.py +0 -0
  23. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/middleware/cookie_domain_middleware.py +0 -0
  24. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/middleware/dynamic_site_middleware.py +0 -0
  25. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/migrations/0001_initial.py +0 -0
  26. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/migrations/0002_alter_alias_id_alter_alias_is_canonical.py +0 -0
  27. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/migrations/0003_alter_alias_options_alter_alias_unique_together_and_more.py +0 -0
  28. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/migrations/0004_alter_alias_managers.py +0 -0
  29. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/migrations/0005_alter_alias_is_canonical.py +0 -0
  30. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/migrations/__init__.py +0 -0
  31. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/models/__init__.py +0 -0
  32. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/models/alias.py +0 -0
  33. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/models/managers.py +0 -0
  34. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/models/signals.py +0 -0
  35. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/models/validators.py +0 -0
  36. {django_multisite2-3.0.0 → django_multisite2-3.1.0}/src/multisite/threadlocals.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-multisite2
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Serve multiple sites from a single Django application
5
5
  Keywords: django,nginx,edc,clinical trials,research,data management,gunicorn,deployment
6
6
  Author: Leonid S Shestera
@@ -303,15 +303,15 @@ Per-site time zones
303
303
  thread, so Django renders every datetime in local time for whichever site served the
304
304
  request. It is the time zone equivalent of what ``SiteID`` does for ``SITE_ID``.
305
305
 
306
- Map each site to a time zone in settings.py. Values may be an IANA key or a ``ZoneInfo``::
306
+ Map each site to a time zone in settings.py. Values must be an IANA key::
307
307
 
308
308
  MULTISITE_TIME_ZONES = {
309
309
  1: "Africa/Dar_es_Salaam",
310
310
  2: "America/New_York",
311
311
  }
312
312
 
313
- Then add the middleware, which must come AFTER ``DynamicSiteMiddleware``, since that is
314
- what resolves ``SITE_ID`` for the request:
313
+
314
+ Add the middleware after ``DynamicSiteMiddleware``
315
315
 
316
316
  .. code-block::
317
317
 
@@ -329,11 +329,10 @@ The lookup itself is available directly::
329
329
 
330
330
  from multisite.utils import get_multisite_timezone
331
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``.
332
+ ``get_multisite_timezone(site_id=None)`` returns the time zone for ``site_id``, or for the
333
+ current ``SITE_ID`` if not given. It requires ``DynamicSiteTimezoneMiddleware`` in
334
+ ``MIDDLEWARE`` and raises ``MultisiteTimezoneError`` otherwise, or if ``site_id`` has no
335
+ entry in ``MULTISITE_TIME_ZONES``. The current site falls back to ``settings.TIME_ZONE``.
337
336
 
338
337
  Outside a request, in management commands, signal handlers or queue workers, no time zone
339
338
  is activated and Django falls back to ``settings.TIME_ZONE``. Wrap the entry point as you
@@ -347,8 +346,8 @@ would with ``SiteID.override()``::
347
346
  Three system checks cover the configuration:
348
347
 
349
348
  * ``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
349
+ * ``multisite.E002`` if the middleware is installed but ``MULTISITE_TIME_ZONES`` is missing or empty
350
+ * ``multisite.E003`` if ``DynamicSiteMiddleware`` is missing altogether
352
351
 
353
352
 
354
353
  Development Environments
@@ -275,15 +275,15 @@ Per-site time zones
275
275
  thread, so Django renders every datetime in local time for whichever site served the
276
276
  request. It is the time zone equivalent of what ``SiteID`` does for ``SITE_ID``.
277
277
 
278
- Map each site to a time zone in settings.py. Values may be an IANA key or a ``ZoneInfo``::
278
+ Map each site to a time zone in settings.py. Values must be an IANA key::
279
279
 
280
280
  MULTISITE_TIME_ZONES = {
281
281
  1: "Africa/Dar_es_Salaam",
282
282
  2: "America/New_York",
283
283
  }
284
284
 
285
- Then add the middleware, which must come AFTER ``DynamicSiteMiddleware``, since that is
286
- what resolves ``SITE_ID`` for the request:
285
+
286
+ Add the middleware after ``DynamicSiteMiddleware``
287
287
 
288
288
  .. code-block::
289
289
 
@@ -301,11 +301,10 @@ The lookup itself is available directly::
301
301
 
302
302
  from multisite.utils import get_multisite_timezone
303
303
 
304
- ``get_multisite_timezone()`` returns the IANA key of the time zone for the current
305
- ``SITE_ID``, always as a ``str``. It falls back to ``settings.TIME_ZONE`` and issues a
306
- ``RuntimeWarning`` if ``MULTISITE_TIME_ZONES`` is unset or has no entry for the current
307
- site, and returns ``settings.TIME_ZONE`` without warning when ``SITE_ID`` is a plain
308
- integer rather than a ``SiteID``.
304
+ ``get_multisite_timezone(site_id=None)`` returns the time zone for ``site_id``, or for the
305
+ current ``SITE_ID`` if not given. It requires ``DynamicSiteTimezoneMiddleware`` in
306
+ ``MIDDLEWARE`` and raises ``MultisiteTimezoneError`` otherwise, or if ``site_id`` has no
307
+ entry in ``MULTISITE_TIME_ZONES``. The current site falls back to ``settings.TIME_ZONE``.
309
308
 
310
309
  Outside a request, in management commands, signal handlers or queue workers, no time zone
311
310
  is activated and Django falls back to ``settings.TIME_ZONE``. Wrap the entry point as you
@@ -319,8 +318,8 @@ would with ``SiteID.override()``::
319
318
  Three system checks cover the configuration:
320
319
 
321
320
  * ``multisite.E001`` if ``DynamicSiteTimezoneMiddleware`` is listed before ``DynamicSiteMiddleware``
322
- * ``multisite.W001`` if ``DynamicSiteMiddleware`` is missing altogether
323
- * ``multisite.W002`` (deploy only) if ``MULTISITE_TIME_ZONES`` is set but the middleware is not installed
321
+ * ``multisite.E002`` if the middleware is installed but ``MULTISITE_TIME_ZONES`` is missing or empty
322
+ * ``multisite.E003`` if ``DynamicSiteMiddleware`` is missing altogether
324
323
 
325
324
 
326
325
  Development Environments
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-multisite2"
3
- version = "3.0.0"
3
+ version = "3.1.0"
4
4
  description = "Serve multiple sites from a single Django application"
5
5
  keywords = [
6
6
  "django",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-multisite2"
3
- version = "3.0.0"
3
+ version = "3.1.0"
4
4
  description = "Serve multiple sites from a single Django application"
5
5
  readme = { file = "README.rst", content-type = "text/x-rst" }
6
6
  keywords = ["django", "nginx", "edc", "clinical trials", "research", "data management", "gunicorn", "deployment"]
@@ -27,7 +27,7 @@ class AppConfig(DjangoAppConfig):
27
27
 
28
28
  def ready(self):
29
29
  register(multisite_middleware_check)
30
- register(multisite_timezone_setting_check, deploy=True)
30
+ register(multisite_timezone_setting_check)
31
31
  if not getattr(settings, "MULTISITE_REGISTER_POST_MIGRATE_SYNC_ALIAS", True):
32
32
  post_migrate.connect(
33
33
  post_migrate_sync_alias,
@@ -0,0 +1,2 @@
1
+ DYNAMIC_SITE_MIDDLEWARE = "multisite.middleware.DynamicSiteMiddleware"
2
+ DYNAMIC_SITE_TIMEZONE_MIDDLEWARE = "multisite.middleware.DynamicSiteTimezoneMiddleware"
@@ -37,6 +37,10 @@ class MultisiteCookieDomainDepthError(Exception):
37
37
  pass
38
38
 
39
39
 
40
+ class MultisiteTimezoneError(Exception):
41
+ pass
42
+
43
+
40
44
  def debug_raise_disallowed_host_exception(e):
41
45
  if getattr(settings, "MULTISITE_DEBUG", None):
42
46
  raise MultisiteDisallowedHost(
@@ -9,6 +9,14 @@ class DynamicSiteTimezoneMiddleware:
9
9
  Must be listed AFTER `multisite.middleware.DynamicSiteMiddleware`,
10
10
  which resolves `settings.SITE_ID` for this request.
11
11
 
12
+ Requires settings.MULTISITE_TIME_ZONES format {site_id: IANA key}.
13
+ For example:
14
+ MULTISITE_TIME_ZONES = {
15
+ 10: "Africa/Gaborone",
16
+ 20: "Africa/Dar_es_Salaam",
17
+ 30: "Africa/Lagos",
18
+ }
19
+
12
20
  Outside a request (management commands, signals, workers) nothing
13
21
  is activated and Django falls back to `settings.TIME_ZONE`. Use
14
22
  `timezone.override(get_multisite_timezone())` there, as you would use
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.conf import settings
4
- from django.core.checks import CheckMessage, Error, Warning
4
+ from django.core.checks import CheckMessage, Error
5
5
 
6
- DYNAMIC_SITE_MIDDLEWARE = "multisite.middleware.DynamicSiteMiddleware"
7
- DYNAMIC_SITE_TIMEZONE_MIDDLEWARE = "multisite.middleware.DynamicSiteTimezoneMiddleware"
6
+ from .constants import DYNAMIC_SITE_MIDDLEWARE, DYNAMIC_SITE_TIMEZONE_MIDDLEWARE
8
7
 
9
8
 
10
9
  def multisite_middleware_check(app_configs, **kwargs) -> list[CheckMessage]: # noqa: ARG001
@@ -22,14 +21,14 @@ def multisite_middleware_check(app_configs, **kwargs) -> list[CheckMessage]: #
22
21
  return errors
23
22
  if DYNAMIC_SITE_MIDDLEWARE not in middleware:
24
23
  errors.append(
25
- Warning(
26
- f"Missing MIDDLEWARE. `{DYNAMIC_SITE_TIMEZONE_MIDDLEWARE}` has no effect "
27
- f"without `{DYNAMIC_SITE_MIDDLEWARE}`.",
24
+ Error(
25
+ f"Missing MIDDLEWARE. `{DYNAMIC_SITE_TIMEZONE_MIDDLEWARE}` "
26
+ f"requires `{DYNAMIC_SITE_MIDDLEWARE}`.",
28
27
  hint=(
29
- "Without it `settings.SITE_ID` is never resolved for the request, so "
30
- "the default site's timezone is activated every time."
28
+ f"Add `{DYNAMIC_SITE_MIDDLEWARE}` to settings "
29
+ f"before `{DYNAMIC_SITE_TIMEZONE_MIDDLEWARE}`."
31
30
  ),
32
- id="multisite.W001",
31
+ id="multisite.E003",
33
32
  )
34
33
  )
35
34
  elif middleware.index(DYNAMIC_SITE_TIMEZONE_MIDDLEWARE) < middleware.index(
@@ -50,24 +49,18 @@ def multisite_middleware_check(app_configs, **kwargs) -> list[CheckMessage]: #
50
49
 
51
50
 
52
51
  def multisite_timezone_setting_check(app_configs, **kwargs) -> list[CheckMessage]: # noqa: ARG001
53
- """Checks that `settings.MULTISITE_TIME_ZONES`, if set, is acted on.
54
-
55
- Registered with `deploy=True`, so it runs under `check --deploy`
56
- and not on every test run.
57
- """
52
+ """Checks that `settings.MULTISITE_TIME_ZONES`, if set, is acted on."""
58
53
  errors: list[CheckMessage] = []
59
- if getattr(settings, "MULTISITE_TIME_ZONES", None) and (
60
- DYNAMIC_SITE_TIMEZONE_MIDDLEWARE
61
- not in list(getattr(settings, "MIDDLEWARE", None) or [])
54
+ if not getattr(settings, "MULTISITE_TIME_ZONES", None) and (
55
+ DYNAMIC_SITE_TIMEZONE_MIDDLEWARE in (list(getattr(settings, "MIDDLEWARE", None) or []))
62
56
  ):
63
57
  errors.append(
64
- Warning(
65
- "settings.MULTISITE_TIME_ZONES is set but has no effect.",
66
- hint=(
67
- f"Add `{DYNAMIC_SITE_TIMEZONE_MIDDLEWARE}` to MIDDLEWARE, after "
68
- f"`{DYNAMIC_SITE_MIDDLEWARE}`."
69
- ),
70
- id="multisite.W002",
58
+ Error(
59
+ "settings.MULTISITE_TIME_ZONES is missing or not set. "
60
+ f"MULTISITE_TIME_ZONES is required if {DYNAMIC_SITE_TIMEZONE_MIDDLEWARE} "
61
+ "is active in middleware.",
62
+ hint="Add `MULTISITE_TIME_ZONES` to settings.",
63
+ id="multisite.E002",
71
64
  )
72
65
  )
73
66
  return errors
@@ -15,6 +15,8 @@ from django.core.exceptions import (
15
15
  from django.http import Http404
16
16
  from django.urls import get_callable
17
17
 
18
+ from .constants import DYNAMIC_SITE_TIMEZONE_MIDDLEWARE
19
+ from .exceptions import MultisiteTimezoneError
18
20
  from .threadlocals import SiteID
19
21
 
20
22
  if TYPE_CHECKING:
@@ -179,35 +181,41 @@ def get_user_sites(request) -> QuerySet[Site]:
179
181
  return sites
180
182
 
181
183
 
182
- def get_multisite_timezone() -> str:
183
- """Returns the IANA key of the time zone for the current site.
184
+ def get_multisite_timezone(site_id: int | None = None) -> str:
185
+ """Returns the IANA key of the time zone for the given site.
184
186
 
185
- Values in `settings.MULTISITE_TIME_ZONES` may be given as a str
186
- or as a `ZoneInfo`. Either way a str is returned, since callers
187
- pass the result straight to `ZoneInfo()`.
187
+ Requires `DynamicSiteTimezoneMiddleware` and that the given site
188
+ exist in settings.MULTISITE_TIME_ZONES.
189
+
190
+ See also DynamicSiteTimezoneMiddleware and system checks.
188
191
  """
189
- timezone = settings.TIME_ZONE
190
- if isinstance(settings.SITE_ID, SiteID):
191
- site_timezones = getattr(settings, "MULTISITE_TIME_ZONES", {})
192
- if not site_timezones:
193
- warnings.warn(
194
- (
195
- "settings.MULTISITE_TIME_ZONES not set for multisite trial. "
196
- f"Defaulting to {settings.TIME_ZONE}"
197
- ),
198
- RuntimeWarning,
199
- stacklevel=2,
200
- )
201
- elif not site_timezones.get(settings.SITE_ID):
202
- warnings.warn(
203
- (
204
- "settings.MULTISITE_TIME_ZONES missing timezone for site "
205
- f"{settings.SITE_ID}. Defaulting to {settings.TIME_ZONE}. "
206
- f"Got {settings.MULTISITE_TIME_ZONES}."
207
- ),
208
- RuntimeWarning,
209
- stacklevel=2,
210
- )
211
- else:
212
- timezone = site_timezones.get(settings.SITE_ID)
213
- return str(timezone)
192
+
193
+ timezone_data: dict[int | str, str] = getattr(settings, "MULTISITE_TIME_ZONES", {})
194
+ if DYNAMIC_SITE_TIMEZONE_MIDDLEWARE not in (
195
+ list(getattr(settings, "MIDDLEWARE", None) or [])
196
+ ):
197
+ func_name = sys._getframe().f_code.co_name
198
+ raise MultisiteTimezoneError(
199
+ "Middleware needed for function. "
200
+ f"Add `{DYNAMIC_SITE_TIMEZONE_MIDDLEWARE}` to MIDDLEWARE. "
201
+ f"Got function `{func_name}`."
202
+ )
203
+
204
+ if isinstance(settings.SITE_ID, SiteID) and not timezone_data:
205
+ warnings.warn(
206
+ (
207
+ "settings.MULTISITE_TIME_ZONES not set for multisite deployment. "
208
+ f"Defaulting to {settings.TIME_ZONE}. Try adding MULTISITE_TIME_ZONES "
209
+ "to your settings."
210
+ ),
211
+ RuntimeWarning,
212
+ stacklevel=2,
213
+ )
214
+ if site_id is not None and not timezone_data.get(site_id):
215
+ raise MultisiteTimezoneError(
216
+ "settings.MULTISITE_TIME_ZONES missing timezone for site_id. "
217
+ f"Expected one of {timezone_data}. Got site_id={site_id}."
218
+ )
219
+
220
+ site_id: str = settings.SITE_ID if site_id is None else site_id
221
+ return timezone_data.get(site_id) or settings.TIME_ZONE