Ticket #15089: 15089_patch_b.diff
File 15089_patch_b.diff, 40.4 KB (added by , 14 years ago) |
---|
-
django/conf/project_template/settings.py
33 33 # http://www.i18nguy.com/unicode/language-identifiers.html 34 34 LANGUAGE_CODE = 'en-us' 35 35 36 # Specify an overriding SITE_ID to statically determine your current site. 37 # If this is not specified, the sites framwork will use the data on the request. 38 # If this is specified, the sites framework will ignore the data on the request. 36 39 SITE_ID = 1 37 40 38 41 # If you set this to False, Django will make some optimizations so as not -
django/contrib/sites/managers.py
1 from django.conf import settings2 1 from django.db import models 3 2 from django.db.models.fields import FieldDoesNotExist 3 from django.contrib.sites.models import Site 4 4 5 5 class CurrentSiteManager(models.Manager): 6 6 "Use this to limit objects to those associated with the current site." … … 38 38 def get_query_set(self): 39 39 if not self.__is_validated: 40 40 self._validate_field_name() 41 return super(CurrentSiteManager, self).get_query_set().filter(**{self.__field_name + '__id__exact': settings.SITE_ID}) 41 return super(CurrentSiteManager, self).get_query_set().filter( 42 **{self.__field_name: Site.objects.get_current()} 43 ) -
django/contrib/sites/middleware.py
1 from django.conf import settings 2 from django.utils.cache import patch_vary_headers 3 from django.contrib.sites.models import get_current_site, Site, RequestSite, SITE_CACHE 4 5 6 class LazySite(object): 7 """ 8 A lazy site object that refers to either Site instance or 9 a case insensitive RequestSite. 10 """ 11 def __get__(self, request, obj_type=None): 12 if not hasattr(request, '_cached_site'): 13 request._cached_site = get_current_site(request) 14 return request._cached_site 15 16 17 class SitesMiddleware(object): 18 19 def process_request(self, request): 20 if not hasattr(request.__class__, 'site'): 21 request.__class__.site = LazySite() 22 return None 23 24 def process_response(self, request, response): 25 """ 26 Forces the HTTP Vary header onto requests to avoid having responses 27 cached from incorrect urlconfs. 28 29 If you'd like to disable this for some reason, set `FORCE_VARY_ON_HOST` 30 in your Django settings file to `False`. 31 """ 32 if getattr(settings, 'SITES_VARY_ON_HOST', True): 33 patch_vary_headers(response, ('Host',)) 34 return response 35 -
django/contrib/sites/__init__.py
1 2 from django.contrib.sites.models import get_current_site -
django/contrib/sites/tests.py
1 1 from django.conf import settings 2 2 from django.contrib.sites.models import Site, RequestSite, get_current_site 3 from django.core.exceptions import ObjectDoesNotExist3 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist 4 4 from django.http import HttpRequest 5 5 from django.test import TestCase 6 6 7 NONE_VAL = object() 7 8 9 class TestSite(): 10 def __init__(self): 11 self.domain = "domain" 12 self.name = "name" 13 self.save = lambda x: x 14 self.delete = lambda x: x 15 8 16 class SitesFrameworkTests(TestCase): 9 17 10 18 def setUp(self): 11 Site(id=settings.SITE_ID, domain="example.com", name="example.com").save() 19 self.old_site_id = settings.SITE_ID if hasattr(settings, "SITE_ID") else NONE_VAL 20 settings.SITE_ID = 1 21 Site(id=settings.SITE_ID, domain="staticsite.com", name="staticsite.com").save() 22 Site(domain="dynamicsite.com", name="dynamicsite.com").save() 12 23 self.old_Site_meta_installed = Site._meta.installed 13 24 Site._meta.installed = True 14 25 15 26 def tearDown(self): 16 27 Site._meta.installed = self.old_Site_meta_installed 28 if self.old_site_id is not NONE_VAL: 29 settings.SITE_ID = self.old_site_id 30 else: 31 del settings.SITE_ID 17 32 18 33 def test_site_manager(self): 19 34 # Make sure that get_current() does not return a deleted Site object. … … 26 41 # After updating a Site object (e.g. via the admin), we shouldn't return a 27 42 # bogus value from the SITE_CACHE. 28 43 site = Site.objects.get_current() 29 self.assertEqual(u" example.com", site.name)44 self.assertEqual(u"staticsite.com", site.name) 30 45 s2 = Site.objects.get(id=settings.SITE_ID) 31 46 s2.name = "Example site" 32 47 s2.save() … … 34 49 self.assertEqual(u"Example site", site.name) 35 50 36 51 def test_get_current_site(self): 52 del settings.SITE_ID 53 37 54 # Test that the correct Site object is returned 38 55 request = HttpRequest() 39 56 request.META = { 40 "SERVER_NAME": " example.com",57 "SERVER_NAME": "dynamicsite.com", 41 58 "SERVER_PORT": "80", 42 59 } 43 60 site = get_current_site(request) 44 61 self.assert_(isinstance(site, Site)) 45 self.assertEqual(site.id, settings.SITE_ID) 62 self.assertEqual(site.id, self.old_site_id+1) 63 self.assertEqual(site.domain, "dynamicsite.com") 46 64 47 65 # Test that an exception is raised if the sites framework is installed 48 66 # but there is no matching Site 49 67 site.delete() 50 68 self.assertRaises(ObjectDoesNotExist, get_current_site, request) 51 69 70 settings.SITE_ID = 1 71 72 # by setting settings.SITE_ID, the requset-driven lookup is overridden 73 site = get_current_site(request) 74 self.assert_(isinstance(site, Site)) 75 self.assertEqual(site.id, self.old_site_id) 76 self.assertEqual(site.domain, "staticsite.com") 77 78 del settings.SITE_ID 79 80 request = HttpRequest() 81 request.META = { 82 "SERVER_NAME": "staticsite.com", 83 "SERVER_PORT": "80", 84 } 85 site = get_current_site(request) 86 self.assert_(isinstance(site, Site)) 87 self.assertEqual(site.id, self.old_site_id) 88 self.assertEqual(site.domain, "staticsite.com") 89 90 # Test that an exception is raised if the sites framework is installed 91 # but there is no matching Site 92 site.delete() 93 self.assertRaises(ObjectDoesNotExist, get_current_site, request) 94 95 # now, setting the SITE_ID should have no effect 96 settings.SITE_ID = 1 97 self.assertRaises(ObjectDoesNotExist, get_current_site, request) 98 52 99 # A RequestSite is returned if the sites framework is not installed 53 100 Site._meta.installed = False 54 101 site = get_current_site(request) 55 102 self.assert_(isinstance(site, RequestSite)) 56 self.assertEqual(site.name, u"example.com") 103 self.assertEqual(site.name, u"staticsite.com") 104 105 def test_site_callback(self): 106 request = HttpRequest() 107 108 # baseline control 109 ts = TestSite() 110 settings.SITE_CALLBACK = lambda request, require_site_obj: ts 111 self.assertEqual(get_current_site(request), ts) 112 113 # test not an ORM Site 114 ts = TestSite() 115 settings.SITE_CALLBACK = lambda request, require_site_obj: ts 116 self.assertRaises(ImproperlyConfigured, get_current_site, request, True) 117 118 # test missing domain 119 ts = TestSite() 120 del ts.domain 121 settings.SITE_CALLBACK = lambda request, require_site_obj: ts 122 self.assertRaises(ImproperlyConfigured, get_current_site, request) 123 124 # test missing name 125 ts = TestSite() 126 del ts.name 127 settings.SITE_CALLBACK = lambda request, require_site_obj: ts 128 self.assertRaises(ImproperlyConfigured, get_current_site, request) 129 130 # test missing save 131 ts = TestSite() 132 del ts.save 133 settings.SITE_CALLBACK = lambda request, require_site_obj: ts 134 self.assertRaises(ImproperlyConfigured, get_current_site, request) 135 136 # test missing delete 137 ts = TestSite() 138 del ts.delete 139 settings.SITE_CALLBACK = lambda request, require_site_obj: ts 140 self.assertRaises(ImproperlyConfigured, get_current_site, request) 141 142 del settings.SITE_CALLBACK -
django/contrib/sites/models.py
1 1 from django.db import models 2 2 from django.utils.translation import ugettext_lazy as _ 3 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist 3 4 4 5 5 6 SITE_CACHE = {} … … 7 8 8 9 class SiteManager(models.Manager): 9 10 11 def get_site_by_request(self, request): 12 host = request.get_host().lower() 13 if ':' in host: 14 host, _ = host.split(':', 1) 15 return self.get(domain__iexact=host) 16 17 def get_site_by_id(self, id): 18 site = SITE_CACHE.get(id, None) 19 if site is None: 20 site = self.get(pk=id) 21 SITE_CACHE[id] = site 22 return site 23 10 24 def get_current(self): 11 25 """ 12 26 Returns the current ``Site`` based on the SITE_ID in the 13 27 project's settings. The ``Site`` object is cached the first 14 28 time it's retrieved from the database. 15 29 """ 30 from warnings import warn 31 warn( 32 "The SiteManager.get_current() method is deprecated in favor of the " 33 "get_current_site() function.", DeprecationWarning 34 ) 35 16 36 from django.conf import settings 17 37 try: 18 38 sid = settings.SITE_ID 19 39 except AttributeError: 20 from django.core.exceptions import ImproperlyConfigured 21 raise ImproperlyConfigured("You're using the Django \"sites framework\" without having set the SITE_ID setting. Create a site in your database and set the SITE_ID setting to fix this error.") 22 try: 23 current_site = SITE_CACHE[sid] 24 except KeyError: 25 current_site = self.get(pk=sid) 26 SITE_CACHE[sid] = current_site 27 return current_site 40 raise ImproperlyConfigured( 41 "You're using the Django \"sites framework\" without having set " 42 "the SITE_ID setting. Create a site in your database and set " 43 "the SITE_ID setting to fix this error." 44 ) 45 return self.get_site_by_id(sid) 28 46 29 47 def clear_cache(self): 30 48 """Clears the ``Site`` object cache.""" … … 34 52 35 53 class Site(models.Model): 36 54 37 domain = models.CharField(_('domain name'), max_length=100 )55 domain = models.CharField(_('domain name'), max_length=100, unique=True) 38 56 name = models.CharField(_('display name'), max_length=50) 39 57 objects = SiteManager() 40 58 … … 71 89 The save() and delete() methods raise NotImplementedError. 72 90 """ 73 91 def __init__(self, request): 74 self.domain = self.name = request.get_host() 92 self.domain = self.name = request.get_host().lower() 75 93 76 94 def __unicode__(self): 77 95 return self.domain … … 82 100 def delete(self): 83 101 raise NotImplementedError('RequestSite cannot be deleted.') 84 102 103 def __repr__(self): 104 return "<RequestSite: %s, %s>" % (self.domain, self.name) 85 105 86 def get_current_site(request): 106 107 ERROR_MSG_1 = "settings.SITE_CALLBACK must return an object with a '%s' field defined. " 108 ERROR_MSG_2 = "settings.SITE_CALLBACK must return an object with a '%s' method defined. " 109 110 def get_current_site(request, require_site_object=False): 87 111 """ 88 112 Checks if contrib.sites is installed and returns either the current 89 113 ``Site`` object or a ``RequestSite`` object based on the request. 90 114 """ 91 if Site._meta.installed: 92 current_site = Site.objects.get_current() 115 from django.conf import settings 116 117 if hasattr(settings, 'SITE_CALLBACK') and settings.SITE_CALLBACK is not None: 118 if callable(settings.SITE_CALLBACK): 119 site = settings.SITE_CALLBACK(request, require_site_object) 120 121 # validate returned object to guarantee contract 122 if site is None: 123 pass # if the settings.SITE_CALLBACK returns None, then use other methods 124 elif require_site_object and not site.__class__ is Site: 125 raise ImproperlyConfigured( 126 'settings.SITE_CALLBACK must return a Site object when ' 127 'require_site_object argument is True.' 128 ) 129 elif not hasattr(site, 'domain'): 130 raise ImproperlyConfigured(ERROR_MSG_1 % 'domain') 131 elif not hasattr(site, 'name'): 132 raise ImproperlyConfigured(ERROR_MSG_1 % 'name') 133 elif not hasattr(site, 'save') or not callable(site.save): 134 raise ImproperlyConfigured(ERROR_MSG_2 % 'save') 135 elif not hasattr(site, 'delete') or not callable(site.delete): 136 raise ImproperlyConfigured(ERROR_MSG_2 % 'delete') 137 else: 138 return site 139 else: 140 raise ImproperlyConfigured( 141 'Settings contains a SITE_CALLBACK value which is not callable.' 142 ) 143 144 if not Site._meta.installed: 145 if not require_site_object: 146 return RequestSite(request) 147 else: 148 raise ImproperlyConfigured( 149 'The Django "sites framework" is being used somewhere in ' 150 'your project in a manner that requires it to be added ' 151 'to INSTALLED_APPS in your settings file.' 152 ) 153 154 elif hasattr(settings, 'SITE_ID'): 155 return Site.objects.get_site_by_id(settings.SITE_ID) 156 93 157 else: 94 current_site = RequestSite(request) 95 return current_site 158 return Site.objects.get_site_by_request(request) -
django/contrib/sites/decorators.py
1 from django.utils.decorators import decorator_from_middleware 2 3 from django.contrib.sites.middleware import SitesMiddleware 4 5 site_aware = decorator_from_middleware(SitesMiddleware) 6 site_aware.__name__ = "site_aware" 7 site_aware.__doc__ = """ 8 This decorator adds a LazySite instance to the request in exactly the same 9 way as the SitesMiddleware, but it can be used on a per view basis. 10 """ 11 -
tests/regressiontests/sites_framework/tests.py
1 1 from django.conf import settings 2 2 from django.contrib.sites.models import Site 3 3 from django.test import TestCase 4 from django.test.client import Client 5 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist 4 6 7 5 8 from models import SyndicatedArticle, ExclusiveArticle, CustomArticle, InvalidArticle, ConfusedArticle 6 9 7 10 class SitesFrameworkTestCase(TestCase): 8 11 def setUp(self): 9 12 Site.objects.get_or_create(id=settings.SITE_ID, domain="example.com", name="example.com") 10 13 Site.objects.create(id=settings.SITE_ID+1, domain="example2.com", name="example2.com") 14 self.old_site_id = settings.SITE_ID 11 15 16 def tearDown(self): 17 settings.SITE_ID = self.old_site_id 18 12 19 def test_site_fk(self): 13 20 article = ExclusiveArticle.objects.create(title="Breaking News!", site_id=settings.SITE_ID) 14 21 self.assertEqual(ExclusiveArticle.on_site.all().get(), article) … … 32 39 def test_invalid_field_type(self): 33 40 article = ConfusedArticle.objects.create(title="More Bad News!", site=settings.SITE_ID) 34 41 self.assertRaises(TypeError, ConfusedArticle.on_site.all) 42 43 def test_middleware(self): 44 #setup meta installed 45 old_meta_installed = Site._meta.installed 46 Site._meta.installed = False 47 48 #baseline control 49 c = Client() 50 extra = { "SERVER_NAME": "example3.com", 51 "SERVER_PORT": "80" } 52 53 result = c.get("/sites/", **extra) 54 self.assertEquals(result.content, "") 55 56 #decorator, using RequestSite 57 result = c.get("/sites/decorated/", **extra) 58 self.assertEquals(result.content, "example3.com") 59 60 #set up middleware 61 middleware_str = 'django.contrib.sites.middleware.SitesMiddleware' 62 old_middleware_setting = settings.MIDDLEWARE_CLASSES 63 if middleware_str not in settings.MIDDLEWARE_CLASSES: 64 settings.MIDDLEWARE_CLASSES += (middleware_str,) 65 old_site_id = settings.SITE_ID 66 del settings.SITE_ID 67 c = Client() 68 Site._meta.installed = True 69 70 #retrieve by request 71 extra["SERVER_NAME"] = "example2.com" 72 result = c.get("/sites/", **extra) 73 self.assertEquals(result.content, "example2.com") 74 75 #retrieve default, no site found 76 extra["SERVER_NAME"] = "example3.com" 77 result = c.get("/sites/", **extra) 78 # this is empty, instead of raising an exception, because 79 # ObjectDoesNotExist has a "silent_variable_failure" field 80 # set to True, which the template engine replaces with "" 81 self.assertEquals(result.content, "") 82 83 #retrieve by site id 84 settings.SITE_ID = old_site_id 85 extra["SERVER_NAME"] = "example2.com" 86 result = c.get("/sites/", **extra) 87 self.assertEquals(result.content, "example.com") 88 89 #retrieve default, no site found 90 extra["SERVER_NAME"] = "example3.com" 91 result = c.get("/sites/", **extra) 92 self.assertEquals(result.content, "example.com") 93 94 #use RequestSite 95 Site._meta.installed = False 96 extra["SERVER_NAME"] = "example3.com" 97 result = c.get("/sites/", **extra) 98 self.assertEquals(result.content, "example3.com") 99 100 #reset to original values 101 Site._meta.installed = old_meta_installed 102 settings.MIDDLEWARE_CLASSES = old_middleware_setting 103 -
tests/urls.py
41 41 42 42 # special headers views 43 43 (r'special_headers/', include('regressiontests.special_headers.urls')), 44 45 # sites middleware tests 46 (r'sites/', include('regressiontests.sites_framework.urls')), 44 47 ) -
docs/ref/contrib/sites.txt
6 6 :synopsis: Lets you operate multiple Web sites from the same database and 7 7 Django project 8 8 9 .. versionchanged:: 1.3 10 11 .. note:: 12 Prior to Django 1.3, django.contrib.sites relied heavily on settings.SITE_ID 13 as the only manner in which to determine the "current site". Now, with 14 Django 1.3, settings.SITE_ID is only one possible way to determine the 15 "current site", with the preferred method being an inspection of the incomming 16 request. 17 18 For this reason, the `Site.objects.get_current()` manager method has been 19 depricated in favor of `django.contrib.sites.get_current_site`. settings.SITE_ID, 20 which used to be a mandatory setting for django.contrib.sites, is now optional. 21 9 22 Django comes with an optional "sites" framework. It's a hook for associating 10 23 objects and functionality to particular Web sites, and it's a holding place for 11 24 the domain names and "verbose" names of your Django-powered sites. 12 25 13 Use it if your single Django installation powers more than one site and you 14 need to differentiate between those sites in some way. 26 This framework has a number of uses, but its primary use is to implement a 27 standard approach to "multitenancy", whereby a single instance or installation 28 of Django can be used to power more than one site. In other words, the sites 29 framework will allow you to differentiate between distinct sites in your 30 application. Django iteslf uses the sites framework in a couple of ways in 31 the contrib apps, automatically via simple conventions. 15 32 16 The whole sites framework is based on a simple model: 33 The "sites" framework centralizes control in a single function, which 34 should act as the entry point for anyone using the library: 17 35 18 .. class:: django.contrib.sites.models.Site36 .. :func:`~django.contrib.sites.get_current_site` 19 37 20 This model has :attr:`~django.contrib.sites.models.Site.domain` and 21 :attr:`~django.contrib.sites.models.Site.name` fields. The :setting:`SITE_ID` 22 setting specifies the database ID of the 23 :class:`~django.contrib.sites.models.Site` object associated with that 24 particular settings file. 38 This function takes a request as a parameter, and returns an object 39 representing the current site. The returned object may be user-defined, 40 or it may be one of two types of object that the sites framework itself 41 implements. Regardless of what specific type of object is returned, however, 42 a `domain` field, corresponding to the domain name of the site, and a `name` 43 field, which is a verbose name that describes the site, will always be defined. 44 Non-ORM objects that are returned are also required to implement mock `save` 45 and `delete` methods, to maintain API compatibilty with the Site model. 25 46 26 How you use this is up to you, but Django uses it in a couple of ways27 automatically via simple conventions. 47 How ``get_current_site`` Works 48 ---------------------------- 28 49 50 .. versionchanged:: 1.3 51 52 The return value of ``get_current_site`` can be determined by any of four 53 things (in order of precidence): 54 55 * the optional SITE_CALLBACK setting, which should reference a callable 56 with the same signature and contract as ``get_current_site`` iteslf-- 57 specifically, it must specify ``request`` and ``require_site_object`` as 58 parameters, and return an object with members ``domain``, ``name``, 59 ``save``, and ``delete`` 60 61 * whether or not the ``contrib.sites`` app is installed in your project, 62 i.e. referenced inside the INSTALLED_APPS list in settings; in the 63 event that ``contrib.sites`` is not installed, a non-ORM RequestSite 64 object is returned 65 66 * the optional SITE_ID setting, which points to an overriding Site 67 database record. 68 69 * the hostname associated with the inbound http request, which will 70 be used to retrieve a Site ORM object from the database. 71 72 The return value of SITE_CALLBACK can be any user-specified object. However, 73 it is important to note that whenever the ``require_site_object`` argument is 74 passed in as ``True`` (the default is ``False``), the object returned must 75 be a django.contrib.sites.models.Site object. 76 77 Similarly, whenever ``contrib.sites`` is not installed in your project, a 78 RequestSite object will be returned instead of a Site object. However, 79 if the ``require_site_object`` argument is passed in as ``True`` then failing 80 to include the sites framework in your INSTALLED_APPS will cause an 81 ImproperlyConfigured exception to be raised. 82 83 Note that the SITE_ID setting acts to override whatever host might be specified 84 in the request. The reason for this override behavior is explained in the section 85 below titled "Static Multitenancy". 86 87 If the last step in the above precidence chain is reached and no Site object 88 is found for the incomming request, then the ``get_current_site()`` function 89 returns None. 90 29 91 Example usage 30 92 ============= 31 93 … … 74 136 75 137 def article_detail(request, article_id): 76 138 try: 77 a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID) 139 site = get_current_site(request, require_site_object=True) 140 a = Article.objects.get(id=article_id, sites=site) 78 141 except Article.DoesNotExist: 79 142 raise Http404 80 143 # ... 81 144 145 Note that because the ``Article`` model relates specifically to the ``Site`` 146 model, ``get_current_site()`` is called with the require_site_object parameter 147 set to True. If this value were not specified, there would be a risk that 148 a ``RequestSite`` or even perhaps a user-defined site object being returned, 149 which would cause a failure in the ORM when the above query were run. 150 82 151 .. _ljworld.com: http://www.ljworld.com/ 83 152 .. _lawrence.com: http://www.lawrence.com/ 84 153 … … 100 169 # ... 101 170 site = models.ForeignKey(Site) 102 171 103 This has the same benefits as described in the last section.172 This has the same benefits as described in the next section. 104 173 105 174 .. _hooking-into-current-site-from-views: 106 175 107 176 Hooking into the current site from views 108 177 ---------------------------------------- 109 178 179 .. versionchanged:: 1.3 180 110 181 You can use the sites framework in your Django views to do 111 182 particular things based on the site in which the view is being called. 112 183 For example:: 113 184 114 185 from django.conf import settings 186 from django.contrib.sites import get_current_site 115 187 116 188 def my_view(request): 117 if settings.SITE_ID == 3: 118 # Do something. 119 else: 120 # Do something else. 121 122 Of course, it's ugly to hard-code the site IDs like that. This sort of 123 hard-coding is best for hackish fixes that you need done quickly. A slightly 124 cleaner way of accomplishing the same thing is to check the current site's 125 domain:: 126 127 from django.conf import settings 128 from django.contrib.sites.models import Site 129 130 def my_view(request): 131 current_site = Site.objects.get(id=settings.SITE_ID) 189 current_site = get_current_site(request) 132 190 if current_site.domain == 'foo.com': 133 191 # Do something 134 192 else: 135 193 # Do something else. 136 194 137 The idiom of retrieving the :class:`~django.contrib.sites.models.Site` object 138 for the value of :setting:`settings.SITE_ID <SITE_ID>` is quite common, so 139 the :class:`~django.contrib.sites.models.Site` model's manager has a 140 ``get_current()`` method. This example is equivalent to the previous one:: 195 For code that relies on getting the current domain but cannot be certain 196 that the sites framework will be installed for any given project, the 197 :func:`~django.contrib.sites.get_current_site` function will return either 198 a Site instance (if the sites framework is installed) or a RequestSite 199 instance (if it is not). This allows loose coupling with the sites framework 200 and provides a usable fallback for cases where it is not installed. 141 201 142 from django.contrib.sites.models import Site 202 Additionally, you can implement your own version of ``get_current_site()`` 203 and assign it to settings.SITE_CALLBACK in order to override all ``get_current_site()`` 204 behavior. See the section "How ``get_current_site`` Works", above. 143 205 144 def my_view(request): 145 current_site = Site.objects.get_current() 146 if current_site.domain == 'foo.com': 147 # Do something 148 else: 149 # Do something else. 206 Getting the current domain for display 207 -------------------------------------- 150 208 151 209 .. versionchanged:: 1.3 152 210 153 For code which relies on getting the current domain but cannot be certain154 that the sites framework will be installed for any given project, there is a155 utility function :func:`~django.contrib.sites.models.get_current_site` that156 takes a request object as an argument and returns either a Site instance (if157 the sites framework is installed) or a RequestSite instance (if it is not).158 This allows loose coupling with the sites framework and provides a usable159 fallback for cases where it is not installed.160 161 Getting the current domain for display162 --------------------------------------163 164 211 LJWorld.com and Lawrence.com both have e-mail alert functionality, which lets 165 212 readers sign up to get notifications when news happens. It's pretty basic: A 166 213 reader signs up on a Web form, and he immediately gets an e-mail saying, … … 168 215 169 216 It'd be inefficient and redundant to implement this signup-processing code 170 217 twice, so the sites use the same code behind the scenes. But the "thank you for 171 signing up" notice needs to be different for each site. By using 172 :class:`~django.contrib.sites.models.Site` 173 objects, we can abstract the "thank you" notice to use the values of the 174 current site's :attr:`~django.contrib.sites.models.Site.name` and 175 :attr:`~django.contrib.sites.models.Site.domain`. 218 signing up" notice needs to be different for each site. By using site objects, 219 we can abstract the "thank you" notice to use the values of whatever object 220 is returned by :func:`~django.contrib.sites.get_current_site`, specifically 221 ``name`` and ``domain``. 176 222 177 223 Here's an example of what the form-handling view looks like:: 178 224 179 from django.contrib.sites .models import Site225 from django.contrib.sites import get_current_site 180 226 from django.core.mail import send_mail 181 227 182 228 def register_for_newsletter(request): 183 229 # Check form values, etc., and subscribe the user. 184 230 # ... 185 231 186 current_site = Site.objects.get_current()232 current_site = get_current_site(request) 187 233 send_mail('Thanks for subscribing to %s alerts' % current_site.name, 188 234 'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name, 189 235 'editor@%s' % current_site.domain, … … 195 241 lawrence.com alerts." On LJWorld.com, the e-mail has the subject "Thanks for 196 242 subscribing to LJWorld.com alerts." Same goes for the e-mail's message body. 197 243 198 Note that an even more flexible (but more heavyweight) way of doing this would 199 be to use Django's template system. Assuming Lawrence.com and LJWorld.com have 200 different template directories (:setting:`TEMPLATE_DIRS`), you could simply farm out 201 to the template system like so:: 244 It's a good idea to exploit the sites objects returned by 245 :func:`~django.contrib.sites.get_current_site` objects as much as possible, 246 to remove unneeded complexity and redundancy. 202 247 248 Getting the current domain for full URLs 249 ---------------------------------------- 250 251 .. versionchanged:: 1.3 252 253 Django's ``get_absolute_url()`` convention is nice for getting your objects' 254 URL without the domain name, but in some cases you might want to display the 255 full URL -- with ``http://`` and the domain and everything -- for an object. 256 To do this, you can use the sites framework. A simple example:: 257 258 from django.contrib.sites import get_current_site 259 from django.shortcuts import render 260 from myapp.models import MyModel 261 262 def view_with_full_urls(request, obj_id): 263 obj = MyModel.objects.get(id=obj_id) 264 partial_url = obj.get_absolute_url() 265 domain = get_current_site(request).domain 266 267 context_data = { "full_url" : 'http://%s%s' % (domain, partial_url) } 268 return render(request, "my_template.html", context_data) 269 270 Static Multitenancy vs. Dynamic Multitenancy 271 ============================================ 272 273 .. versionadded:: 1.3 274 275 As per 'wikipedia'_: 276 277 "Multitenancy refers to a principle in software architecture where a single instance 278 of the software runs on a server, serving multiple client organizations (tenants). 279 Multitenancy is contrasted with a multi-instance architecture where separate software 280 instances (or hardware systems) are set up for different client organizations." 281 282 Django implements two forms of multitenancy, what is referred to in this document 283 as "static" (or partial) multitenancy and "dynamic" (or full) multitenancy. 284 285 Static multitenancy is distinguished by its deployment architecture: a common 286 database installation is shared by N runtime instances of Django, all running off of 287 the same codebase, but each instance differing in its localized configuration. 288 Two instances operating in this fasion might differ, for example, only in the templates 289 installed in their respective template directories. Each instance would be 290 affiliated with only a single ``Site``, configured using settings.SITE_ID, 291 and would access specific site-affiliated data only affilitated with that ``Site``. 292 In such a configuration, to change the ``Site`` with which an instance is affiliated 293 would require restarging that instance and possibly reconfiguring the webserver. 294 295 Revisit for a moment the case of Lawrence.com and LJWorld.com's email notification system. 296 Using Django's template system, each website can send out notifications that contain 297 unique layout and design elements, in addition to unique content. All that a static 298 multitenant setup requires to enable this behavior is that the TEMPLATE_DIRS setting in 299 each instance differ, and that :file:`subject.txt` and :file:`message.txt` template 300 files exist in both the LJWorld.com and Lawrence.com template directories. 301 302 The view code itself would be the same, however, in both runtime instances, and would 303 reference the same database: 304 203 305 from django.core.mail import send_mail 204 306 from django.template import loader, Context 205 307 … … 208 310 # ... 209 311 210 312 subject = loader.get_template('alerts/subject.txt').render(Context({})) 211 message = loader.get_template('alerts/message. txt').render(Context({}))313 message = loader.get_template('alerts/message.html').render(Context({})) 212 314 send_mail(subject, message, 'editor@ljworld.com', [user.email]) 213 315 214 316 # ... 215 317 216 In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` template 217 files for both the LJWorld.com and Lawrence.com template directories. That 218 gives you more flexibility, but it's also more complex. 318 Typically, a static multitenant implementation involves a small and relatively static 319 set of sites running off a given codebase. In a dynamic multitenant system, however, 320 the set of tenant sites changes often and should be managable via the database without 321 the need for differing instance configurations. 219 322 220 It's a good idea to exploit the :class:`~django.contrib.sites.models.Site` 221 objects as much as possible, to remove unneeded complexity and redundancy. 323 In a dynamic multitnant system, all instances are identical. Instead of relying on a 324 global settings.SITE_ID value to determine the current site for the whole instance, 325 each request is determined to have a "current site" based on the host it specifies. 326 The requirement to have the ``request`` on hand in every situation where the "current 327 site" is to be retrieved does place constraints on a system's design. Furthermore, 328 dynamic multitenant systems cannot take advantage of static configuration differences 329 (such as in the above example) in order to provide differing functionality. All 330 multitenant behavior must be handled programatically. Even with these limitations, 331 however, dynamic multitenancy is superior to static multitenency in its flexibility, 332 and its ability to scale to a large number of tenants. 222 333 223 Getting the current domain for full URLs 224 ---------------------------------------- 334 ...note:: 335 Prior to Django version 1.3, only "static multitenancy" was officially supported, 336 and as a result at various places in the Django source code settings.SITE_ID 337 was used to retrieve the "current site" object without reference to the 338 incomming request. Prior versions of this documentation encouraged such an approach. 339 With the introduction of `get_current_site` as the standard entry-point to the 340 sites framework, however, direct use of settings.SITE_ID to retrieve the current 341 site, or any attempt to retrieve the current site inside the request-response 342 loop without reference to the incomming request, has been deprecated. 343 344 Care should be taken in implementing your multitenant system to always have the 345 recent request on hand wherever you might wish to retrieve the current site. Always 346 use ``get_current_site`` in every such circumstance, even if you are only 347 implementing static multitenancy, in order to ensure that your application is 348 compatible with both forms of multitenancy should you wish to shift your 349 implementation in the future, and to avoid lock-in. 225 350 226 Django's ``get_absolute_url()`` convention is nice for getting your objects' 227 URL without the domain name, but in some cases you might want to display the 228 full URL -- with ``http://`` and the domain and everything -- for an object. 229 To do this, you can use the sites framework. A simple example:: 351 .. _wikipedia: http://en.wikipedia.org/wiki/Multitenancy 230 352 231 >>> from django.contrib.sites.models import Site232 >>> obj = MyModel.objects.get(id=3)233 >>> obj.get_absolute_url()234 '/mymodel/objects/3/'235 >>> Site.objects.get_current().domain236 'example.com'237 >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())238 'http://example.com/mymodel/objects/3/'239 240 353 Caching the current ``Site`` object 241 354 =================================== 242 355 356 .. versionchanged:: 1.3 357 243 358 As the current site is stored in the database, each call to 244 ``Site.objects.get_current()`` could result in a database query. But Django is a 245 little cleverer than that: on the first request, the current site is cached, and 246 any subsequent call returns the cached data instead of hitting the database. 359 :func:`~django.contrib.sites.get_current_site` could result in a database query. 360 But Django helps reduce this burden when a ``Site`` object's id (such as 361 settings.SITE_ID) is being used to retrieve the Site record. On the first request, 362 the current site is cached, and any subsequent call returns the cached data instead 363 of hitting the database. 247 364 248 365 If for any reason you want to force a database query, you can tell Django to 249 clear the cache using ``Site.objects.clear_cache()``: :366 clear the cache using ``Site.objects.clear_cache()``: 250 367 251 368 # First call; current site fetched from database. 252 current_site = Site.objects.get_current()369 current_site = get_current_site(request) 253 370 # ... 254 371 255 # Second call; current site fetched from cache .256 current_site = Site.objects.get_current()372 # Second call; current site fetched from cache, as long as SITE_ID is defined. 373 current_site = get_current_site(request) 257 374 # ... 258 375 259 376 # Force a database query for the third call. 260 377 Site.objects.clear_cache() 261 current_site = Site.objects.get_current()378 current_site = get_current_site(request) 262 379 263 380 The ``CurrentSiteManager`` 264 381 ========================== 265 382 383 ..note:: 384 The ``CurrentSiteManager`` is deprecated in version 1.3, due to its 385 reliance on settings.SITE_ID as the sole manner it uses to determing 386 the current site. Similar functionality can be achived by using 387 ``get_current_site`` in your views. 388 266 389 .. class:: django.contrib.sites.managers.CurrentSiteManager 267 390 268 391 If :class:`~django.contrib.sites.models.Site` plays a key role in your … … 401 524 :class:`~django.contrib.sites.models.RequestSite` class, which can be used as a 402 525 fallback when the database-backed sites framework is not available. 403 526 404 A :class:`~django.contrib.sites.models.RequestSite` object has a similar 405 interface to a normal :class:`~django.contrib.sites.models.Site` object, except 406 its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an 527 The :class:`~django.contrib.sites.models.RequestSite` object, referred to at 528 various places above, has a similar interface to a normal 529 :class:`~django.contrib.sites.models.Site` object, except its 530 :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an 407 531 :class:`~django.http.HttpRequest` object. It's able to deduce the 408 532 :attr:`~django.contrib.sites.models.RequestSite.domain` and 409 533 :attr:`~django.contrib.sites.models.RequestSite.name` by looking at the … … 411 535 and :meth:`~django.contrib.sites.models.RequestSite.delete()` methods to match 412 536 the interface of :class:`~django.contrib.sites.models.Site`, but the methods 413 537 raise :exc:`NotImplementedError`. 538 539 As noted above, if your application cannot handle a ``RequestSite`` object, for 540 instance because you need a ``Site`` object to use in a foreign key or many to many 541 database relation, you can call ``get_current_site`` with the ``require_site_object`` 542 keyword argument set to ``True``. In such a case, if the sites framework is 543 not installed, an exception will be raised.