diff options
| author | Chris McDonough <chrism@plope.com> | 2010-12-22 16:57:23 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2010-12-22 16:57:23 -0500 |
| commit | 6f6d3649b280d3585d4c48541cac14e3959f9776 (patch) | |
| tree | 59f83c6c1fd7259ddc40799ea1087d7aaee89843 | |
| parent | d9419fb90c161b9b05e87bca175909a28d93cc28 (diff) | |
| download | pyramid-6f6d3649b280d3585d4c48541cac14e3959f9776.tar.gz pyramid-6f6d3649b280d3585d4c48541cac14e3959f9776.tar.bz2 pyramid-6f6d3649b280d3585d4c48541cac14e3959f9776.zip | |
change flash API as per email convo with Ben and Mike
| -rw-r--r-- | docs/api/interfaces.rst | 1 | ||||
| -rw-r--r-- | docs/narr/flash.rst | 126 | ||||
| -rw-r--r-- | pyramid/flash.py | 32 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 57 | ||||
| -rw-r--r-- | pyramid/session.py | 19 | ||||
| -rw-r--r-- | pyramid/tests/test_flash.py | 81 | ||||
| -rw-r--r-- | pyramid/tests/test_session.py | 69 |
7 files changed, 117 insertions, 268 deletions
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index dab64ba15..b3c14e5f7 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -35,4 +35,3 @@ Other Interfaces .. autointerface:: ITemplateRenderer - .. autointerface:: IFlashMessages diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst index 331793533..d41c2cdaf 100644 --- a/docs/narr/flash.rst +++ b/docs/narr/flash.rst @@ -11,113 +11,97 @@ factory` as described in :ref:`using_the_default_session_factory` or Flash messaging has two main uses: to display a status message only once to the user after performing an internal redirect, and to allow generic code to log messages for single-time display without having direct access to an HTML -template. The user interface consists of two methods of the :term:`session` -object. +template. The user interface consists of a number of methods of the +:term:`session` object. Using the ``session.flash`` Method ---------------------------------- -To add a message to a flash queue, use a session object's ``flash`` method: +To add a message to a flash message queue, use a session object's ``flash`` +method: .. code-block:: python :linenos: request.session.flash('mymessage') -The ``.flash`` method appends a message to the queue, creating the queue if -necessary. The message is not modified in any way. +The ``.flash`` method appends a message to a flash queue, creating the queue +if necessary. -The ``category`` argument names a category or level. The library defines -several default category names: ``debug``, ``info``, ``success``, ``warning`` -and ``error``. The default category level is ``info``. +``.flash`` accepts three arguments: -The ``queue_name`` argument allows you to define multiple message -queues. This can be used to display different kinds of messages in different -places on a page. You cam pass any name for your queue, but it must be a -string. The default value is the empty string, which chooses the default -queue. Each queue is independent, and can be popped by ``unflash`` -separately. +.. method:: flash(message, queue='', allow_duplicate=True) -Constant names for flash message category names are importable from the -:mod:`pyramid.flash` module as ``DEBUG``, ``INFO``, ``SUCCESS``, ``WARNING`` -and ``ERROR``, which respectively name ``debug``, ``info``, ``success``, -``warning`` and ``error`` strings. For example you can do this: +The ``message`` argument is required. It represents a message you wish to +later display to a user. It is usually a string but the ``message`` you +provide is not modified in any way. -.. code-block:: python +The ``queue`` argument allows you to choose a queue to which to append the +message you provide. This can be used to push different kinds of messages +into flash storage for later display in different places on a page. You cam +pass any name for your queue, but it must be a string. The default value is +the empty string, which chooses the default queue. Each queue is independent, +and can be popped by ``pop_flash`` or examined via ``peek_flash`` separately. +``queue`` defaults to the empty string. The empty string represents the +default flash message queue. - from pyramid import flash - request.session.flash(msg, flash.DEBUG) +.. code-block:: python -Or you can use the literal name ``debug``: + request.session.flash(msg, 'myappsqueue') -.. code-block:: python +The ``allow_duplicate`` argument, which defaults to ``True``. If this is +``False``, if you attempt to add a message to a queue which is already +present in the queue, it will not be added. - request.session.flash(msg, 'debug') +Using the ``session.pop_flash`` Method +-------------------------------------- -Both examples do the same thing. The meanings of flash category names are -detailed in :mod:`pyramid.flash`. +Once one or more messages has been added to a flash queue by the +``session.flash`` API, the ``session.pop_flash`` API can be used to pop that +queue and return it for use. To pop a particular queue of messages from the flash object, use the session -object's ``unflash`` method. +object's ``pop_flash`` method. .. code-block:: python :linenos: - >>> request.session.flash('info message', 'info') - >>> messages = request.session.unflash() - >>> messages['info'] + >>> request.session.flash('info message') + >>> request.session.pop_flash() ['info message'] -Using the ``session.unflash`` Method ------------------------------------- - -Once one or more messages has been added to a flash queue by the -``session.flash`` API, the ``session.unflash`` API can be used to pop that -queue and return it for use. - -For example some code that runs in a view callable might call the -``session.flash`` API: +Calling ``session.pop_flash()`` again like above without a corresponding call +to ``session.flash`` will return an empty list, because the queue has already +been popped. .. code-block:: python :linenos: - request.session.flash('mymessage') - -A corresponding ``session.unflash`` might be called on a subsequent request: - -.. code-block:: python - :linenos: + >>> request.session.flash('info message') + >>> request.session.pop_flash() + ['info message'] + >>> request.session.pop_flash() + [] - messages = request.session.unflash() +The object returned from ``pop_flash`` is a list. -Calling ``session.unflash`` again like above without a corresponding call to -``session.flash`` will return an empty ``messages`` object, because the queue -has already been popped. +Using the ``session.pop_flash`` Method +-------------------------------------- -The ``messages`` object returned from ``unflash`` is a dictionary-like -object. Its keys are category names, and its values are sequences of -strings. For ease of use, the dict-like object returned by ``unflash`` isn't -a "plain" dict: it's an object which has several helper methods, each named -after a particular flash category level. These methods return all messages -related to the category name: +Once one or more messages has been added to a flash queue by the +``session.flash`` API, the ``session.peek_flash`` API can be used to "peek" +at that queue. Unlike ``session.pop_flash``, the queue is not popped from +flash storage. .. code-block:: python :linenos: - >>> request.session.flash('debug message', 'debug') - >>> request.session.flash('info message', 'info') - >>> messages = request.session.unflash() - >>> info_messages = messages.debug() - ['debug message'] - >>> info_messages = messages.info() + >>> request.session.flash('info message') + >>> request.session.peek_flash() ['info message'] - -The full API of the ``messages`` object returned by ``unflash`` is documented -in :class:`pyramid.interfaces.IFlashMessages`. - -.. The ``ignore_duplicate`` flag tells whether to suppress duplicate -.. messages. If true, and another message with identical text exists in the -.. queue, don't add the new message. But if the existing message has a -.. different category than the new message, change its category to match the -.. new message. - + >>> request.session.peek_flash() + ['info message'] + >>> request.session.pop_flash() + ['info message'] + >>> request.session.peek_flash() + [] diff --git a/pyramid/flash.py b/pyramid/flash.py deleted file mode 100644 index 9e9f44634..000000000 --- a/pyramid/flash.py +++ /dev/null @@ -1,32 +0,0 @@ -from zope.interface import implements - -from pyramid.interfaces import IFlashMessages - -# flash message categories -DEBUG = 'debug' # development messages -INFO = 'info' # informational messages -SUCCESS = 'success' # a message indicating success -WARNING = 'warning' # not an error, but not a success -ERROR = 'error' # an action was unsuccessful - -class FlashMessages(dict): - implements(IFlashMessages) - def custom(self, name): - messages = self.get(name, []) - return messages - - def debug(self): - return self.get(DEBUG, []) - - def info(self): - return self.get(INFO, []) - - def success(self): - return self.get(SUCCESS, []) - - def warning(self): - return self.get(WARNING, []) - - def error(self): - return self.get(ERROR, []) - diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 3ab833be8..45a63dc8b 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -120,34 +120,6 @@ class ITemplateRenderer(IRenderer): accepts arbitrary keyword arguments and returns a string or unicode object """ -class IFlashMessages(Interface): - """ Dictionary-like object which maps flash category names to lists of - flash messages. Also supports an API for obtaining classes of flash - message lists.""" - def custom(name): - """ Return a sequence of custom-category flash messages or an empty - list if no messages of this custom category existed in the queue.""" - - def debug(): - """ Return a sequence of flash.DEBUG category flash messages or an - empty list if no flash.DEBUG messages existed in the queue.""" - - def info(): - """ Return a sequence of flash.INFO category flash messages or an - empty list if no flash.INFO messages existed in the queue.""" - - def success(): - """ Return a sequence of flash.SUCCESS category flash messages or an - empty list if no flash.SUCCESS messages existed in the queue.""" - - def warning(): - """ Return a sequence of flash.WARNING category flash messages or an - empty list if no flash.WARNING messages existed in the queue.""" - - def error(): - """ Return a sequence of flash.ERROR category flash messages or an - empty list if no flash.ERROR messages existed in the queue.""" - # internal interfaces class IRequest(Interface): @@ -488,17 +460,24 @@ class ISession(Interface): the sessioning machinery to notice the mutation of the internal dictionary.""" - def flash(msg, category='info', queue_name=''): - """ Push a flash message onto the stack related to the category and - queue name. Multiple flash message queues can be managed by passing - an optional ``queue_name``. Default category names are 'debug', - 'info', 'success', 'warning', and 'error' (these have constant names - importable from the ``pyramid.flash`` module). A custom category - name is also permitted.""" - - def unflash(queue_name=''): - """ Pop a queue from the flash message storage. This method returns - an object which implements ``pyramid.interfaces.IFlashMessages``""" + def flash(msg, queue='', allow_duplicate=True): + """ Push a flash message onto the end of the flash queue represented + by ``queue``. An alternate flash message queue can used by passing + an optional ``queue``, which must be a string. If + ``allow_duplicate`` is false, if the ``msg`` already exists in the + queue, it will not be readded.""" + + def pop_flash(queue=''): + """ Pop a queue from the flash storage. The queue is removed from + flash storage after this message is called. The queue is returned; + it is a list of flash messages added by + :meth:`pyramid.interfaces.ISesssion.flash`""" + + def peek_flash(queue=''): + """ Peek at a queue in the flash storage. The queue remains in + flash storage after this message is called. The queue is returned; + it is a list of flash messages added by + :meth:`pyramid.interfaces.ISesssion.flash`""" # mapping methods diff --git a/pyramid/session.py b/pyramid/session.py index 8c3bd1897..97db09e98 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -18,7 +18,6 @@ import base64 from zope.interface import implements from pyramid.interfaces import ISession -from pyramid import flash def manage_accessed(wrapped): """ Decorator which causes a cookie to be set when a wrapped @@ -170,15 +169,19 @@ def UnencryptedCookieSessionFactoryConfig( # flash API methods @manage_accessed - def flash(self, msg, category=flash.INFO, queue_name=''): - storage = self.setdefault('_f_' + queue_name, {}) - category = storage.setdefault(category, []) - category.append(msg) + def flash(self, msg, queue='', allow_duplicate=True): + storage = self.setdefault('_f_' + queue, []) + if allow_duplicate or (msg not in storage): + storage.append(msg) @manage_accessed - def unflash(self, queue_name=''): - storage = self.pop('_f_' + queue_name, {}) - return flash.FlashMessages(storage) + def pop_flash(self, queue=''): + storage = self.pop('_f_' + queue, []) + return storage + + def peek_flash(self, queue=''): + storage = self.get('_f_' + queue, []) + return storage # non-API methods def _set_cookie(self, response): diff --git a/pyramid/tests/test_flash.py b/pyramid/tests/test_flash.py deleted file mode 100644 index cce01d45b..000000000 --- a/pyramid/tests/test_flash.py +++ /dev/null @@ -1,81 +0,0 @@ -import unittest - -class TestFlashMessages(unittest.TestCase): - def _getTargetClass(self): - from pyramid.flash import FlashMessages - return FlashMessages - - def _makeOne(self, *arg, **kw): - cls = self._getTargetClass() - return cls(*arg, **kw) - - def test_class_conforms(self): - from zope.interface.verify import verifyClass - from pyramid.interfaces import IFlashMessages - verifyClass(IFlashMessages, self._getTargetClass()) - - def test_instance_conforms(self): - from zope.interface.verify import verifyObject - from pyramid.interfaces import IFlashMessages - messages = self._makeOne() - verifyObject(IFlashMessages, messages) - - def test_debug_filled(self): - from pyramid import flash - expected = ['one', 'two'] - messages = self._makeOne({flash.DEBUG:expected}) - self.assertEqual(messages.debug(), expected) - - def test_debug_empty(self): - messages = self._makeOne() - self.assertEqual(messages.debug(), []) - - def test_info_filled(self): - from pyramid import flash - expected = ['one', 'two'] - messages = self._makeOne({flash.INFO:expected}) - self.assertEqual(messages.info(), expected) - - def test_info_empty(self): - messages = self._makeOne() - self.assertEqual(messages.info(), []) - - def test_success_filled(self): - from pyramid import flash - expected = ['one', 'two'] - messages = self._makeOne({flash.SUCCESS:expected}) - self.assertEqual(messages.success(), expected) - - def test_success_empty(self): - messages = self._makeOne() - self.assertEqual(messages.success(), []) - - def test_warning_filled(self): - from pyramid import flash - expected = ['one', 'two'] - messages = self._makeOne({flash.WARNING:expected}) - self.assertEqual(messages.warning(), expected) - - def test_warning_empty(self): - messages = self._makeOne() - self.assertEqual(messages.warning(), []) - - def test_error_filled(self): - from pyramid import flash - expected = ['one', 'two'] - messages = self._makeOne({flash.ERROR:expected}) - self.assertEqual(messages.error(), expected) - - def test_error_empty(self): - messages = self._makeOne() - self.assertEqual(messages.error(), []) - - def test_custom_filled(self): - expected = ['one', 'two'] - messages = self._makeOne({'custom':expected}) - self.assertEqual(messages.custom('custom'), expected) - - def test_custom_empty(self): - messages = self._makeOne() - self.assertEqual(messages.custom('custom'), []) - diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index bfaa6cd97..930e4697b 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -117,55 +117,52 @@ class TestUnencryptedCookieSession(unittest.TestCase): session = self._makeOne(request) session.flash('msg1') session.flash('msg2') - self.assertEqual(dict(session['_f_']), - {'info':['msg1', 'msg2']}) + self.assertEqual(session['_f_'], ['msg1', 'msg2']) def test_flash_mixed(self): - from pyramid import flash request = testing.DummyRequest() session = self._makeOne(request) - session.flash('warn1', flash.WARNING) - session.flash('warn2', flash.WARNING) - session.flash('err1', flash.ERROR) - session.flash('err2', flash.ERROR) - self.assertEqual(dict(session['_f_']), - {flash.WARNING:['warn1', 'warn2'], - flash.ERROR:['err1', 'err2']}) + session.flash('warn1', 'warn') + session.flash('warn2', 'warn') + session.flash('err1', 'error') + session.flash('err2', 'error') + self.assertEqual(session['_f_warn'], ['warn1', 'warn2']) - def test_flash_with_nondefault_queue(self): - from pyramid import flash + def test_pop_flash_default_queue(self): request = testing.DummyRequest() session = self._makeOne(request) - session.flash('one_1', queue_name='one') - session.flash('one_2', queue_name='one') - session.flash('two_1', queue_name='two') - session.flash('two_2', queue_name='two') - self.assertEqual(dict(session['_f_one']), - {flash.INFO:['one_1', 'one_2']}) - self.assertEqual(dict(session['_f_two']), - {flash.INFO:['two_1', 'two_2']}) + queue = ['one', 'two'] + session['_f_'] = queue + result = session.pop_flash() + self.assertEqual(result, queue) + self.assertEqual(session.get('_f_'), None) - def test_unflash_default_queue(self): - from pyramid import flash - from pyramid.interfaces import IFlashMessages + def test_pop_flash_nodefault_queue(self): request = testing.DummyRequest() session = self._makeOne(request) - storage = {flash.INFO:['one', 'two']} - session['_f_'] = storage - result = session.unflash() - self.assertEqual(dict(result), storage) - self.failUnless(IFlashMessages.providedBy(result)) + queue = ['one', 'two'] + session['_f_error'] = queue + result = session.pop_flash('error') + self.assertEqual(result, queue) + self.assertEqual(session.get('_f_error'), None) - def test_unflash_nodefault_queue(self): - from pyramid import flash - from pyramid.interfaces import IFlashMessages + def test_peek_flash_default_queue(self): request = testing.DummyRequest() session = self._makeOne(request) - storage = {flash.INFO:['one', 'two']} - session['_f_one'] = storage - result = session.unflash('one') - self.assertEqual(dict(result), storage) - self.failUnless(IFlashMessages.providedBy(result)) + queue = ['one', 'two'] + session['_f_'] = queue + result = session.peek_flash() + self.assertEqual(result, queue) + self.assertEqual(session.get('_f_'), queue) + + def test_peek_flash_nodefault_queue(self): + request = testing.DummyRequest() + session = self._makeOne(request) + queue = ['one', 'two'] + session['_f_error'] = queue + result = session.peek_flash('error') + self.assertEqual(result, queue) + self.assertEqual(session.get('_f_error'), queue) class Test_manage_accessed(unittest.TestCase): def _makeOne(self, wrapped): |
