diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 459a42febb..6e07d45568 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -16,7 +16,7 @@ from django.contrib.auth import authenticate, get_user_model from django.contrib.auth.models import User from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher from django.contrib.auth.tokens import default_token_generator -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site UNMASKED_DIGITS_TO_SHOW = 6 diff --git a/django/contrib/auth/tests/test_views.py b/django/contrib/auth/tests/test_views.py index 6d841c287e..d2910886a9 100644 --- a/django/contrib/auth/tests/test_views.py +++ b/django/contrib/auth/tests/test_views.py @@ -4,7 +4,8 @@ import os import re from django.conf import global_settings, settings -from django.contrib.sites.models import Site, RequestSite +from django.contrib.sites.models import Site +from django.contrib.sites.requests import RequestSite from django.contrib.admin.models import LogEntry from django.contrib.auth.models import User from django.core import mail diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 013ed9f3e2..bcb4b6a733 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -15,7 +15,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm from django.contrib.auth.tokens import default_token_generator -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site @sensitive_post_parameters() diff --git a/django/contrib/comments/feeds.py b/django/contrib/comments/feeds.py index ec2d4cdbbc..aa98e63ec9 100644 --- a/django/contrib/comments/feeds.py +++ b/django/contrib/comments/feeds.py @@ -1,5 +1,5 @@ from django.contrib.syndication.views import Feed -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.contrib import comments from django.utils.translation import ugettext as _ diff --git a/django/contrib/comments/moderation.py b/django/contrib/comments/moderation.py index 03c3583e68..504b8be3b2 100644 --- a/django/contrib/comments/moderation.py +++ b/django/contrib/comments/moderation.py @@ -62,7 +62,7 @@ from django.contrib.comments import signals from django.db.models.base import ModelBase from django.template import Context, loader from django.contrib import comments -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.utils import timezone class AlreadyModerated(Exception): diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py index 2943a83095..6ee34adb4a 100644 --- a/django/contrib/contenttypes/tests.py +++ b/django/contrib/contenttypes/tests.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.views import shortcut -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.db import models from django.http import HttpRequest, Http404 from django.test import TestCase, override_settings diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index 7318cf4203..22df1444a1 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals from django import http from django.contrib.contenttypes.models import ContentType -from django.contrib.sites.models import Site, get_current_site +from django.contrib.sites.models import Site +from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext as _ diff --git a/django/contrib/flatpages/templatetags/flatpages.py b/django/contrib/flatpages/templatetags/flatpages.py index 5e7e9bdfe5..4b036e92bb 100644 --- a/django/contrib/flatpages/templatetags/flatpages.py +++ b/django/contrib/flatpages/templatetags/flatpages.py @@ -1,7 +1,7 @@ from django import template from django.conf import settings from django.contrib.flatpages.models import FlatPage -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site register = template.Library() diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index 117fbb6f55..81d04c33b8 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -1,6 +1,6 @@ from django.conf import settings from django.contrib.flatpages.models import FlatPage -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect from django.shortcuts import get_object_or_404 from django.template import loader, RequestContext diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index a65612a541..ebfecc5b26 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -5,7 +5,7 @@ import warnings from django.apps import apps from django.http import HttpResponse, Http404 from django.template import loader -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.core import urlresolvers from django.core.paginator import EmptyPage, PageNotAnInteger from django.contrib.gis.db.models.fields import GeometryField diff --git a/django/contrib/redirects/middleware.py b/django/contrib/redirects/middleware.py index 4894e30216..6f0b6bc92e 100644 --- a/django/contrib/redirects/middleware.py +++ b/django/contrib/redirects/middleware.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.apps import apps from django.conf import settings from django.contrib.redirects.models import Redirect -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ImproperlyConfigured from django import http diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py index f310ee113f..aa184e99bd 100644 --- a/django/contrib/sitemaps/views.py +++ b/django/contrib/sitemaps/views.py @@ -1,7 +1,7 @@ from calendar import timegm from functools import wraps -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.core import urlresolvers from django.core.paginator import EmptyPage, PageNotAnInteger from django.http import Http404 diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py index 18f72c9b84..8f72e1a11c 100644 --- a/django/contrib/sites/models.py +++ b/django/contrib/sites/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import string +import warnings from django.core.exceptions import ImproperlyConfigured, ValidationError from django.db import models @@ -8,6 +9,9 @@ from django.db.models.signals import pre_save, pre_delete from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import python_2_unicode_compatible +from .requests import RequestSite as RealRequestSite +from .shortcuts import get_current_site as real_get_current_site + SITE_CACHE = {} @@ -74,38 +78,19 @@ class Site(models.Model): return self.domain -@python_2_unicode_compatible -class RequestSite(object): - """ - A class that shares the primary interface of Site (i.e., it has - ``domain`` and ``name`` attributes) but gets its data from a Django - HttpRequest object rather than from a database. - - The save() and delete() methods raise NotImplementedError. - """ - def __init__(self, request): - self.domain = self.name = request.get_host() - - def __str__(self): - return self.domain - - def save(self, force_insert=False, force_update=False): - raise NotImplementedError('RequestSite cannot be saved.') - - def delete(self): - raise NotImplementedError('RequestSite cannot be deleted.') +class RequestSite(RealRequestSite): + def __init__(self, *args, **kwargs): + warnings.warn( + "Please import RequestSite from django.contrib.sites.requests.", + PendingDeprecationWarning, stacklevel=2) + super(RequestSite, self).__init__(*args, **kwargs) def get_current_site(request): - """ - Checks if contrib.sites is installed and returns either the current - ``Site`` object or a ``RequestSite`` object based on the request. - """ - if Site._meta.installed: - current_site = Site.objects.get_current() - else: - current_site = RequestSite(request) - return current_site + warnings.warn( + "Please import get_current_site from django.contrib.sites.shortcuts.", + PendingDeprecationWarning, stacklevel=2) + return real_get_current_site(request) def clear_site_cache(sender, **kwargs): diff --git a/django/contrib/sites/requests.py b/django/contrib/sites/requests.py new file mode 100644 index 0000000000..233d840945 --- /dev/null +++ b/django/contrib/sites/requests.py @@ -0,0 +1,25 @@ +from __future__ import unicode_literals + +from django.utils.encoding import python_2_unicode_compatible + + +@python_2_unicode_compatible +class RequestSite(object): + """ + A class that shares the primary interface of Site (i.e., it has + ``domain`` and ``name`` attributes) but gets its data from a Django + HttpRequest object rather than from a database. + + The save() and delete() methods raise NotImplementedError. + """ + def __init__(self, request): + self.domain = self.name = request.get_host() + + def __str__(self): + return self.domain + + def save(self, force_insert=False, force_update=False): + raise NotImplementedError('RequestSite cannot be saved.') + + def delete(self): + raise NotImplementedError('RequestSite cannot be deleted.') diff --git a/django/contrib/sites/shortcuts.py b/django/contrib/sites/shortcuts.py new file mode 100644 index 0000000000..c525948286 --- /dev/null +++ b/django/contrib/sites/shortcuts.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals + +from django.apps import apps + + +def get_current_site(request): + """ + Checks if contrib.sites is installed and returns either the current + ``Site`` object or a ``RequestSite`` object based on the request. + """ + # Imports are inside the function because its point is to avoid importing + # the Site models when django.contrib.sites isn't installed. + if apps.is_installed('django.contrib.sites'): + from .models import Site + return Site.objects.get_current() + else: + from .requests import RequestSite + return RequestSite(request) diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py index efa4fefa44..8f014df638 100644 --- a/django/contrib/sites/tests.py +++ b/django/contrib/sites/tests.py @@ -1,11 +1,14 @@ from __future__ import unicode_literals from django.conf import settings -from django.contrib.sites.models import Site, RequestSite, get_current_site from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.http import HttpRequest from django.test import TestCase, modify_settings, override_settings +from .models import Site +from .requests import RequestSite +from .shortcuts import get_current_site + @modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'}) class SitesFrameworkTests(TestCase): diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 12d2c4b1af..e4ff6e6d12 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from calendar import timegm from django.conf import settings -from django.contrib.sites.models import get_current_site +from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import HttpResponse, Http404 from django.template import loader, TemplateDoesNotExist, RequestContext diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 001e21c0a5..51b346ca5d 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -12,7 +12,7 @@ def cache_page(*args, **kwargs): The cache is keyed by the URL and some data from the headers. Additionally there is the key prefix that is used to distinguish different cache areas in a multi-site setup. You could use the - sites.get_current_site().domain, for example, as that is unique across a Django + get_current_site().domain, for example, as that is unique across a Django project. Additionally, all headers from the response's Vary header will be taken diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index d8d0110a74..b8cdb27c5a 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -184,6 +184,9 @@ these changes. * ``AppCommand.handle_app()`` will no longer be supported. +* ``RequestSite`` will be located in ``django.contrib.sites.requests`` and + ``get_current_site`` in ``django.contrib.sites.shortcuts``. + * FastCGI support via the ``runfcgi`` management command will be removed. Please deploy your project using WSGI. diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 80473c59aa..b0d9a4b8ee 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -6,8 +6,6 @@ The "sites" framework :synopsis: Lets you operate multiple Web sites from the same database and Django project -.. currentmodule:: django.contrib.sites.models - Django comes with an optional "sites" framework. It's a hook for associating objects and functionality to particular Web sites, and it's a holding place for the domain names and "verbose" names of your Django-powered sites. @@ -15,9 +13,9 @@ the domain names and "verbose" names of your Django-powered sites. Use it if your single Django installation powers more than one site and you need to differentiate between those sites in some way. -The whole sites framework is based on a simple model: +The sites framework is mainly based on a simple model: -.. class:: Site +.. class:: models.Site A model for storing the ``domain`` and ``name`` attributes of a Web site. The :setting:`SITE_ID` setting specifies the database ID of the @@ -32,7 +30,6 @@ The whole sites framework is based on a simple model: A human-readable "verbose" name for the Web site. - How you use this is up to you, but Django uses it in a couple of ways automatically via simple conventions. @@ -80,7 +77,7 @@ This accomplishes several things quite nicely: The view code that displays a given story just checks to make sure the requested story is on the current site. It looks something like this:: - from django.contrib.sites.models import get_current_site + from django.contrib.sites.shortcuts import get_current_site def article_detail(request, article_id): try: @@ -137,7 +134,7 @@ hard-coding is best for hackish fixes that you need done quickly. The cleaner way of accomplishing the same thing is to check the current site's domain:: - from django.contrib.sites.models import get_current_site + from django.contrib.sites.shortcuts import get_current_site def my_view(request): current_site = get_current_site(request) @@ -149,7 +146,8 @@ domain:: pass This has also the advantage of checking if the sites framework is installed, -and return a :class:`RequestSite` instance if it is not. +and return a :class:`~django.contrib.sites.requests.RequestSite` instance if +it is not. If you don't have access to the request object, you can use the ``get_current()`` method of the :class:`~django.contrib.sites.models.Site` @@ -185,7 +183,7 @@ current site's :attr:`~django.contrib.sites.models.Site.name` and Here's an example of what the form-handling view looks like:: - from django.contrib.sites.models import get_current_site + from django.contrib.sites.shortcuts import get_current_site from django.core.mail import send_mail def register_for_newsletter(request): @@ -296,12 +294,10 @@ clear the cache using ``Site.objects.clear_cache()``:: Site.objects.clear_cache() current_site = Site.objects.get_current() -.. currentmodule:: django.contrib.sites.managers - The ``CurrentSiteManager`` ========================== -.. class:: CurrentSiteManager +.. class:: managers.CurrentSiteManager If :class:`~django.contrib.sites.models.Site` plays a key role in your application, consider using the helpful @@ -426,8 +422,6 @@ Here's how Django uses the sites framework: :class:`~django.contrib.sites.models.Site` to work out the domain for the site that it will redirect to. -.. currentmodule:: django.contrib.sites.models - ``RequestSite`` objects ======================= @@ -435,32 +429,50 @@ Here's how Django uses the sites framework: Some :doc:`django.contrib ` applications take advantage of the sites framework but are architected in a way that doesn't *require* the -sites framework to be installed in your database. (Some people don't want to, or -just aren't *able* to install the extra database table that the sites framework -requires.) For those cases, the framework provides a -:class:`~django.contrib.sites.models.RequestSite` class, which can be used as a -fallback when the database-backed sites framework is not available. +sites framework to be installed in your database. (Some people don't want to, +or just aren't *able* to install the extra database table that the sites +framework requires.) For those cases, the framework provides a +:class:`django.contrib.sites.requests.RequestSite` class, which can be used as +a fallback when the database-backed sites framework is not available. -.. class:: RequestSite +.. class:: requests.RequestSite A class that shares the primary interface of :class:`~django.contrib.sites.models.Site` (i.e., it has ``domain`` and ``name`` attributes) but gets its data from a Django :class:`~django.http.HttpRequest` object rather than from a database. - The ``save()`` and ``delete()`` methods raise ``NotImplementedError``. - .. method:: __init__(request) Sets the ``name`` and ``domain`` attributes to the value of :meth:`~django.http.HttpRequest.get_host`. + .. versionchanged:: 1.7 -A :class:`~django.contrib.sites.models.RequestSite` object has a similar -interface to a normal :class:`~django.contrib.sites.models.Site` object, except -its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an -:class:`~django.http.HttpRequest` object. It's able to deduce the -``domain`` and ``name`` by looking at the request's domain. It has ``save()`` -and ``delete()`` methods to match the interface of + This class used to be defined in ``django.contrib.sites.models``. + +A :class:`~django.contrib.sites.requests.RequestSite` object has a similar +interface to a normal :class:`~django.contrib.sites.models.Site` object, +except its :meth:`~django.contrib.sites.requests.RequestSite.__init__()` +method takes an :class:`~django.http.HttpRequest` object. It's able to deduce +the ``domain`` and ``name`` by looking at the request's domain. It has +``save()`` and ``delete()`` methods to match the interface of :class:`~django.contrib.sites.models.Site`, but the methods raise -``NotImplementedError``. +:exc:`~exceptions.NotImplementedError`.. + +``get_current_site`` shortcut +============================= + +Finally, to avoid repetitive fallback code, the framework provides a +:func:`django.contrib.sites.shortcut.get_current_site` function. + +.. function:: shortcuts.get_current_site + + A function that checks if ``django.contrib.sites`` is installed and + returns either the current :class:`~django.contrib.sites.models.Site` + object or a :class:`~django.contrib.sites.requests.RequestSite` object + based on the request. + + .. versionchanged:: 1.7 + + This function used to be defined in ``django.contrib.sites.models``. diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 81b07d9780..65b4582deb 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -131,7 +131,7 @@ into those elements. representing the current site. This is useful for ``{{ site.domain }}`` or ``{{ site.name }}``. If you do *not* have the Django sites framework installed, this will be set to a - :class:`django.contrib.sites.models.RequestSite` object. See the + :class:`~django.contrib.sites.requests.RequestSite` object. See the :ref:`RequestSite section of the sites framework documentation ` for more. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 684daa988c..e56939f3ce 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1009,6 +1009,19 @@ than simply ``myapp/models.py``, Django would look for :ref:`initial SQL data will search ``myapp/sql/`` as documented. The old location will continue to work until Django 1.9. +Reorganization of ``django.contrib.sites`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``django.contrib.sites`` provides reduced functionality when it isn't in +:setting:`INSTALLED_APPS`. The app-loading refactor adds some constraints in +that situation. As a consequence, two objects were moved, and the old +locations are deprecated: + +* :class:`~django.contrib.sites.requests.RequestSite` now lives in + ``django.contrib.sites.requests``. +* :func:`~django.contrib.sites.shortcuts.get_current_site` now lives in + ``django.contrib.sites.shortcuts``. + ``declared_fieldsets`` attribute on ``ModelAdmin`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index a721fb548c..5d93e432a4 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -648,7 +648,7 @@ patterns. * ``site``: The current :class:`~django.contrib.sites.models.Site`, according to the :setting:`SITE_ID` setting. If you don't have the site framework installed, this will be set to an instance of - :class:`~django.contrib.sites.models.RequestSite`, which derives the + :class:`~django.contrib.sites.requests.RequestSite`, which derives the site name and domain from the current :class:`~django.http.HttpRequest`. @@ -744,7 +744,7 @@ patterns. * ``site``: The current :class:`~django.contrib.sites.models.Site`, according to the :setting:`SITE_ID` setting. If you don't have the site framework installed, this will be set to an instance of - :class:`~django.contrib.sites.models.RequestSite`, which derives the + :class:`~django.contrib.sites.requests.RequestSite`, which derives the site name and domain from the current :class:`~django.http.HttpRequest`.