Kaydet (Commit) 9ffab9ce authored tarafından Aymeric Augustin's avatar Aymeric Augustin

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.
üst ca95f8e4
...@@ -16,7 +16,7 @@ from django.contrib.auth import authenticate, get_user_model ...@@ -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
......
...@@ -4,7 +4,8 @@ import os ...@@ -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
......
...@@ -15,7 +15,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout ...@@ -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()
......
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 _
......
...@@ -62,7 +62,7 @@ from django.contrib.comments import signals ...@@ -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):
......
...@@ -2,7 +2,7 @@ from __future__ import unicode_literals ...@@ -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
......
...@@ -2,7 +2,8 @@ from __future__ import unicode_literals ...@@ -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 _
......
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()
......
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
......
...@@ -5,7 +5,7 @@ import warnings ...@@ -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
......
...@@ -3,7 +3,7 @@ from __future__ import unicode_literals ...@@ -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
......
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
......
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 ...@@ -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): ...@@ -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):
......
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.')
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)
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):
......
...@@ -3,7 +3,7 @@ from __future__ import unicode_literals ...@@ -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
......
...@@ -12,7 +12,7 @@ def cache_page(*args, **kwargs): ...@@ -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
......
...@@ -184,6 +184,9 @@ these changes. ...@@ -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.
......
...@@ -6,8 +6,6 @@ The "sites" framework ...@@ -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. ...@@ -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: ...@@ -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: ...@@ -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 ...@@ -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:: ...@@ -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 ...@@ -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()``:: ...@@ -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: ...@@ -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: ...@@ -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
This class used to be defined in ``django.contrib.sites.models``.
A :class:`~django.contrib.sites.models.RequestSite` object has a similar A :class:`~django.contrib.sites.requests.RequestSite` object has a similar
interface to a normal :class:`~django.contrib.sites.models.Site` object, except interface to a normal :class:`~django.contrib.sites.models.Site` object,
its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an except its :meth:`~django.contrib.sites.requests.RequestSite.__init__()`
:class:`~django.http.HttpRequest` object. It's able to deduce the method takes an :class:`~django.http.HttpRequest` object. It's able to deduce
``domain`` and ``name`` by looking at the request's domain. It has ``save()`` the ``domain`` and ``name`` by looking at the request's domain. It has
and ``delete()`` methods to match the interface of ``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``.
...@@ -131,7 +131,7 @@ into those elements. ...@@ -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.
......
...@@ -1009,6 +1009,19 @@ than simply ``myapp/models.py``, Django would look for :ref:`initial SQL data ...@@ -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``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......
...@@ -648,7 +648,7 @@ patterns. ...@@ -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. ...@@ -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`.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment