Moved RequestSite and get_current_site.

Following the app-loading refactor, these objects must live outside of
django.contrib.sites.models because they must be available without
importing the django.contrib.sites.models module when
django.contrib.sites isn't installed.

Refs #21680. Thanks Carl and Loic for reporting this issue.
This commit is contained in:
Aymeric Augustin 2014-01-25 21:54:25 +01:00
parent ca95f8e435
commit 9ffab9cee1
23 changed files with 137 additions and 76 deletions

View File

@ -16,7 +16,7 @@ from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher
from django.contrib.auth.tokens import default_token_generator 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 UNMASKED_DIGITS_TO_SHOW = 6

View File

@ -4,7 +4,8 @@ import os
import re import re
from django.conf import global_settings, settings 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.admin.models import LogEntry
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import mail from django.core import mail

View File

@ -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.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm
from django.contrib.auth.tokens import default_token_generator 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() @sensitive_post_parameters()

View File

@ -1,5 +1,5 @@
from django.contrib.syndication.views import Feed 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.contrib import comments
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _

View File

@ -62,7 +62,7 @@ from django.contrib.comments import signals
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.template import Context, loader from django.template import Context, loader
from django.contrib import comments 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 from django.utils import timezone
class AlreadyModerated(Exception): class AlreadyModerated(Exception):

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.views import shortcut 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.db import models
from django.http import HttpRequest, Http404 from django.http import HttpRequest, Http404
from django.test import TestCase, override_settings from django.test import TestCase, override_settings

View File

@ -2,7 +2,8 @@ from __future__ import unicode_literals
from django import http from django import http
from django.contrib.contenttypes.models import ContentType 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.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _

View File

@ -1,7 +1,7 @@
from django import template from django import template
from django.conf import settings from django.conf import settings
from django.contrib.flatpages.models import FlatPage 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() register = template.Library()

View File

@ -1,6 +1,6 @@
from django.conf import settings from django.conf import settings
from django.contrib.flatpages.models import FlatPage 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.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template import loader, RequestContext from django.template import loader, RequestContext

View File

@ -5,7 +5,7 @@ import warnings
from django.apps import apps from django.apps import apps
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.template import loader 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 import urlresolvers
from django.core.paginator import EmptyPage, PageNotAnInteger from django.core.paginator import EmptyPage, PageNotAnInteger
from django.contrib.gis.db.models.fields import GeometryField from django.contrib.gis.db.models.fields import GeometryField

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.redirects.models import Redirect 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.core.exceptions import ImproperlyConfigured
from django import http from django import http

View File

@ -1,7 +1,7 @@
from calendar import timegm from calendar import timegm
from functools import wraps 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 import urlresolvers
from django.core.paginator import EmptyPage, PageNotAnInteger from django.core.paginator import EmptyPage, PageNotAnInteger
from django.http import Http404 from django.http import Http404

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import string import string
import warnings
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models 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.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible 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 = {} SITE_CACHE = {}
@ -74,38 +78,19 @@ class Site(models.Model):
return self.domain return self.domain
@python_2_unicode_compatible class RequestSite(RealRequestSite):
class RequestSite(object): def __init__(self, *args, **kwargs):
""" warnings.warn(
A class that shares the primary interface of Site (i.e., it has "Please import RequestSite from django.contrib.sites.requests.",
``domain`` and ``name`` attributes) but gets its data from a Django PendingDeprecationWarning, stacklevel=2)
HttpRequest object rather than from a database. super(RequestSite, self).__init__(*args, **kwargs)
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.')
def get_current_site(request): def get_current_site(request):
""" warnings.warn(
Checks if contrib.sites is installed and returns either the current "Please import get_current_site from django.contrib.sites.shortcuts.",
``Site`` object or a ``RequestSite`` object based on the request. PendingDeprecationWarning, stacklevel=2)
""" return real_get_current_site(request)
if Site._meta.installed:
current_site = Site.objects.get_current()
else:
current_site = RequestSite(request)
return current_site
def clear_site_cache(sender, **kwargs): def clear_site_cache(sender, **kwargs):

View File

@ -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.')

View File

@ -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)

View File

@ -1,11 +1,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings 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.core.exceptions import ObjectDoesNotExist, ValidationError
from django.http import HttpRequest from django.http import HttpRequest
from django.test import TestCase, modify_settings, override_settings 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'}) @modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'})
class SitesFrameworkTests(TestCase): class SitesFrameworkTests(TestCase):

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from calendar import timegm from calendar import timegm
from django.conf import settings 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.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.template import loader, TemplateDoesNotExist, RequestContext from django.template import loader, TemplateDoesNotExist, RequestContext

View File

@ -12,7 +12,7 @@ def cache_page(*args, **kwargs):
The cache is keyed by the URL and some data from the headers. 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 Additionally there is the key prefix that is used to distinguish different
cache areas in a multi-site setup. You could use the 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. project.
Additionally, all headers from the response's Vary header will be taken Additionally, all headers from the response's Vary header will be taken

View File

@ -184,6 +184,9 @@ these changes.
* ``AppCommand.handle_app()`` will no longer be supported. * ``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 * FastCGI support via the ``runfcgi`` management command will be
removed. Please deploy your project using WSGI. removed. Please deploy your project using WSGI.

View File

@ -6,8 +6,6 @@ The "sites" framework
:synopsis: Lets you operate multiple Web sites from the same database and :synopsis: Lets you operate multiple Web sites from the same database and
Django project Django project
.. currentmodule:: django.contrib.sites.models
Django comes with an optional "sites" framework. It's a hook for associating 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 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. 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 Use it if your single Django installation powers more than one site and you
need to differentiate between those sites in some way. 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. A model for storing the ``domain`` and ``name`` attributes of a Web site.
The :setting:`SITE_ID` setting specifies the database ID of the 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. 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 How you use this is up to you, but Django uses it in a couple of ways
automatically via simple conventions. 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 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:: 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): def article_detail(request, article_id):
try: 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 cleaner way of accomplishing the same thing is to check the current site's
domain:: domain::
from django.contrib.sites.models import get_current_site from django.contrib.sites.shortcuts import get_current_site
def my_view(request): def my_view(request):
current_site = get_current_site(request) current_site = get_current_site(request)
@ -149,7 +146,8 @@ domain::
pass pass
This has also the advantage of checking if the sites framework is installed, 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 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` ``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:: 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 from django.core.mail import send_mail
def register_for_newsletter(request): def register_for_newsletter(request):
@ -296,12 +294,10 @@ clear the cache using ``Site.objects.clear_cache()``::
Site.objects.clear_cache() Site.objects.clear_cache()
current_site = Site.objects.get_current() current_site = Site.objects.get_current()
.. currentmodule:: django.contrib.sites.managers
The ``CurrentSiteManager`` The ``CurrentSiteManager``
========================== ==========================
.. class:: CurrentSiteManager .. class:: managers.CurrentSiteManager
If :class:`~django.contrib.sites.models.Site` plays a key role in your If :class:`~django.contrib.sites.models.Site` plays a key role in your
application, consider using the helpful 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 :class:`~django.contrib.sites.models.Site` to work out the domain for the
site that it will redirect to. site that it will redirect to.
.. currentmodule:: django.contrib.sites.models
``RequestSite`` objects ``RequestSite`` objects
======================= =======================
@ -435,32 +429,50 @@ Here's how Django uses the sites framework:
Some :doc:`django.contrib </ref/contrib/index>` applications take advantage of Some :doc:`django.contrib </ref/contrib/index>` applications take advantage of
the sites framework but are architected in a way that doesn't *require* the 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 sites framework to be installed in your database. (Some people don't want to,
just aren't *able* to install the extra database table that the sites framework or just aren't *able* to install the extra database table that the sites
requires.) For those cases, the framework provides a framework requires.) For those cases, the framework provides a
:class:`~django.contrib.sites.models.RequestSite` class, which can be used as a :class:`django.contrib.sites.requests.RequestSite` class, which can be used as
fallback when the database-backed sites framework is not available. a fallback when the database-backed sites framework is not available.
.. class:: RequestSite .. class:: requests.RequestSite
A class that shares the primary interface of A class that shares the primary interface of
:class:`~django.contrib.sites.models.Site` (i.e., it has :class:`~django.contrib.sites.models.Site` (i.e., it has
``domain`` and ``name`` attributes) but gets its data from a Django ``domain`` and ``name`` attributes) but gets its data from a Django
:class:`~django.http.HttpRequest` object rather than from a database. :class:`~django.http.HttpRequest` object rather than from a database.
The ``save()`` and ``delete()`` methods raise ``NotImplementedError``.
.. method:: __init__(request) .. method:: __init__(request)
Sets the ``name`` and ``domain`` attributes to the value of Sets the ``name`` and ``domain`` attributes to the value of
:meth:`~django.http.HttpRequest.get_host`. :meth:`~django.http.HttpRequest.get_host`.
.. versionchanged:: 1.7
A :class:`~django.contrib.sites.models.RequestSite` object has a similar This class used to be defined in ``django.contrib.sites.models``.
interface to a normal :class:`~django.contrib.sites.models.Site` object, except
its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an A :class:`~django.contrib.sites.requests.RequestSite` object has a similar
:class:`~django.http.HttpRequest` object. It's able to deduce the interface to a normal :class:`~django.contrib.sites.models.Site` object,
``domain`` and ``name`` by looking at the request's domain. It has ``save()`` except its :meth:`~django.contrib.sites.requests.RequestSite.__init__()`
and ``delete()`` methods to match the interface of 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 :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``.

View File

@ -131,7 +131,7 @@ into those elements.
representing the current site. This is useful for ``{{ site.domain representing the current site. This is useful for ``{{ site.domain
}}`` or ``{{ site.name }}``. If you do *not* have the Django sites }}`` or ``{{ site.name }}``. If you do *not* have the Django sites
framework installed, this will be set to a 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 :ref:`RequestSite section of the sites framework documentation
<requestsite-objects>` for more. <requestsite-objects>` for more.

View File

@ -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 will search ``myapp/sql/`` as documented. The old location will continue to
work until Django 1.9. 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`` ``declared_fieldsets`` attribute on ``ModelAdmin``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -648,7 +648,7 @@ patterns.
* ``site``: The current :class:`~django.contrib.sites.models.Site`, * ``site``: The current :class:`~django.contrib.sites.models.Site`,
according to the :setting:`SITE_ID` setting. If you don't have the according to the :setting:`SITE_ID` setting. If you don't have the
site framework installed, this will be set to an instance of 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 site name and domain from the current
:class:`~django.http.HttpRequest`. :class:`~django.http.HttpRequest`.
@ -744,7 +744,7 @@ patterns.
* ``site``: The current :class:`~django.contrib.sites.models.Site`, * ``site``: The current :class:`~django.contrib.sites.models.Site`,
according to the :setting:`SITE_ID` setting. If you don't have the according to the :setting:`SITE_ID` setting. If you don't have the
site framework installed, this will be set to an instance of 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 site name and domain from the current
:class:`~django.http.HttpRequest`. :class:`~django.http.HttpRequest`.