django-multisite2 2.0.0__py3-none-any.whl

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 (51) hide show
  1. django_multisite2-2.0.0.dist-info/LICENSE +28 -0
  2. django_multisite2-2.0.0.dist-info/METADATA +246 -0
  3. django_multisite2-2.0.0.dist-info/RECORD +51 -0
  4. django_multisite2-2.0.0.dist-info/WHEEL +5 -0
  5. django_multisite2-2.0.0.dist-info/top_level.txt +2 -0
  6. multisite/__init__.py +1 -0
  7. multisite/admin/__init__.py +3 -0
  8. multisite/admin/alias_admin.py +58 -0
  9. multisite/admin/multisite_changelist.py +64 -0
  10. multisite/admin/multisite_model_admin.py +161 -0
  11. multisite/apps.py +29 -0
  12. multisite/exceptions.py +57 -0
  13. multisite/forms.py +22 -0
  14. multisite/hacks.py +163 -0
  15. multisite/management/__init__.py +0 -0
  16. multisite/management/commands/__init__.py +0 -0
  17. multisite/management/commands/update_public_suffix_list.py +33 -0
  18. multisite/middleware/__init__.py +4 -0
  19. multisite/middleware/cookie_domain_middleware.py +72 -0
  20. multisite/middleware/dynamic_site_middleware.py +198 -0
  21. multisite/migrations/0001_initial.py +71 -0
  22. multisite/migrations/0002_alter_alias_id_alter_alias_is_canonical.py +32 -0
  23. multisite/migrations/0003_alter_alias_options_alter_alias_unique_together_and_more.py +41 -0
  24. multisite/migrations/0004_alter_alias_managers.py +22 -0
  25. multisite/migrations/__init__.py +0 -0
  26. multisite/models/__init__.py +3 -0
  27. multisite/models/alias.py +110 -0
  28. multisite/models/managers.py +196 -0
  29. multisite/models/signals.py +36 -0
  30. multisite/models/validators.py +14 -0
  31. multisite/tests/__init__.py +0 -0
  32. multisite/tests/get_test_allowed_hosts.py +14 -0
  33. multisite/tests/get_test_http_response.py +13 -0
  34. multisite/tests/hosts.py +46 -0
  35. multisite/tests/test_settings.py +23 -0
  36. multisite/tests/tests/__init__.py +0 -0
  37. multisite/tests/tests/request_factory.py +12 -0
  38. multisite/tests/tests/test_alias.py +271 -0
  39. multisite/tests/tests/test_contrib_site.py +21 -0
  40. multisite/tests/tests/test_cookie_domain_middleware.py +196 -0
  41. multisite/tests/tests/test_dynamic_site_middleware.py +262 -0
  42. multisite/tests/tests/test_site_cache.py +127 -0
  43. multisite/tests/tests/test_site_domain.py +24 -0
  44. multisite/tests/tests/test_site_id.py +76 -0
  45. multisite/tests/tests/test_template_loader.py +49 -0
  46. multisite/threadlocals.py +136 -0
  47. multisite/utils.py +172 -0
  48. multisite_app/__init__.py +0 -0
  49. multisite_app/apps.py +6 -0
  50. multisite_app/urls.py +5 -0
  51. multisite_app/views.py +8 -0
@@ -0,0 +1,57 @@
1
+ from django.conf import settings
2
+
3
+
4
+ class MultisiteError(Exception):
5
+ pass
6
+
7
+
8
+ class MultisiteSiteDoesNotExist(Exception):
9
+ pass
10
+
11
+
12
+ class MultisiteCacheError(Exception):
13
+ pass
14
+
15
+
16
+ class MultisiteFallbackError(Exception):
17
+ pass
18
+
19
+
20
+ class MultisiteDisallowedHost(Exception):
21
+ pass
22
+
23
+
24
+ class MultisiteInvalidHostError(Exception):
25
+ pass
26
+
27
+
28
+ class MultisiteServerError(Exception):
29
+ pass
30
+
31
+
32
+ class MultisiteCookieDomainDepthError(Exception):
33
+ pass
34
+
35
+
36
+ def debug_raise_disallowed_host_exception(e):
37
+ if getattr(settings, "MULTISITE_DEBUG", None):
38
+ raise MultisiteDisallowedHost(
39
+ f"DisallowedHost. To silence this exception, set MULTISITE_DEBUG=False. Got {e}."
40
+ )
41
+
42
+
43
+ def debug_raise_cache_missed_exception(netloc, alias):
44
+ if getattr(settings, "MULTISITE_DEBUG", None):
45
+ raise MultisiteCacheError(
46
+ f"Cache missed. Got location {netloc} and Alias {alias}. "
47
+ "To silence this exception, set MULTISITE_DEBUG=False."
48
+ )
49
+
50
+
51
+ def debug_check_status_code(response, **kwargs) -> None:
52
+ if response.status_code == 500 and getattr(settings, "MULTISITE_DEBUG", None):
53
+ raise MultisiteServerError(
54
+ f"HttpResponse 500. {kwargs}. Got {response}."
55
+ "To silence this exception, set MULTISITE_DEBUG=False."
56
+ )
57
+ return None
multisite/forms.py ADDED
@@ -0,0 +1,22 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+
3
+ from django.contrib.sites.admin import SiteAdmin
4
+ from django.core.exceptions import ObjectDoesNotExist, ValidationError
5
+
6
+ from .models import Alias
7
+
8
+
9
+ class SiteForm(SiteAdmin.form):
10
+ def clean_domain(self):
11
+ domain = self.cleaned_data["domain"]
12
+
13
+ try:
14
+ alias = Alias.objects.get(domain=domain)
15
+ except ObjectDoesNotExist:
16
+ # New Site that doesn't clobber an Alias
17
+ return domain
18
+
19
+ if alias.site_id == self.instance.pk and alias.is_canonical:
20
+ return domain
21
+
22
+ raise ValidationError('Cannot overwrite non-canonical Alias: "%s"' % alias.domain)
multisite/hacks.py ADDED
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from django.conf import settings
6
+ from django.core.exceptions import ObjectDoesNotExist
7
+ from django.db.models.signals import post_delete, post_save
8
+
9
+ __all__ = ["use_framework_for_site_cache"]
10
+
11
+ from multisite.exceptions import MultisiteSiteDoesNotExist
12
+
13
+
14
+ def use_framework_for_site_cache():
15
+ """Patches sites app to use the caching framework instead of a dict.
16
+
17
+ This patch has to exist because SITE_CACHE is normally a dict,
18
+ which is local only to the process. When running multiple
19
+ processes, a change to a Site will not be reflected across other
20
+ ones.
21
+ """
22
+ from django.contrib.sites import models
23
+
24
+ # Patch the SITE_CACHE
25
+ site_cache = SiteCache()
26
+ models.SITE_CACHE = DictCache(site_cache)
27
+
28
+ # Patch the SiteManager class
29
+ models.SiteManager.clear_cache = site_manager_clear_cache
30
+ models.SiteManager._get_site_by_id = site_manager_get_site_by_id
31
+
32
+ # Hooks to update SiteCache
33
+ post_save.connect(site_cache._site_changed_hook, sender=models.Site)
34
+ post_delete.connect(site_cache._site_deleted_hook, sender=models.Site)
35
+
36
+
37
+ def site_manager_clear_cache(self):
38
+ """Clears the ``Site`` object cache.
39
+
40
+ Override `SiteManager.clear_cache` so it doesn't clobber
41
+ SITE_CACHE
42
+ """
43
+ models = sys.modules.get(self.__class__.__module__)
44
+ models.SITE_CACHE.clear()
45
+
46
+
47
+ def site_manager_get_site_by_id(self, site_id):
48
+ """Patch _get_site_by_id to retrieve the site from the cache at
49
+ the beginning of the method to avoid a race condition.
50
+
51
+ Override SiteManager._get_site_by_id
52
+ """
53
+ models = sys.modules.get(self.__class__.__module__)
54
+ site = models.SITE_CACHE.get(site_id)
55
+ if site is None:
56
+ try:
57
+ site = self.get(pk=site_id)
58
+ except ObjectDoesNotExist as e:
59
+ raise MultisiteSiteDoesNotExist(f"{e} Got site_id={site_id}.")
60
+ models.SITE_CACHE[site_id] = site
61
+ return site
62
+
63
+
64
+ class SiteCache:
65
+ """Wrapper for SITE_CACHE that assigns a key_prefix."""
66
+
67
+ def __init__(self, cache=None):
68
+ from django.core.cache import caches
69
+
70
+ if cache is None:
71
+ cache_alias = getattr(settings, "CACHE_MULTISITE_ALIAS", "default")
72
+ self._key_prefix = getattr(
73
+ settings,
74
+ "CACHE_MULTISITE_KEY_PREFIX",
75
+ settings.CACHES[cache_alias].get("KEY_PREFIX", ""),
76
+ )
77
+ cache = caches[cache_alias]
78
+ else:
79
+ self._key_prefix = getattr(
80
+ settings, "CACHE_MULTISITE_KEY_PREFIX", cache.key_prefix
81
+ )
82
+ self._cache = cache
83
+
84
+ def _get_cache_key(self, key):
85
+ return "sites.%s.%s" % (self.key_prefix, key)
86
+
87
+ def _clean_site(self, site):
88
+ # Force site.id to be an int, not a SiteID object.
89
+ site.id = int(site.id)
90
+ return site
91
+
92
+ @property
93
+ def key_prefix(self):
94
+ return self._key_prefix
95
+
96
+ def get(self, key, *args, **kwargs):
97
+ return self._cache.get(key=self._get_cache_key(key), *args, **kwargs)
98
+
99
+ def set(self, key, value, *args, **kwargs):
100
+ self._cache.set(
101
+ key=self._get_cache_key(key), value=self._clean_site(value), *args, **kwargs
102
+ )
103
+
104
+ def delete(self, key, *args, **kwargs):
105
+ self._cache.delete(key=self._get_cache_key(key), *args, **kwargs)
106
+
107
+ def __contains__(self, key, *args, **kwargs):
108
+ return self._cache.__contains__(key=self._get_cache_key(key), *args, **kwargs)
109
+
110
+ def clear(self, *args, **kwargs):
111
+ self._cache.clear(*args, **kwargs)
112
+
113
+ def _site_changed_hook(self, sender, instance, raw, *args, **kwargs):
114
+ if raw:
115
+ return
116
+ self.set(key=instance.pk, value=instance)
117
+
118
+ def _site_deleted_hook(self, sender, instance, *args, **kwargs):
119
+ self.delete(key=instance.pk)
120
+
121
+
122
+ class DictCache:
123
+ """Add dictionary protocol to `django.core.cache.backends.
124
+ BaseCache`.
125
+ """
126
+
127
+ def __init__(self, cache):
128
+ self._cache = cache
129
+
130
+ def __getitem__(self, key):
131
+ """x.__getitem__(y) <==> x[y]"""
132
+ hash(key) # Raise TypeError if unhashable
133
+ result = self._cache.get(key=key)
134
+ if result is None:
135
+ raise KeyError(key)
136
+ return result
137
+
138
+ def __setitem__(self, key, value):
139
+ """x.__setitem__(i, y) <==> x[i]=y"""
140
+ hash(key) # Raise TypeError if unhashable
141
+ self._cache.set(key=key, value=value)
142
+
143
+ def __delitem__(self, key):
144
+ """x.__delitem__(y) <==> del x[y]"""
145
+ hash(key) # Raise TypeError if unhashable
146
+ self._cache.delete(key=key)
147
+
148
+ def __contains__(self, item):
149
+ """D.__contains__(k) -> True if D has a key k, else False."""
150
+ hash(item) # Raise TypeError if unhashable
151
+ return self._cache.__contains__(key=item)
152
+
153
+ def clear(self):
154
+ """D.clear() -> None. Remove all items from D."""
155
+ self._cache.clear()
156
+
157
+ def get(self, key, default=None, version=None):
158
+ """D.key(k[, d]) -> k if D has a key k, else d.
159
+
160
+ Defaults to None.
161
+ """
162
+ hash(key) # Raise TypeError if unhashable
163
+ return self._cache.get(key=key, default=default, version=version)
File without changes
File without changes
@@ -0,0 +1,33 @@
1
+ from __future__ import absolute_import, print_function, unicode_literals
2
+
3
+ import logging
4
+ import tempfile
5
+
6
+ import tldextract
7
+ from django.conf import settings
8
+ from django.core.management.base import BaseCommand
9
+
10
+
11
+ class Command(BaseCommand):
12
+ def handle(self, **options):
13
+ self.setup_logging(verbosity=options.get("verbosity", 1))
14
+
15
+ cache_dir = getattr(
16
+ settings, "MULTISITE_PUBLIC_SUFFIX_LIST_CACHE_DIR", tempfile.gettempdir()
17
+ )
18
+ self.log("Updating {cache_dir}".format(cache_dir=cache_dir))
19
+
20
+ extract = tldextract.TLDExtract(cache_dir=cache_dir)
21
+ extract.update(fetch_now=True)
22
+ self.log("Done.")
23
+
24
+ def setup_logging(self, verbosity):
25
+ self.verbosity = int(verbosity)
26
+
27
+ # Connect to tldextract's logger
28
+ self.logger = logging.getLogger("tldextract")
29
+ if self.verbosity < 2:
30
+ self.logger.setLevel(logging.CRITICAL)
31
+
32
+ def log(self, msg):
33
+ self.logger.info(msg)
@@ -0,0 +1,4 @@
1
+ from .cookie_domain_middleware import CookieDomainMiddleware
2
+ from .dynamic_site_middleware import DynamicSiteMiddleware
3
+
4
+ __all__ = ["CookieDomainMiddleware", "DynamicSiteMiddleware"]
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import tempfile
4
+
5
+ import tldextract
6
+ from django.conf import settings
7
+
8
+ __all__ = ["CookieDomainMiddleware"]
9
+
10
+ from multisite.exceptions import MultisiteCookieDomainDepthError
11
+
12
+
13
+ def get_cookie_domain_depth():
14
+ return getattr(settings, "MULTISITE_COOKIE_DOMAIN_DEPTH", 0)
15
+
16
+
17
+ def get_public_suffix_list_cache_dir():
18
+ return getattr(settings, "MULTISITE_PUBLIC_SUFFIX_LIST_CACHE_DIR", None)
19
+
20
+
21
+ class CookieDomainMiddleware:
22
+ def __init__(self, get_response):
23
+ self.get_response = get_response
24
+ try:
25
+ self.depth = int(get_cookie_domain_depth())
26
+ except ValueError:
27
+ raise MultisiteCookieDomainDepthError(
28
+ f"Invalid MULTISITE_COOKIE_DOMAIN_DEPTH. Got {get_cookie_domain_depth()}"
29
+ )
30
+ if self.depth < 0:
31
+ raise MultisiteCookieDomainDepthError(
32
+ f"Invalid MULTISITE_COOKIE_DOMAIN_DEPTH. Got {get_cookie_domain_depth()}"
33
+ )
34
+ self.psl_cache_dir = get_public_suffix_list_cache_dir()
35
+ if self.psl_cache_dir is None:
36
+ self.psl_cache_dir = tempfile.gettempdir()
37
+ self._tldextract = None
38
+
39
+ def __call__(self, request):
40
+ response = self.get_response(request)
41
+ matched = self.match_cookies(response)
42
+ if not matched:
43
+ return response # No cookies to edit
44
+
45
+ parsed = self.tldextract(request.get_host())
46
+ if not parsed.suffix:
47
+ return response # IP address or local path
48
+ if not parsed.domain:
49
+ return response # Only TLD
50
+
51
+ subdomains = parsed.subdomain.split(".") if parsed.subdomain else []
52
+ if not self.depth:
53
+ subdomains = [""]
54
+ elif len(subdomains) < self.depth:
55
+ return response # Not enough subdomain parts
56
+ else:
57
+ subdomains = [""] + subdomains[-self.depth :]
58
+
59
+ domain = ".".join(subdomains + [parsed.domain, parsed.suffix])
60
+
61
+ for morsel in matched:
62
+ morsel["domain"] = domain
63
+ return response
64
+
65
+ def tldextract(self, url):
66
+ if self._tldextract is None:
67
+ self._tldextract = tldextract.TLDExtract(cache_dir=self.psl_cache_dir)
68
+ return self._tldextract(url)
69
+
70
+ @staticmethod
71
+ def match_cookies(response):
72
+ return [c for c in response.cookies.values() if not c["domain"]]
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ from hashlib import md5 as md5_constructor
4
+ from urllib.parse import urlsplit, urlunsplit
5
+
6
+ from django.conf import settings
7
+ from django.contrib.sites.models import SITE_CACHE, Site
8
+ from django.core import mail
9
+ from django.core.cache import caches
10
+ from django.core.exceptions import DisallowedHost
11
+ from django.db.models.signals import post_delete, post_init, pre_save
12
+ from django.http import HttpResponse, HttpResponsePermanentRedirect
13
+
14
+ from ..exceptions import (
15
+ MultisiteError,
16
+ MultisiteInvalidHostError,
17
+ debug_check_status_code,
18
+ debug_raise_cache_missed_exception,
19
+ debug_raise_disallowed_host_exception,
20
+ )
21
+ from ..models import Alias
22
+
23
+ __all__ = ["DynamicSiteMiddleware"]
24
+
25
+ from ..utils import (
26
+ get_cache_multisite_alias,
27
+ get_cache_multisite_prefix,
28
+ get_fallback_kwargs,
29
+ get_fallback_view_or_404,
30
+ )
31
+
32
+
33
+ class DynamicSiteMiddleware:
34
+ def __init__(self, get_response):
35
+ self.get_response = get_response
36
+ if not hasattr(settings.SITE_ID, "set"):
37
+ raise MultisiteError(
38
+ "Invalid type for settings.SITE_ID: %s" % type(settings.SITE_ID).__name__
39
+ )
40
+
41
+ self.cache_alias = get_cache_multisite_alias()
42
+ self.key_prefix = get_cache_multisite_prefix() or settings.CACHES[
43
+ self.cache_alias
44
+ ].get("KEY_PREFIX", "")
45
+
46
+ self.cache = caches[self.cache_alias]
47
+ post_init.connect(
48
+ self.site_domain_cache_hook, sender=Site, dispatch_uid="multisite_post_init"
49
+ )
50
+ pre_save.connect(self.site_domain_changed_hook, sender=Site)
51
+ post_delete.connect(self.site_deleted_hook, sender=Site)
52
+
53
+ def __call__(self, request):
54
+ # if 500 here -> Site ObjectDoesNotExist was raised by get_current_site()
55
+ # debug_check_status_code(response, request=request)
56
+ try:
57
+ netloc = request.get_host().lower()
58
+ except DisallowedHost as e:
59
+ debug_raise_disallowed_host_exception(e)
60
+ settings.SITE_ID.reset()
61
+ response = self.fallback_view(request)
62
+ else:
63
+ cache_key = self.get_cache_key(netloc)
64
+ if (alias := self.cache.get(cache_key)) is not None:
65
+ # found Alias
66
+ self.cache.set(cache_key, alias)
67
+ settings.SITE_ID.set(alias.site_id)
68
+ response = self.redirect_to_canonical(request, alias)
69
+ elif (alias := self.get_alias(netloc)) is None:
70
+ # Cache missed, fallback using settings.MULTISITE_FALLBACK
71
+ debug_raise_cache_missed_exception(netloc, alias)
72
+ settings.SITE_ID.reset()
73
+ response = self.fallback_view(request)
74
+ else:
75
+ # found Site
76
+ self.cache.set(cache_key, alias)
77
+ settings.SITE_ID.set(alias.site_id)
78
+ SITE_CACHE[settings.SITE_ID] = alias.site # Pre-populate SITE_CACHE
79
+ response = self.redirect_to_canonical(request, alias)
80
+ # debug_check_status_code(response, netloc=netloc, alias=alias, site=alias.site)
81
+ return response or self.get_response(request)
82
+
83
+ @staticmethod
84
+ def redirect_to_canonical(request, alias: Alias) -> HttpResponse | None:
85
+ if not alias.redirect_to_canonical or alias.is_canonical:
86
+ response = None
87
+ else:
88
+ url = urlsplit(request.build_absolute_uri(request.get_full_path()))
89
+ url = urlunsplit(
90
+ (url.scheme, alias.site.domain, url.path, url.query, url.fragment)
91
+ )
92
+ response = HttpResponsePermanentRedirect(url)
93
+ return response
94
+
95
+ def get_cache_key(self, netloc):
96
+ """Returns a cache key based on ``netloc``."""
97
+ netloc = md5_constructor(netloc.encode("utf-8"), usedforsecurity=False)
98
+ return "multisite.alias.%s.%s" % (self.key_prefix, netloc.hexdigest())
99
+
100
+ @staticmethod
101
+ def netloc_parse(netloc):
102
+ """Returns ``(host, port)`` for ``netloc`` of the form
103
+ ``'host:port'``.
104
+
105
+ If netloc does not have a port number, ``port`` will be None.
106
+ """
107
+ if ":" in netloc:
108
+ return netloc.rsplit(":", 1)
109
+ else:
110
+ return netloc, None
111
+
112
+ def get_alias(self, netloc) -> Alias | None:
113
+ """Returns Alias matching ``netloc``. Otherwise,
114
+ returns None.
115
+ """
116
+ host, port = self.netloc_parse(netloc)
117
+ try:
118
+ alias = Alias.objects.resolve(host=host, port=port)
119
+ except MultisiteInvalidHostError:
120
+ alias = None
121
+ if alias is None:
122
+ # Running under TestCase or runserver?
123
+ alias = self.get_development_alias(netloc)
124
+ return alias
125
+
126
+ @staticmethod
127
+ def fallback_view(request) -> HttpResponse:
128
+ """Runs the fallback view function in
129
+ ``settings.MULTISITE_FALLBACK``.
130
+
131
+ If ``MULTISITE_FALLBACK`` is None, raises an Http404 error.
132
+
133
+ If ``MULTISITE_FALLBACK`` is callable, will treat that
134
+ callable as a view that returns an HttpResponse.
135
+
136
+ If ``MULTISITE_FALLBACK`` is a string, will resolve it to a
137
+ view that returns an HttpResponse.
138
+
139
+ In order to use a generic view that takes additional
140
+ parameters, ``settings.MULTISITE_FALLBACK_KWARGS`` may be a
141
+ dictionary of additional keyword arguments.
142
+ """
143
+ view = get_fallback_view_or_404()
144
+ kwargs = get_fallback_kwargs()
145
+ if hasattr(view, "as_view"):
146
+ return view.as_view(**kwargs)(request)
147
+ return view(request, **kwargs)
148
+
149
+ @staticmethod
150
+ def get_development_alias(netloc) -> Alias:
151
+ """Returns valid Alias when in development mode. Otherwise,
152
+ returns None.
153
+
154
+ Development mode is either:
155
+ - Running tests, i.e. manage.py test
156
+ - Running locally in `settings.DEBUG` = True, where the
157
+ hostname is a top-level name, i.e. localhost
158
+ """
159
+ alias = None
160
+ # When running tests, django.core.mail.outbox exists and
161
+ # netloc == 'testserver'
162
+ is_testserver = hasattr(mail, "outbox") and netloc in (
163
+ "testserver",
164
+ "adminsite.com",
165
+ )
166
+ # When using runserver, assume that host will only have one path
167
+ # component. This covers 'localhost' and your machine name.
168
+ is_local_debug = settings.DEBUG and len(netloc.split(".")) == 1
169
+ if is_testserver or is_local_debug:
170
+ try:
171
+ # Prefer the default SITE_ID
172
+ site_id = settings.SITE_ID.get_default()
173
+ except ValueError:
174
+ # Fallback to the first Site object
175
+ alias = Alias.canonical.order_by("site")[0]
176
+ else:
177
+ alias = Alias.canonical.get(site=site_id)
178
+ return alias
179
+
180
+ @classmethod
181
+ def site_domain_cache_hook(cls, sender, instance, *args, **kwargs):
182
+ """Caches `Site.domain` in the object for
183
+ site_domain_changed_hook.
184
+ """
185
+ instance._domain_cache = instance.domain
186
+
187
+ def site_domain_changed_hook(self, sender, instance, raw, *args, **kwargs):
188
+ """Clears the cache if `Site.domain` has changed."""
189
+ if raw or instance.pk is None:
190
+ return
191
+
192
+ original = getattr(instance, "_domain_cache", None)
193
+ if original != instance.domain:
194
+ self.cache.clear()
195
+
196
+ def site_deleted_hook(self, *args, **kwargs):
197
+ """Clears the cache if Site was deleted."""
198
+ self.cache.clear()
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import absolute_import, unicode_literals
3
+
4
+ from django.db import migrations, models
5
+
6
+ import multisite.models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+ dependencies = [
11
+ ("sites", "0001_initial"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name="Alias",
17
+ fields=[
18
+ (
19
+ "id",
20
+ models.AutoField(
21
+ verbose_name="ID",
22
+ serialize=False,
23
+ auto_created=True,
24
+ primary_key=True,
25
+ ),
26
+ ),
27
+ (
28
+ "domain",
29
+ models.CharField(
30
+ help_text='Either "domain" or "domain:port"',
31
+ unique=True,
32
+ max_length=100,
33
+ verbose_name="domain name",
34
+ ),
35
+ ),
36
+ (
37
+ "is_canonical",
38
+ models.NullBooleanField(
39
+ default=None,
40
+ validators=[multisite.models.validators.validate_true_or_none],
41
+ editable=False,
42
+ help_text="Does this domain name match the one in site?",
43
+ verbose_name="is canonical?",
44
+ ),
45
+ ),
46
+ (
47
+ "redirect_to_canonical",
48
+ models.BooleanField(
49
+ default=True,
50
+ help_text="Should this domain name redirect to the one in site?",
51
+ verbose_name="redirect to canonical?",
52
+ ),
53
+ ),
54
+ (
55
+ "site",
56
+ models.ForeignKey(
57
+ related_name="aliases",
58
+ to="sites.Site",
59
+ on_delete=models.CASCADE,
60
+ ),
61
+ ),
62
+ ],
63
+ options={
64
+ "verbose_name_plural": "aliases",
65
+ },
66
+ ),
67
+ migrations.AlterUniqueTogether(
68
+ name="alias",
69
+ unique_together=set([("is_canonical", "site")]),
70
+ ),
71
+ ]
@@ -0,0 +1,32 @@
1
+ # Generated by Django 4.0.4 on 2022-05-12 23:39
2
+
3
+ from django.db import migrations, models
4
+
5
+ import multisite.models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ dependencies = [
10
+ ("multisite", "0001_initial"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="alias",
16
+ name="id",
17
+ field=models.BigAutoField(
18
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
19
+ ),
20
+ ),
21
+ migrations.AlterField(
22
+ model_name="alias",
23
+ name="is_canonical",
24
+ field=models.BooleanField(
25
+ default=False,
26
+ editable=False,
27
+ help_text="Does this domain name match the one in site?",
28
+ validators=[multisite.models.validators.validate_true_or_none],
29
+ verbose_name="is canonical?",
30
+ ),
31
+ ),
32
+ ]