Code

Ticket #4604: messages.7.diff

File messages.7.diff, 15.5 KB (added by SmileyChris, 6 years ago)

implement __getitem__ too, some people are using stuff like {{ messages.0 }}

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