Code

Ticket #4604: messages.2.diff

File messages.2.diff, 15.3 KB (added by SmileyChris, 6 years ago)

More tests

Line 
1Index: django/conf/global_settings.py
2===================================================================
3--- django/conf/global_settings.py      (revision 6894)
4+++ django/conf/global_settings.py      (working copy)
5@@ -154,6 +154,7 @@
6     'django.core.context_processors.debug',
7     'django.core.context_processors.i18n',
8     'django.core.context_processors.media',
9+    'django.core.context_processors.messages',
10 #    'django.core.context_processors.request',
11 )
12 
13Index: django/core/context_processors.py
14===================================================================
15--- django/core/context_processors.py   (revision 6894)
16+++ django/core/context_processors.py   (working copy)
17@@ -8,6 +8,7 @@
18 """
19 
20 from django.conf import settings
21+from django.utils.functional import memoize, lazy
22 
23 def auth(request):
24     """
25@@ -24,10 +25,49 @@
26         user = AnonymousUser()
27     return {
28         'user': user,
29-        'messages': user.get_and_delete_messages(),
30         'perms': PermWrapper(user),
31     }
32 
33+class LazyMessages(object):
34+    def __init__(self, request):
35+        self.request = request
36+    def __iter__(self):
37+        return self.messages.__iter__()
38+    def __len__(self):
39+        return len(self.messages)
40+    def __nonzero__(self):
41+        return bool(self.messages)
42+    def __unicode__(self):
43+        return unicode(self.messages)
44+    def _get_messages(self):
45+        if hasattr(self, '_messages'):
46+            return self._messages
47+        # First, retreive any messages for the user.
48+        if hasattr(self.request, 'user') and \
49+           hasattr(self.request.user, 'get_and_delete_messages'):
50+            self._messages = self.request.user.get_and_delete_messages()
51+        else:
52+            self._messages = []
53+        # Next, retrieve any messages for the session.
54+        if hasattr(self.request, 'session'):
55+            self._messages += self.request.session.get_and_delete_messages()
56+        return self._messages
57+    messages = property(_get_messages)
58+
59+def messages(request):
60+    """
61+    Return messages for the session and the current user.
62+
63+    The messages are lazy loaded, so no messages are retreived and deleted
64+    unless requested from the template.
65+   
66+    Both contrib.session and contrib.auth are optional. If neither is provided,
67+    no 'messages' variable will be added to the context.
68+    """
69+    if hasattr(request, 'session') or hasattr(request, 'user'):
70+        return {'messages': LazyMessages(request)}
71+    return {}
72+
73 def debug(request):
74     "Returns context variables helpful for debugging."
75     context_extras = {}
76Index: django/contrib/sessions/tests.py
77===================================================================
78--- django/contrib/sessions/tests.py    (revision 6894)
79+++ django/contrib/sessions/tests.py    (working copy)
80@@ -16,6 +16,19 @@
81 'dog'
82 >>> db_session.pop('some key', 'does not exist')
83 'does not exist'
84+>>> db_session.get_messages()
85+[]
86+>>> db_session.create_message('first post')
87+>>> db_session.get_messages()
88+['first post']
89+>>> db_session.get_and_delete_messages()
90+['first post']
91+>>> db_session.get_and_delete_messages()
92+[]
93+>>> db_session.create_message('hello')
94+>>> db_session.create_message('world')
95+>>> db_session.get_and_delete_messages()
96+['hello', 'world']
97 >>> db_session.save()
98 >>> db_session.exists(db_session.session_key)
99 True
100@@ -33,6 +46,19 @@
101 'dog'
102 >>> file_session.pop('some key', 'does not exist')
103 'does not exist'
104+>>> file_session.get_messages()
105+[]
106+>>> file_session.create_message('first post')
107+>>> file_session.get_messages()
108+['first post']
109+>>> file_session.get_and_delete_messages()
110+['first post']
111+>>> file_session.get_and_delete_messages()
112+[]
113+>>> file_session.create_message('hello')
114+>>> file_session.create_message('world')
115+>>> file_session.get_and_delete_messages()
116+['hello', 'world']
117 >>> file_session.save()
118 >>> file_session.exists(file_session.session_key)
119 True
120@@ -57,6 +83,19 @@
121 'dog'
122 >>> cache_session.pop('some key', 'does not exist')
123 'does not exist'
124+>>> cache_session.get_messages()
125+[]
126+>>> cache_session.create_message('first post')
127+>>> cache_session.get_messages()
128+['first post']
129+>>> cache_session.get_and_delete_messages()
130+['first post']
131+>>> cache_session.get_and_delete_messages()
132+[]
133+>>> cache_session.create_message('hello')
134+>>> cache_session.create_message('world')
135+>>> cache_session.get_and_delete_messages()
136+['hello', 'world']
137 >>> cache_session.save()
138 >>> cache_session.delete(cache_session.session_key)
139 >>> cache_session.exists(cache_session.session_key)
140Index: django/contrib/sessions/backends/base.py
141===================================================================
142--- django/contrib/sessions/backends/base.py    (revision 6894)
143+++ django/contrib/sessions/backends/base.py    (working copy)
144@@ -18,6 +18,7 @@
145     """
146     TEST_COOKIE_NAME = 'testcookie'
147     TEST_COOKIE_VALUE = 'worked'
148+    MESSAGES_NAME = '_messages'
149 
150     def __init__(self, session_key=None):
151         self._session_key = session_key
152@@ -68,6 +69,20 @@
153     def delete_test_cookie(self):
154         del self[self.TEST_COOKIE_NAME]
155 
156+    def get_messages(self):
157+            return self.get(self.MESSAGES_NAME, [])
158+
159+    def get_and_delete_messages(self):
160+            return self.pop(self.MESSAGES_NAME, [])
161+
162+    def create_message(self, message):
163+        messages = self.get(self.MESSAGES_NAME)
164+        if messages is None:
165+            messages = []
166+            self[self.MESSAGES_NAME] = messages
167+        messages.append(message)
168+        self.modified = True
169+
170     def encode(self, session_dict):
171         "Returns the given session dictionary pickled and encoded as a string."
172         pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
173Index: tests/regressiontests/messages/__init__.py
174===================================================================
175Index: tests/regressiontests/messages/tests.py
176===================================================================
177--- tests/regressiontests/messages/tests.py     (revision 0)
178+++ tests/regressiontests/messages/tests.py     (revision 0)
179@@ -0,0 +1,74 @@
180+"""
181+>>> from django.core import context_processors
182+>>> from django.http import HttpRequest
183+
184+Set up request with a fake user and session (just enough to test getting and
185+deleting messages).
186+>>> request = HttpRequest()
187+>>> class FakeMessageObj:
188+...     def __init__(self, object, messages):
189+...         self.messages = messages
190+...         self.object = object
191+...     def get_and_delete_messages(self):
192+...         print 'Getting and deleting any %s messages...' % self.object
193+...         m = self.messages
194+...         self.messages = []
195+...         return m
196+>>> request.user = FakeMessageObj('user', ['User message'])
197+>>> request.session = FakeMessageObj('session', ['Message 1', 'Second message'])
198+
199+Run the messages context processor, and pull out the messages context variable.
200+>>> context = context_processors.messages(request)
201+>>> messages = context['messages']
202+
203+The messages context variable is a LazyMessages class. The messages haven't
204+actually been retreived (and deleted) yet.
205+>>> messages.__class__
206+<class 'django.core.context_processors.LazyMessages'>
207+
208+When any of the following methods are called, the messages are retreived from
209+the session (and user if contrib.auth is installed) from the LazyMessages
210+object to be retreived: __iter__, __len__, __nonzero__, __unicode__
211+>>> len(messages)
212+Getting and deleting any user messages...
213+Getting and deleting any session messages...
214+3
215+
216+When messages are retreived, messages are deleted from the session (and user if
217+contrib.auth is installed).
218+>>> request.user.messages
219+[]
220+>>> request.session.messages
221+[]
222+
223+The messages are still available to the LazyMessages instance because it caches
224+them.
225+>>> for message in messages:
226+...     print message
227+User message
228+Message 1
229+Second message
230+
231+Both contrib.sessions and contrib.auth are optional. If neither are provided,
232+no 'messages' variable will be added to the context.
233+>>> del request.user
234+>>> request.session = FakeMessageObj('session', [])
235+>>> context = context_processors.messages(request)
236+>>> messages = context['messages']
237+>>> if messages:
238+...     print 'messages found!'
239+Getting and deleting any session messages...
240+
241+>>> del request.session
242+>>> request.user = FakeMessageObj('user', [])
243+>>> context = context_processors.messages(request)
244+>>> messages = context['messages']
245+>>> if messages:
246+...     print 'messages found!'
247+Getting and deleting any user messages...
248+
249+>>> del request.user
250+>>> context = context_processors.messages(request)
251+>>> context
252+{}
253+"""
254\ No newline at end of file
255Index: tests/regressiontests/messages/models.py
256===================================================================
257Index: tests/regressiontests/middleware/models.py
258===================================================================
259Index: docs/sessions.txt
260===================================================================
261--- docs/sessions.txt   (revision 6894)
262+++ docs/sessions.txt   (working copy)
263@@ -193,6 +193,39 @@
264         request.session.set_test_cookie()
265         return render_to_response('foo/login_form.html')
266 
267+Messages
268+========
269+
270+**New in Django development version**
271+
272+The session message system provides a simple way to queue messages for all
273+(anonymous or authenticated) site visitors. To associate messages with users in
274+the user database, use the `authentication message framework`_.
275+
276+.. _authentication message framework: ../authentication/#messages
277+
278+Messages are associated with a session, therefore a message only lasts as long
279+as a session is valid (see `browser-length sessions vs. persistent sessions`_).
280+
281+The message system relies on the session middleware and is accessed via
282+``request.session``. The API is simple:
283+
284+    * To create a new message, use
285+      ``request.session.create_message(message='message text').``
286+
287+    * To retreive the messages, use ``request.session.get_messages()``,
288+      which returns a list of any messages (strings) in the session's queue.
289+
290+    * To retrieve and delete messages, use
291+      ``request.session.get_and_delete_messages()``, which returns the list of
292+      any messages in the session's queue and then deletes the messages from the
293+      queue.
294+
295+The `django.core.context_processors.messages`_ context processor makes both
296+session messages and user messages available to templates.
297+
298+.. _django.core.context_processors.messages: ../templates_python/#django-core-context_processors-messages
299+
300 Using sessions out of views
301 ===========================
302 
303Index: docs/authentication.txt
304===================================================================
305--- docs/authentication.txt     (revision 6894)
306+++ docs/authentication.txt     (working copy)
307@@ -956,8 +956,11 @@
308 Messages
309 ========
310 
311-The message system is a lightweight way to queue messages for given users.
312+The user message system is a lightweight way to queue messages for given users.
313+To send messages to anonymous users, use `session messages`_.
314 
315+.. _session framework: ../sessions/#messages
316+
317 A message is associated with a ``User``. There's no concept of expiration or
318 timestamps.
319 
320@@ -983,24 +986,20 @@
321             context_instance=RequestContext(request))
322 
323 When you use ``RequestContext``, the currently logged-in user and his/her
324-messages are made available in the `template context`_ as the template variable
325-``{{ messages }}``. Here's an example of template code that displays messages::
326+messages are made available in the `template context`_ as the ``{{ messages }}``
327+template variable.
328 
329-    {% if messages %}
330-    <ul>
331-        {% for message in messages %}
332-        <li>{{ message }}</li>
333-        {% endfor %}
334-    </ul>
335-    {% endif %}
336+**New in Django development version**
337 
338-Note that ``RequestContext`` calls ``get_and_delete_messages`` behind the
339-scenes, so any messages will be deleted even if you don't display them.
340+The ``{{ messages }}`` template variable will also contain session messages.
341+For more information, see `django.core.context_processors.messages`_.
342 
343-Finally, note that this messages framework only works with users in the user
344-database. To send messages to anonymous users, use the `session framework`_.
345+.. _django.core.context_processors.messages: ../templates_python/#django-core-context_processors-messages
346 
347-.. _session framework: ../sessions/
348+Also note that previously, ``RequestContext`` directly called
349+``get_and_delete_messages`` behind the scenes, so any messages were deleted even
350+if not displayed. Messages are now only deleted if the ``{{ messages }}``
351+variable is accessed in a template.
352 
353 Other authentication sources
354 ============================
355Index: docs/templates_python.txt
356===================================================================
357--- docs/templates_python.txt   (revision 6894)
358+++ docs/templates_python.txt   (working copy)
359@@ -295,7 +295,8 @@
360     ("django.core.context_processors.auth",
361     "django.core.context_processors.debug",
362     "django.core.context_processors.i18n",
363-    "django.core.context_processors.media")
364+    "django.core.context_processors.media",
365+    "django.core.context_processors.messages")
366 
367 Each processor is applied in order. That means, if one processor adds a
368 variable to the context and a second processor adds a variable with the same
369@@ -345,11 +346,6 @@
370       logged-in user (or an ``AnonymousUser`` instance, if the client isn't
371       logged in). See the `user authentication docs`_.
372 
373-    * ``messages`` -- A list of messages (as strings) for the currently
374-      logged-in user. Behind the scenes, this calls
375-      ``request.user.get_and_delete_messages()`` for every request. That method
376-      collects the user's messages and deletes them from the database.
377-
378       Note that messages are set with ``user.message_set.create``. See the
379       `message docs`_ for more.
380 
381@@ -358,10 +354,13 @@
382       permissions that the currently logged-in user has. See the `permissions
383       docs`_.
384 
385-.. _user authentication docs: ../authentication/#users
386-.. _message docs: ../authentication/#messages
387-.. _permissions docs: ../authentication/#permissions
388+**New in Django development version**
389 
390+Previously, a ``messages`` variable was also added to ``RequestContext``
391+containing a list of messages for the currently logged-in user. This
392+functionality has been moved to the `django.core.context_processors.messages`_
393+context processor.
394+
395 django.core.context_processors.debug
396 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
397 
398@@ -409,6 +408,31 @@
399 `HttpRequest object`_. Note that this processor is not enabled by default;
400 you'll have to activate it.
401 
402+django.core.context_processors.messages
403+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
404+
405+If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
406+``RequestContext`` will contain a variable ``messages``, which is a list of
407+messages (as strings) containing any messages for the current session (see
408+`session messages`_) or the currently logged-in user (see `auth messages`_).
409+
410+The messages are not retrieved and cleared (using ``get_and_delete_messages``)
411+until the ``messages`` variable is accessed in a template.
412+
413+Here's an example of template code that displays messages made available by this
414+context processor::
415+
416+       {% if messages %}
417+       <ul>
418+           {% for message in messages %}
419+           <li>{{ message }}</li>
420+           {% endfor %}
421+       </ul>
422+       {% endif %}
423+
424+.. _session messages: ../sessions/#messages
425+.. _auth messages: ../authentication/#messages
426+
427 Writing your own context processors
428 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
429