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.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

View File

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

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.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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -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 </ref/contrib/index>` 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``.

View File

@ -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
<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
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``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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`.