summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Plant <L.Plant.98@cantab.net>2009-10-14 18:09:13 +0000
committerLuke Plant <L.Plant.98@cantab.net>2009-10-14 18:09:13 +0000
commitc161bf21f0ab9f2945ae7a44e5757b071f7eb712 (patch)
treee6664d0d2f3e1956dd3bc5954bea0ecee9960871
parentf14833ee67f535d66ce48155d5424024927bfe2d (diff)
Fixed #6552, #12031 - Make django.core.context_processors.auth lazy to avoid "Vary: Cookie"
Thanks to olau@iola.dk, Suor for the report git-svn-id: http://code.djangoproject.com/svn/django/trunk@11623 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/contrib/auth/tests/remote_user.py10
-rw-r--r--django/core/context_processors.py50
-rw-r--r--tests/regressiontests/context_processors/fixtures/context-processors-users.xml17
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html1
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html1
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html1
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html1
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html1
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html3
-rw-r--r--tests/regressiontests/context_processors/tests.py43
-rw-r--r--tests/regressiontests/context_processors/urls.py5
-rw-r--r--tests/regressiontests/context_processors/views.py26
12 files changed, 145 insertions, 14 deletions
diff --git a/django/contrib/auth/tests/remote_user.py b/django/contrib/auth/tests/remote_user.py
index 842d589a54..6115edcfd0 100644
--- a/django/contrib/auth/tests/remote_user.py
+++ b/django/contrib/auth/tests/remote_user.py
@@ -2,7 +2,7 @@ from datetime import datetime
from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend
-from django.contrib.auth.models import AnonymousUser, User
+from django.contrib.auth.models import User
from django.test import TestCase
@@ -30,15 +30,15 @@ class RemoteUserTest(TestCase):
num_users = User.objects.count()
response = self.client.get('/remote_user/')
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
response = self.client.get('/remote_user/', REMOTE_USER=None)
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
response = self.client.get('/remote_user/', REMOTE_USER='')
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
def test_unknown_user(self):
@@ -115,7 +115,7 @@ class RemoteUserNoCreateTest(RemoteUserTest):
def test_unknown_user(self):
num_users = User.objects.count()
response = self.client.get('/remote_user/', REMOTE_USER='newuser')
- self.assert_(isinstance(response.context['user'], AnonymousUser))
+ self.assert_(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
diff --git a/django/core/context_processors.py b/django/core/context_processors.py
index cb07125ce7..707068d3be 100644
--- a/django/core/context_processors.py
+++ b/django/core/context_processors.py
@@ -8,6 +8,27 @@ RequestContext.
"""
from django.conf import settings
+from django.utils.functional import lazy, memoize, LazyObject
+
+class ContextLazyObject(LazyObject):
+ """
+ A lazy object initialised from any function, useful for lazily
+ adding things to the Context.
+
+ Designed for compound objects of unknown type. For simple objects of known
+ type, use django.utils.functional.lazy.
+ """
+ def __init__(self, func):
+ """
+ Pass in a callable that returns the actual value to be used
+ """
+ self.__dict__['_setupfunc'] = func
+ # For some reason, we have to inline LazyObject.__init__ here to avoid
+ # recursion
+ self._wrapped = None
+
+ def _setup(self):
+ self._wrapped = self._setupfunc()
def auth(request):
"""
@@ -17,15 +38,26 @@ def auth(request):
If there is no 'user' attribute in the request, uses AnonymousUser (from
django.contrib.auth).
"""
- if hasattr(request, 'user'):
- user = request.user
- else:
- from django.contrib.auth.models import AnonymousUser
- user = AnonymousUser()
+ # If we access request.user, request.session is accessed, which results in
+ # 'Vary: Cookie' being sent in every request that uses this context
+ # processor, which can easily be every request on a site if
+ # TEMPLATE_CONTEXT_PROCESSORS has this context processor added. This kills
+ # the ability to cache. So, we carefully ensure these attributes are lazy.
+ # We don't use django.utils.functional.lazy() for User, because that
+ # requires knowing the class of the object we want to proxy, which could
+ # break with custom auth backends. LazyObject is a less complete but more
+ # flexible solution that is a good enough wrapper for 'User'.
+ def get_user():
+ if hasattr(request, 'user'):
+ return request.user
+ else:
+ from django.contrib.auth.models import AnonymousUser
+ return AnonymousUser()
+
return {
- 'user': user,
- 'messages': user.get_and_delete_messages(),
- 'perms': PermWrapper(user),
+ 'user': ContextLazyObject(get_user),
+ 'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
+ 'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
}
def debug(request):
@@ -79,7 +111,7 @@ class PermWrapper(object):
def __getitem__(self, module_name):
return PermLookupDict(self.user, module_name)
-
+
def __iter__(self):
# I am large, I contain multitudes.
raise TypeError("PermWrapper is not iterable.")
diff --git a/tests/regressiontests/context_processors/fixtures/context-processors-users.xml b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml
new file mode 100644
index 0000000000..aba8f4aace
--- /dev/null
+++ b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="100" model="auth.user">
+ <field type="CharField" name="username">super</field>
+ <field type="CharField" name="first_name">Super</field>
+ <field type="CharField" name="last_name">User</field>
+ <field type="CharField" name="email">super@example.com</field>
+ <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+ <field type="BooleanField" name="is_staff">True</field>
+ <field type="BooleanField" name="is_active">True</field>
+ <field type="BooleanField" name="is_superuser">True</field>
+ <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+ <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+ <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+ <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+ </object>
+</django-objects>
diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
new file mode 100644
index 0000000000..b5c65db28d
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
@@ -0,0 +1 @@
+{{ user }}
diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
new file mode 100644
index 0000000000..7b7e448ad2
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
@@ -0,0 +1 @@
+{% for m in messages %}{{ m }}{% endfor %}
diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html
new file mode 100644
index 0000000000..8d1c8b69c3
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html
@@ -0,0 +1 @@
+
diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
new file mode 100644
index 0000000000..a5db868e9e
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
@@ -0,0 +1 @@
+{% if perms.auth %}Has auth permissions{% endif %}
diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
new file mode 100644
index 0000000000..a28ff937f8
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
@@ -0,0 +1 @@
+{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html
new file mode 100644
index 0000000000..7ff2c938a2
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html
@@ -0,0 +1,3 @@
+unicode: {{ user }}
+id: {{ user.id }}
+username: {{ user.username }}
diff --git a/tests/regressiontests/context_processors/tests.py b/tests/regressiontests/context_processors/tests.py
index eadd6310b1..a05b143e55 100644
--- a/tests/regressiontests/context_processors/tests.py
+++ b/tests/regressiontests/context_processors/tests.py
@@ -36,3 +36,46 @@ class RequestContextProcessorTests(TestCase):
self.assertContains(response, url)
response = self.client.post(url, {'path': '/blah/'})
self.assertContains(response, url)
+
+class AuthContextProcessorTests(TestCase):
+ """
+ Tests for the ``django.core.context_processors.auth`` processor
+ """
+ urls = 'regressiontests.context_processors.urls'
+ fixtures = ['context-processors-users.xml']
+
+ def test_session_not_accessed(self):
+ """
+ Tests that the session is not accessed simply by including
+ the auth context processor
+ """
+ response = self.client.get('/auth_processor_no_attr_access/')
+ self.assertContains(response, "Session not accessed")
+
+ def test_session_is_accessed(self):
+ """
+ Tests that the session is accessed if the auth context processor
+ is used and relevant attributes accessed.
+ """
+ response = self.client.get('/auth_processor_attr_access/')
+ self.assertContains(response, "Session accessed")
+
+ def test_perms_attrs(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_perms/')
+ self.assertContains(response, "Has auth permissions")
+
+ def test_message_attrs(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_messages/')
+ self.assertContains(response, "Message 1")
+
+ def test_user_attrs(self):
+ """
+ Test that ContextLazyObject wraps objects properly
+ """
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/auth_processor_user/')
+ self.assertContains(response, "unicode: super")
+ self.assertContains(response, "id: 100")
+ self.assertContains(response, "username: super")
diff --git a/tests/regressiontests/context_processors/urls.py b/tests/regressiontests/context_processors/urls.py
index 7e8ba967c1..45c5fe7777 100644
--- a/tests/regressiontests/context_processors/urls.py
+++ b/tests/regressiontests/context_processors/urls.py
@@ -5,4 +5,9 @@ import views
urlpatterns = patterns('',
(r'^request_attrs/$', views.request_processor),
+ (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
+ (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
+ (r'^auth_processor_user/$', views.auth_processor_user),
+ (r'^auth_processor_perms/$', views.auth_processor_perms),
+ (r'^auth_processor_messages/$', views.auth_processor_messages),
)
diff --git a/tests/regressiontests/context_processors/views.py b/tests/regressiontests/context_processors/views.py
index 66e7132c05..e5195f9c65 100644
--- a/tests/regressiontests/context_processors/views.py
+++ b/tests/regressiontests/context_processors/views.py
@@ -6,3 +6,29 @@ from django.template.context import RequestContext
def request_processor(request):
return render_to_response('context_processors/request_attrs.html',
RequestContext(request, {}, processors=[context_processors.request]))
+
+def auth_processor_no_attr_access(request):
+ r1 = render_to_response('context_processors/auth_attrs_no_access.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+ # *After* rendering, we check whether the session was accessed
+ return render_to_response('context_processors/auth_attrs_test_access.html',
+ {'session_accessed':request.session.accessed})
+
+def auth_processor_attr_access(request):
+ r1 = render_to_response('context_processors/auth_attrs_access.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+ return render_to_response('context_processors/auth_attrs_test_access.html',
+ {'session_accessed':request.session.accessed})
+
+def auth_processor_user(request):
+ return render_to_response('context_processors/auth_attrs_user.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_perms(request):
+ return render_to_response('context_processors/auth_attrs_perms.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_messages(request):
+ request.user.message_set.create(message="Message 1")
+ return render_to_response('context_processors/auth_attrs_messages.html',
+ RequestContext(request, {}, processors=[context_processors.auth]))