├── doc └── source │ ├── _static │ └── .gitkeep │ ├── modules │ ├── index.rst │ ├── decorators.rst │ ├── context_processors.rst │ ├── models.rst │ ├── storage │ │ ├── index.rst │ │ ├── cookie.rst │ │ └── session.rst │ ├── middleware.rst │ └── codec │ │ ├── json_impl.rst │ │ ├── pickle_impl.rst │ │ ├── json_zlib_impl.rst │ │ └── index.rst │ ├── _templates │ └── layout.html │ ├── installation.rst │ ├── custom_codecs.rst │ ├── index.rst │ ├── custom_storages.rst │ ├── getting_involved.rst │ ├── configuration.rst │ ├── changelog.rst │ ├── conf.py │ └── usage.rst ├── src └── djangoflash │ ├── tests │ ├── testproj │ │ ├── media │ │ │ └── test.css │ │ ├── app │ │ │ ├── models.py │ │ │ ├── __init__.py │ │ │ ├── urls.py │ │ │ ├── views.py │ │ │ └── tests.py │ │ ├── __init__.py │ │ ├── templates │ │ │ └── simple.html │ │ ├── urls.py │ │ ├── manage.py │ │ └── settings.py │ ├── __init__.py │ ├── suite.py │ ├── context_processors.py │ ├── decorators.py │ ├── storage.py │ ├── codec.py │ └── models.py │ ├── views.py │ ├── codec │ ├── json_impl.py │ ├── json_zlib_impl.py │ ├── pickle_impl.py │ └── __init__.py │ ├── __init__.py │ ├── decorators.py │ ├── storage │ ├── __init__.py │ ├── session.py │ └── cookie.py │ ├── context_processors.py │ ├── middleware.py │ └── models.py ├── .gitignore ├── AUTHORS ├── README.rst ├── setup.py ├── LICENSE ├── fabfile.py └── ez_setup.py /doc/source/_static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/media/test.css: -------------------------------------------------------------------------------- 1 | html * {} 2 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/app/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Create your models here. 4 | """ 5 | -------------------------------------------------------------------------------- /src/djangoflash/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Django-Flash doesn't provide any Django views. 4 | """ 5 | -------------------------------------------------------------------------------- /src/djangoflash/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Dummy file to make this directory a package. 4 | """ 5 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Dummy file to make this directory a package. 4 | """ 5 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Dummy file to make this directory a package. 4 | """ 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python bytecodes 2 | *.pyc 3 | 4 | # Build directory 5 | build/* 6 | dist/* 7 | doc/build/* 8 | 9 | # Egg info directory 10 | *.egg-info 11 | 12 | # Test coverage file 13 | .coverage 14 | -------------------------------------------------------------------------------- /doc/source/modules/index.rst: -------------------------------------------------------------------------------- 1 | .. _modulesindex: 2 | 3 | Django-Flash overview 4 | ===================== 5 | 6 | .. toctree:: 7 | 8 | models 9 | middleware 10 | context_processors 11 | decorators 12 | storage/index 13 | codec/index 14 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/templates/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Simple template 4 | 5 | 6 | 7 | Flash context: {{ flash.message }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /doc/source/modules/decorators.rst: -------------------------------------------------------------------------------- 1 | :mod:`djangoflash.decorators` --- Django-Flash decorators 2 | ========================================================= 3 | 4 | .. automodule:: djangoflash.decorators 5 | :members: 6 | :synopsis: Django-flash decorators 7 | 8 | 9 | .. seealso:: 10 | :ref:`modulesindex` 11 | 12 | -------------------------------------------------------------------------------- /doc/source/modules/context_processors.rst: -------------------------------------------------------------------------------- 1 | :mod:`djangoflash.context_processors` --- Django-Flash context processors 2 | ========================================================================= 3 | 4 | .. automodule:: djangoflash.context_processors 5 | :members: 6 | :synopsis: Django-flash context processors 7 | 8 | .. seealso:: 9 | :ref:`modulesindex` 10 | -------------------------------------------------------------------------------- /doc/source/modules/models.rst: -------------------------------------------------------------------------------- 1 | :mod:`djangoflash.models` --- Django-Flash model 2 | ================================================ 3 | 4 | .. automodule:: djangoflash.models 5 | :synopsis: Django-flash model 6 | 7 | 8 | :class:`FlashScope` Class 9 | ````````````````````````` 10 | 11 | .. autoclass:: FlashScope 12 | :show-inheritance: 13 | :members: 14 | 15 | 16 | .. seealso:: 17 | :ref:`modulesindex` 18 | 19 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | from django.conf import settings 4 | 5 | urlpatterns = patterns('', 6 | (r'', include('testproj.app.urls')), 7 | 8 | # django-flash needs to ignore requests to static files, in development mode 9 | (r'^media/(?P.*)$', 'django.views.static.serve', \ 10 | {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), 11 | ) 12 | -------------------------------------------------------------------------------- /doc/source/modules/storage/index.rst: -------------------------------------------------------------------------------- 1 | :mod:`djangoflash.storage` --- Flash storage backends 2 | ===================================================== 3 | 4 | .. automodule:: djangoflash.storage 5 | :members: 6 | :synopsis: Django-flash storage backend 7 | 8 | 9 | Built-in flash storage backends 10 | ``````````````````````````````` 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | 15 | session 16 | cookie 17 | 18 | .. seealso:: 19 | :ref:`modulesindex` 20 | -------------------------------------------------------------------------------- /doc/source/modules/middleware.rst: -------------------------------------------------------------------------------- 1 | .. _middleware: 2 | 3 | :mod:`djangoflash.middleware` --- Django-Flash middleware 4 | ========================================================= 5 | 6 | .. automodule:: djangoflash.middleware 7 | :synopsis: Django-flash middleware 8 | 9 | :class:`FlashMiddleware` Class 10 | `````````````````````````````` 11 | 12 | .. autoclass:: FlashMiddleware 13 | :show-inheritance: 14 | :members: 15 | 16 | 17 | .. seealso:: 18 | :ref:`modulesindex` 19 | -------------------------------------------------------------------------------- /doc/source/modules/codec/json_impl.rst: -------------------------------------------------------------------------------- 1 | .. _json_codec: 2 | 3 | :mod:`djangoflash.codec.json_impl` --- JSON-based codec implementation 4 | ====================================================================== 5 | 6 | .. automodule:: djangoflash.codec.json_impl 7 | :synopsis: JSON-based codec implementation 8 | 9 | 10 | :class:`CodecClass` Class 11 | ````````````````````````` 12 | 13 | .. autoclass:: CodecClass 14 | :show-inheritance: 15 | :members: 16 | 17 | 18 | .. seealso:: 19 | :ref:`modulesindex` 20 | 21 | -------------------------------------------------------------------------------- /doc/source/modules/storage/cookie.rst: -------------------------------------------------------------------------------- 1 | .. _storage_cookie: 2 | 3 | :mod:`djangoflash.storage.cookie` --- Cookie-based flash storage 4 | ================================================================ 5 | 6 | .. automodule:: djangoflash.storage.cookie 7 | :synopsis: Cookie-based flash storage 8 | 9 | 10 | :class:`FlashStorageClass` Class 11 | ```````````````````````````````` 12 | 13 | .. autoclass:: FlashStorageClass 14 | :show-inheritance: 15 | :members: 16 | 17 | 18 | .. seealso:: 19 | :ref:`modulesindex` 20 | 21 | -------------------------------------------------------------------------------- /doc/source/modules/codec/pickle_impl.rst: -------------------------------------------------------------------------------- 1 | .. _pickle_codec: 2 | 3 | :mod:`djangoflash.codec.pickle_impl` --- Pickle-based codec implementation 4 | ========================================================================== 5 | 6 | .. automodule:: djangoflash.codec.pickle_impl 7 | :synopsis: JSON-based codec implementation 8 | 9 | 10 | :class:`CodecClass` Class 11 | ````````````````````````` 12 | 13 | .. autoclass:: CodecClass 14 | :show-inheritance: 15 | :members: 16 | 17 | 18 | .. seealso:: 19 | :ref:`modulesindex` 20 | 21 | -------------------------------------------------------------------------------- /doc/source/modules/storage/session.rst: -------------------------------------------------------------------------------- 1 | .. _storage_session: 2 | 3 | :mod:`djangoflash.storage.session` --- Session-based flash storage 4 | ================================================================== 5 | 6 | .. automodule:: djangoflash.storage.session 7 | :synopsis: Session-based flash storage 8 | 9 | 10 | :class:`FlashStorageClass` Class 11 | ```````````````````````````````` 12 | 13 | .. autoclass:: FlashStorageClass 14 | :show-inheritance: 15 | :members: 16 | 17 | 18 | .. seealso:: 19 | :ref:`modulesindex` 20 | 21 | -------------------------------------------------------------------------------- /doc/source/modules/codec/json_zlib_impl.rst: -------------------------------------------------------------------------------- 1 | .. _json_zlib_codec: 2 | 3 | :mod:`djangoflash.codec.json_zlib_impl` --- JSON/zlib-based codec implementation 4 | ================================================================================ 5 | 6 | .. automodule:: djangoflash.codec.json_zlib_impl 7 | :synopsis: JSON/zlib-based codec implementation 8 | 9 | 10 | :class:`CodecClass` Class 11 | ````````````````````````` 12 | 13 | .. autoclass:: CodecClass 14 | :show-inheritance: 15 | :members: 16 | 17 | 18 | .. seealso:: 19 | :ref:`modulesindex` 20 | 21 | -------------------------------------------------------------------------------- /doc/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block footer %} 4 | {{ super() }} 5 | 9 | 14 | {% endblock %} 15 | 16 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/app/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | from testproj.app import views 4 | 5 | urlpatterns = patterns('', 6 | (r'^default/$', views.render_template), 7 | (r'^set_flash_var/$', views.set_flash_var), 8 | (r'^set_another_flash_var/$', views.set_another_flash_var), 9 | (r'^set_now_var/$', views.set_now_var), 10 | (r'^keep_var/$', views.keep_var), 11 | (r'^keep_var_decorator/$', views.keep_var_decorator), 12 | (r'^discard_var/$', views.discard_var), 13 | (r'^replace_flash/$', views.replace_flash), 14 | (r'^remove_flash/$', views.remove_flash), 15 | ) 16 | -------------------------------------------------------------------------------- /doc/source/modules/codec/index.rst: -------------------------------------------------------------------------------- 1 | :mod:`djangoflash.codec` --- Flash serialization codecs 2 | ======================================================= 3 | 4 | .. automodule:: djangoflash.codec 5 | :members: get_codec 6 | :synopsis: Codecs for data transport 7 | 8 | 9 | :class:`BaseCodec` Class 10 | ```````````````````````` 11 | 12 | .. autoclass:: BaseCodec 13 | :show-inheritance: 14 | :members: 15 | 16 | 17 | Built-in serialization codecs 18 | ````````````````````````````` 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | 23 | json_impl 24 | json_zlib_impl 25 | pickle_impl 26 | 27 | .. seealso:: 28 | :ref:`modulesindex` 29 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Django-Flash was originally created in late 2008 at Destaquenet Technology 2 | Solutions, a brazilian software development and consultancy startup. 3 | 4 | The PRIMARY AUTHORS are (and/or have been): 5 | 6 | Daniel Martins 7 | 8 | 9 | And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- 10 | people who have helped directly or indirectly to make Django-Flash much better 11 | (in alphabetic order): 12 | 13 | Chris Beaven 14 | Francesco Crippa 15 | Jeremy Sule 16 | Leah Culver 17 | Tobias McNulty 18 | -------------------------------------------------------------------------------- /src/djangoflash/codec/json_impl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides a JSON-based codec implementation. 4 | """ 5 | 6 | try: 7 | import json 8 | except ImportError: 9 | from django.utils import simplejson as json 10 | 11 | from djangoflash.codec import BaseCodec 12 | from djangoflash.models import FlashScope 13 | 14 | 15 | class CodecClass(BaseCodec): 16 | """JSON-based codec implementation. 17 | """ 18 | def __init__(self): 19 | """Returns a new JSON-based codec. 20 | """ 21 | BaseCodec.__init__(self) 22 | 23 | def encode(self, flash): 24 | """Encodes the given *flash* as a JSON string. 25 | """ 26 | return json.dumps(flash.to_dict()) 27 | 28 | def decode(self, encoded_flash): 29 | """Restores the *flash* from the given JSON string. 30 | """ 31 | return FlashScope(json.loads(encoded_flash)) 32 | -------------------------------------------------------------------------------- /src/djangoflash/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Django-flash 5 | ~~~~~~~~~~~~ 6 | 7 | Rails-like *flash* messages support for Django. 8 | 9 | :copyright: 2008-2009, Destaquenet Technology Solutions. 10 | :license: BSD. 11 | """ 12 | 13 | __version__ = '1.8' 14 | 15 | __author__ = 'Daniel Fernandes Martins' 16 | __email__ = 'daniel@destaquenet.com' 17 | 18 | 19 | def run_tests(verbosity=1): 20 | """Runs the tests. This function is useful when you want to check if an 21 | already installed version of Django-Flash (e.g. one that don't have a 22 | ``setup.py`` file) works as expected. Example:: 23 | 24 | $ python -c "import djangoflash; djangoflash.run_tests();" 25 | """ 26 | from djangoflash.tests import suite 27 | import unittest 28 | runner = unittest.TextTestRunner(verbosity=verbosity) 29 | unittest.main(module=suite, testRunner=runner) 30 | -------------------------------------------------------------------------------- /src/djangoflash/decorators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides decorators to simplify common tasks. 4 | """ 5 | 6 | from djangoflash.context_processors import CONTEXT_VAR 7 | 8 | 9 | def keep_messages(*keys): 10 | """Prevents specific values from being removed during the processing of 11 | the decorated view. If this decorator is used with no args, the entire 12 | flash is preserved. 13 | """ 14 | def _keep_messages(view_method): 15 | def _wrapped_view_method(request, *args, **kwargs): 16 | if hasattr(request, CONTEXT_VAR): 17 | flash = getattr(request, CONTEXT_VAR) 18 | flash.keep(*keys) 19 | return view_method(request, *args, **kwargs) 20 | return _wrapped_view_method 21 | 22 | if len(keys) == 1 and callable(keys[0]): 23 | view_method = keys[0] 24 | keys = [] 25 | return _keep_messages(view_method) 26 | return _keep_messages 27 | -------------------------------------------------------------------------------- /src/djangoflash/codec/json_zlib_impl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides a JSON-based codec implementation that uses the 4 | :mod:`zlib` module to reduce the encoded flash footprint. 5 | """ 6 | 7 | import zlib 8 | 9 | from djangoflash.codec.json_impl import CodecClass as JSONCodecClass 10 | from djangoflash.models import FlashScope 11 | 12 | 13 | class CodecClass(JSONCodecClass): 14 | """JSON/zlib-based codec implementation. 15 | """ 16 | def __init__(self): 17 | """Returns a new JSON/zlib-based codec. 18 | """ 19 | JSONCodecClass.__init__(self) 20 | 21 | def encode(self, flash): 22 | """Encodes the given *flash* as a zlib compressed JSON string. 23 | """ 24 | return zlib.compress(JSONCodecClass.encode(self, flash)) 25 | 26 | def decode(self, encoded_flash): 27 | """Restores the *flash* from the given zlib compressed JSON string. 28 | """ 29 | return JSONCodecClass.decode(self, zlib.decompress(encoded_flash)) 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django-Flash 2 | ============ 3 | 4 | Django-Flash is a simple Django extension that provides support for Rails_-like 5 | *flash* messages. 6 | 7 | The *flash* is a temporary storage mechanism that looks like a Python 8 | dictionary, so you can store values associated with keys and later retrieve 9 | them. It has one special property: by default, values stored into the *flash* 10 | during the processing of a request will be available during the processing of 11 | the immediately following request. Once that second request has been 12 | processed, those values are removed automatically from the storage. 13 | 14 | This is an open source project licenced under the terms of The 15 | `BSD License`_ and sponsored by Destaquenet Technology Solutions, a 16 | brazilian software development and consultancy startup. 17 | 18 | 19 | Installation and Usage 20 | ---------------------- 21 | 22 | Please read the `online documentation `_ 23 | for further instructions. 24 | 25 | 26 | .. _BSD License: http://www.opensource.org/licenses/bsd-license.php 27 | .. _Rails: http://www.rubyonrails.org/ 28 | -------------------------------------------------------------------------------- /src/djangoflash/codec/pickle_impl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides a Pickle-based codec implementation. 4 | 5 | .. warning:: 6 | The use of this codec is not recommended since the 7 | `Pickle documentation `_ itself 8 | clearly states that it's not intended to be secure against erroneous or 9 | maliciously constructed data. 10 | """ 11 | 12 | try: 13 | import cPickle as pickle 14 | except ImportError: 15 | import pickle 16 | 17 | from djangoflash.codec import BaseCodec 18 | 19 | 20 | class CodecClass(BaseCodec): 21 | """Pickle-based codec implementation. 22 | """ 23 | def __init__(self): 24 | """Returns a new Pickle-based codec. 25 | """ 26 | BaseCodec.__init__(self) 27 | 28 | def encode(self, flash): 29 | """Encodes the given *flash* as a Pickle dump string. 30 | """ 31 | return pickle.dumps(flash, pickle.HIGHEST_PROTOCOL) 32 | 33 | def decode(self, encoded_flash): 34 | """Restores the *flash* from the given Pickle dump string. 35 | """ 36 | return pickle.loads(encoded_flash) 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import ez_setup 5 | ez_setup.use_setuptools() 6 | 7 | from setuptools import setup, find_packages 8 | 9 | setup( 10 | name = 'django-flash', 11 | version = '1.8', 12 | author = 'Daniel Fernandes Martins', 13 | author_email = 'daniel@destaquenet.com', 14 | description = 'Django-flash is a simple Django extension which provides support for Rails-like flash messages.', 15 | license = 'BSD', 16 | platforms = ['Any'], 17 | keywords = ['django', 'flash', 'session', 'scope', 'context', 'contrib'], 18 | url = 'http://djangoflash.destaquenet.com/', 19 | classifiers = [ 20 | 'Development Status :: 5 - Production/Stable', 21 | 'Intended Audience :: Developers', 22 | 'License :: OSI Approved :: BSD License', 23 | 'Natural Language :: English', 24 | 'Operating System :: OS Independent', 25 | 'Programming Language :: Python', 26 | 'Framework :: Django', 27 | 'Topic :: Internet :: WWW/HTTP' 28 | ], 29 | install_requires = ['Django>=1.0_final'], 30 | packages = find_packages('src'), 31 | package_dir = {'':'src'}, 32 | include_package_data = True, 33 | zip_safe = False, 34 | test_suite = 'djangoflash.tests.suite', 35 | ) 36 | -------------------------------------------------------------------------------- /src/djangoflash/tests/suite.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Project's test suite. 4 | """ 5 | 6 | import sys 7 | 8 | # Adds the Django test project to system path 9 | from django.core.management import setup_environ 10 | import djangoflash.tests.testproj.settings as project_settings 11 | sys.path.insert(0, setup_environ(project_settings)) 12 | 13 | # Imports unit tests 14 | from context_processors import * 15 | from decorators import * 16 | from models import * 17 | from storage import * 18 | from codec import * 19 | 20 | # Now, the integration tests, which depends on SQLite 21 | has_sqlite = True 22 | 23 | try: 24 | import sqlite3 25 | except ImportError: 26 | try: 27 | import pysqlite2 28 | has_sqlite = True 29 | except ImportError: 30 | pass 31 | 32 | # Runs the integration tests if at least one module was found 33 | if has_sqlite: 34 | # Bootstraps integration environment 35 | import django.test.utils as test_utils 36 | from django.db import connection 37 | test_utils.setup_test_environment() 38 | connection.creation.create_test_db() 39 | 40 | # Imports integration tests 41 | from testproj.app.tests import * 42 | else: 43 | print >> sys.stderr, 'Integration: module "sqlite3" (or "pysqlite2") is required... SKIPPED' 44 | -------------------------------------------------------------------------------- /src/djangoflash/storage/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package provides some built-in flash storage backends used to persist 4 | the *flash* contents across requests. 5 | """ 6 | 7 | from django.conf import settings 8 | 9 | 10 | # Alias for use in settings file --> name of module in "storage" directory. 11 | # Any storage that is not in this dictionary is treated as a Python import 12 | # path to a custom storage. 13 | 14 | # This config style is deprecated in Django 1.2, but we'll continue to support 15 | # these alias for some more time. 16 | STORAGES = { 17 | 'session': 'session', 18 | 'cookie': 'cookie', 19 | } 20 | 21 | def get_storage(module): 22 | """Creates and returns the flash storage backend defined in the given 23 | module path (ex: ``"myapp.mypackage.mymodule"``). The argument can also 24 | be an alias to a built-in storage backend, such as ``"session"`` or 25 | ``"cookie"``. 26 | """ 27 | if module in STORAGES: 28 | mod = __import__('djangoflash.storage.%s' % STORAGES[module], \ 29 | {}, {}, ['']) 30 | else: 31 | mod = __import__(module, {}, {}, ['']) 32 | return getattr(mod, 'FlashStorageClass')() 33 | 34 | # Get the flash storage specified in the project's settings. Use the session 35 | # storage by default (for both security and backward compatibility reasons). 36 | storage = get_storage(getattr(settings, 'FLASH_STORAGE', 'session')) 37 | -------------------------------------------------------------------------------- /src/djangoflash/storage/session.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides a session-based flash storage backend. 4 | 5 | Since this backend relies on the user's session, you need to include the 6 | :class:`SessionMiddleware` class to the ``MIDDLEWARE_CLASSES`` section of your 7 | project's ``settings.py`` file:: 8 | 9 | MIDDLEWARE_CLASSES = ( 10 | 'django.contrib.sessions.middleware.SessionMiddleware', 11 | 'djangoflash.middleware.FlashMiddleware', 12 | ) 13 | 14 | .. seealso:: 15 | :ref:`configuration` 16 | """ 17 | 18 | class FlashStorageClass(object): 19 | """Session-based flash storage backend. 20 | """ 21 | def __init__(self): 22 | """Returns a new session-based flash storage backend. 23 | """ 24 | self._key = '_djflash_session' 25 | 26 | def set(self, flash, request, response): 27 | """Stores the given :class:`FlashScope` object in the session. 28 | """ 29 | if hasattr(request, 'session'): 30 | if flash: 31 | request.session[self._key] = flash 32 | elif self._key in request.session: 33 | del request.session[self._key] 34 | 35 | def get(self, request): 36 | """Returns :class:`FlashScope` object stored in the session. 37 | """ 38 | if hasattr(request, 'session') and self._key in request.session: 39 | return request.session[self._key] 40 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | There are several ways to download and install Django-Flash: 5 | 6 | **Via PyPI** 7 | 8 | Execute the following command line to download and install the latest 9 | stable version from CheeseShop_:: 10 | 11 | $ easy_install -U django-flash 12 | 13 | 14 | Follow `these instructions `_ to 15 | install SetupTools if you don't have it already. 16 | 17 | 18 | **Via GitHub** 19 | 20 | If you are a Git_ user and want to take a closer look at the project's 21 | source code, you would rather clone our 22 | `public repository `_ 23 | instead:: 24 | 25 | $ git clone git://github.com/danielfm/django-flash.git 26 | $ cd django-flash 27 | $ python setup.py install 28 | 29 | 30 | **Zip file/tarball** 31 | 32 | Django-Flash is also available for download as 33 | `compressed archives `_ 34 | (either ``zip`` and ``tgz``). After unzip/untar the archive, execute the 35 | following command to install Django-Flash:: 36 | 37 | $ python setup.py install 38 | 39 | 40 | **Manually** 41 | 42 | To add Django-Flash to your project as a bundled library, just add the 43 | ``djangoflash`` directory into your project along with the other apps. 44 | 45 | 46 | .. _CheeseShop: http://pypi.python.org/pypi 47 | .. _Git: http://git-scm.com/ 48 | 49 | -------------------------------------------------------------------------------- /doc/source/custom_codecs.rst: -------------------------------------------------------------------------------- 1 | .. _custom_codecs: 2 | 3 | Creating a custom serialization codec 4 | ------------------------------------- 5 | 6 | Since :ref:`version 1.7 `, Django-Flash supports custom flash 7 | serialization codecs. 8 | 9 | By default, Django-Flash provides three built-in codecs: 10 | 11 | * :mod:`djangoflash.codec.json_impl` -- JSON-based codec (default); 12 | * :mod:`djangoflash.codec.json_zlib_impl` -- JSON/zlib-based codec; 13 | * :mod:`djangoflash.codec.pickle_impl` -- Pickle-based codec; 14 | 15 | The good news is that you can create your own codec if the existing ones are 16 | getting in your way. To do so, the first thing you need to do is create a 17 | Python module with a class called :class:`CodecClass`:: 18 | 19 | # Let's suppose this module is called 'myproj.djangoflash.custom' 20 | 21 | from djangoflash.codec import BaseCodec 22 | 23 | class CodecClass(BaseCodec): 24 | def __init__(self): 25 | BaseCodec.__init__(self) 26 | 27 | def encode(self, flash): 28 | pass 29 | 30 | def decode(self, encoded_flash): 31 | pass 32 | 33 | 34 | Note that custom codecs must extend the :class:`djangoflash.codec.BaseCodec` 35 | class direct or indirectly. 36 | 37 | Finally, to use your custom codec, add the following setting to your project's 38 | ``settings.py`` file:: 39 | 40 | FLASH_CODEC = 'myproj.djangoflash.custom' # Path to module 41 | 42 | 43 | .. seealso:: 44 | :ref:`configuration` 45 | 46 | -------------------------------------------------------------------------------- /src/djangoflash/tests/context_processors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """djangoflash.context_processors test cases. 4 | """ 5 | 6 | from unittest import TestCase 7 | 8 | from django.core.exceptions import SuspiciousOperation 9 | from django.http import HttpRequest 10 | 11 | from djangoflash.context_processors import CONTEXT_VAR, flash 12 | from djangoflash.models import FlashScope 13 | 14 | 15 | class FlashContextProcessorTestCase(TestCase): 16 | """Tests the context processor used to expose the flash to view templates. 17 | """ 18 | def setUp(self): 19 | self.request = HttpRequest() 20 | self.scope = FlashScope(); 21 | setattr(self.request, CONTEXT_VAR, self.scope); 22 | 23 | def test_expose_flash(self): 24 | """FlashContextProcessor: should expose the flash to view templates. 25 | """ 26 | self.assertEqual(flash(self.request), {CONTEXT_VAR:self.scope}) 27 | 28 | def test_expose_inexistent_flash(self): 29 | """FlashContextProcessor: should fail when there's no flash available. 30 | """ 31 | delattr(self.request, CONTEXT_VAR) 32 | self.assertTrue(isinstance(flash(self.request)[CONTEXT_VAR], \ 33 | FlashScope)) 34 | 35 | def test_expose_invalid_flash(self): 36 | """FlashContextProcessor: should fail when exposing an invalid object as being the flash. 37 | """ 38 | self.request.flash = 'Invalid object' 39 | self.assertRaises(SuspiciousOperation, flash, self.request) 40 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for testproj project. 2 | 3 | import os 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | # For Django < 1.2 9 | DATABASE_ENGINE = 'sqlite3' 10 | DATABASE_NAME = 'db' 11 | 12 | # For Django >= 1.2 13 | DATABASES = { 14 | 'default': { 15 | 'ENGINE':'django.db.backends.sqlite3', 16 | 'NAME': 'db' 17 | } 18 | } 19 | 20 | SECRET_KEY = 'g9b@q$)=^xd2g@-7pg=j=h3*8+xd#hgn-9je@iq5_m#seg&d1y' 21 | 22 | MEDIA_URL = '/media/' 23 | ADMIN_MEDIA_PREFIX = '/admin/' 24 | MEDIA_ROOT = os.path.normpath(os.path.dirname(__file__) + '/media/') 25 | 26 | TEMPLATE_LOADERS = ( 27 | 'django.template.loaders.filesystem.load_template_source', 28 | ) 29 | 30 | MIDDLEWARE_CLASSES = ( 31 | 'django.middleware.common.CommonMiddleware', 32 | 'django.contrib.sessions.middleware.SessionMiddleware', 33 | 'djangoflash.middleware.FlashMiddleware', 34 | ) 35 | 36 | TEMPLATE_CONTEXT_PROCESSORS = ( 37 | 'djangoflash.context_processors.flash', 38 | 'django.core.context_processors.media', 39 | ) 40 | 41 | ROOT_URLCONF = 'testproj.urls' 42 | 43 | TEMPLATE_DIRS = ( 44 | os.path.normpath(os.path.dirname(__file__) + '/templates'), 45 | ) 46 | 47 | INSTALLED_APPS = ( 48 | 'app', 49 | 'django.contrib.sessions', 50 | ) 51 | 52 | 53 | # Settings introduced by Django-Flash: 54 | 55 | # FLASH_IGNORE_MEDIA = DEBUG # True, False 56 | # FLASH_STORAGE = 'session' # 'session, 'cookie', 'path.to.module' 57 | # FLASH_CODEC = 'json' # 'json', 'json_zlib', 'pickle', 'path.to.module' 58 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/app/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | 3 | from django.core.urlresolvers import reverse 4 | from django.http import HttpResponse, HttpResponseRedirect 5 | from django.shortcuts import render_to_response 6 | from django.template import RequestContext 7 | 8 | from djangoflash.decorators import keep_messages 9 | 10 | 11 | def render_template(request): 12 | return render_to_response('simple.html', \ 13 | context_instance=RequestContext(request)) 14 | 15 | def set_flash_var(request): 16 | request.flash['message'] = 'Message' 17 | return render_template(request) 18 | 19 | def set_another_flash_var(request): 20 | request.flash['anotherMessage'] = 'Another message' 21 | return render_template(request) 22 | 23 | def set_now_var(request): 24 | request.flash.now['message'] = 'Message' 25 | return render_template(request) 26 | 27 | def keep_var(request): 28 | request.flash.keep('message') 29 | return render_template(request) 30 | 31 | @keep_messages('message') 32 | def keep_var_decorator(request): 33 | return render_template(request) 34 | 35 | def discard_var(request): 36 | # Should behave the same way 'flash.now' does 37 | request.flash['message'] = 'Message' 38 | request.flash.discard('message') 39 | return render_template(request) 40 | 41 | def replace_flash(request): 42 | request.flash = "Replacing the flash with a string" 43 | return render_template(request) 44 | 45 | def remove_flash(request): 46 | # I've seen this happen, I'm not kidding... :) 47 | del request.flash 48 | return render_template(request) 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2010, Destaquenet Technology Solutions 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Destaquenet Technology Solutions nor the names of 15 | its contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/djangoflash/storage/cookie.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides a cookie-based flash storage backend. 4 | 5 | .. warning:: 6 | The actual :class:`FlashScope` object is sent back to the user in a cookie. 7 | Although some encryption is performed to help spot when the flash data is 8 | modified by third-parties, this backend should be avoided when sensitive 9 | information is stored in the *flash*. 10 | 11 | .. warning:: 12 | Although in general user agents' cookie support should have no fixed limits, 13 | according to `RFC-2965 `_, section 5.3, 14 | all implementations must support at least 4096 bytes per cookie. So be 15 | careful about the amount of data you store in the *flash* when using this 16 | storage backend. 17 | """ 18 | 19 | from djangoflash.codec import codec 20 | 21 | 22 | class FlashStorageClass(object): 23 | """Cookie-based flash storage backend. 24 | """ 25 | 26 | def __init__(self): 27 | """Returns a new cookie-based flash storage backend. 28 | """ 29 | self._key = '_djflash_cookie' 30 | 31 | def set(self, flash, request, response): 32 | """Stores the given :class:`FlashScope` object in a cookie. 33 | """ 34 | if flash: 35 | response.set_cookie(self._key, codec.encode_and_sign(flash)) 36 | elif self._key in request.COOKIES: 37 | response.delete_cookie(self._key) 38 | 39 | def get(self, request): 40 | """Returns :class:`FlashScope` object stored in a cookie. 41 | """ 42 | data = request.COOKIES.get(self._key) 43 | if data: 44 | return codec.decode_signed(data) 45 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Django-Flash --- Rails-like *flash* messages support for Django 2 | =============================================================== 3 | 4 | Django-Flash is a simple Django extension that provides support for Rails_-like 5 | *flash* messages. 6 | 7 | The *flash* is a temporary storage mechanism that looks like a Python 8 | dictionary, so you can store values associated with keys and later retrieve 9 | them. It has one special property: by default, values stored into the *flash* 10 | during the processing of a request will be available during the processing of 11 | the immediately following request. Once that second request has been 12 | processed, those values are removed automatically from the storage. 13 | 14 | This is an open source project licenced under the terms of The 15 | `BSD License`_ and sponsored by `Destaquenet Technology Solutions`_, a 16 | brazilian software development and consultancy startup. 17 | 18 | .. seealso:: 19 | `PDF version `_ of 20 | this documentation. 21 | 22 | 23 | Documentation contents 24 | ---------------------- 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | 29 | installation 30 | configuration 31 | usage 32 | custom_storages 33 | custom_codecs 34 | modules/index 35 | getting_involved 36 | changelog 37 | 38 | 39 | Indices and tables 40 | ------------------ 41 | 42 | * :ref:`genindex` 43 | * :ref:`modindex` 44 | * :ref:`search` 45 | 46 | 47 | .. _BSD License: http://www.opensource.org/licenses/bsd-license.php 48 | .. _Django: http://www.djangoproject.org/ 49 | .. _Rails: http://www.rubyonrails.org/ 50 | .. _Destaquenet Technology Solutions: http://www.destaquenet.com/ 51 | 52 | -------------------------------------------------------------------------------- /doc/source/custom_storages.rst: -------------------------------------------------------------------------------- 1 | .. _custom_storages: 2 | 3 | Creating a custom flash storage backend 4 | --------------------------------------- 5 | 6 | Since :ref:`version 1.5`, Django-Flash supports custom flash 7 | storage backends. 8 | 9 | By default, Django-flash provides two built-in storage backends: 10 | 11 | * :mod:`djangoflash.storage.session` -- Session-based storage (default); 12 | * :mod:`djangoflash.storage.cookie` -- Cookie-based storage; 13 | 14 | The good news is that you can create your own storage backend if the existing 15 | ones are getting in your way. To do so, the first thing you need to do is 16 | create a Python module with a class called :class:`FlashStorageClass`:: 17 | 18 | # Let's suppose this module is called 'myproj.djangoflash.custom' 19 | 20 | # You can use the serialization codec configured by the user 21 | from djangoflash.codec import codec 22 | 23 | class FlashStorageClass(object): 24 | def _is_flash_stored(self, request): 25 | # This method checks whether the flash is already stored 26 | pass 27 | 28 | def set(self, flash, request, response): 29 | if flash: 30 | # Store the flash 31 | pass 32 | elif self._is_flash_stored(request): 33 | # Flash is null or empty, so remove the already stored flash 34 | pass 35 | 36 | def get(self, request): 37 | if self._is_flash_stored(request): 38 | # Return the stored flash 39 | pass 40 | 41 | 42 | Then, to use your custom flash storage backend, add the following setting 43 | to your project's ``settings.py`` file:: 44 | 45 | FLASH_STORAGE = 'myproj.djangoflash.custom' # Path to module 46 | 47 | 48 | .. seealso:: 49 | :ref:`configuration` 50 | 51 | -------------------------------------------------------------------------------- /src/djangoflash/context_processors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This module provides the context processor that exposes 5 | :class:`djangoflash.models.FlashScope` objects to view templates. 6 | 7 | To plug this context processor to your Django project, edit your project's 8 | ``settings.py`` file as follows:: 9 | 10 | TEMPLATE_CONTEXT_PROCESSORS = ( 11 | 'djangoflash.context_processors.flash', 12 | ) 13 | 14 | 15 | Doing this, the view templates will be able to access the *flash* contents 16 | using the ``flash`` context variable. 17 | 18 | .. warning:: 19 | Your views should use the :class:`RequestContext` class to render the 20 | templates, otherwise the ``flash`` variable (along with *all* other 21 | variables provided by other context processors) won't be available to them. 22 | Please read the 23 | `Django docs `_ 24 | for further instructions. 25 | 26 | """ 27 | 28 | from django.core.exceptions import SuspiciousOperation 29 | from djangoflash.models import FlashScope 30 | 31 | 32 | # Name of the variable used to keep FlashScope objects both as an attribute 33 | # django.http.HttpRequest and the template context. 34 | CONTEXT_VAR = 'flash' 35 | 36 | def flash(request): 37 | """This context processor gets the :class:`FlashScope` object from the 38 | current *request* and adds it to the template context: 39 | 40 | .. code-block:: html+django 41 | 42 | 43 | 44 | 45 | request.flash['message'] = {{ flash.message }} 46 | 47 | 48 | 49 | """ 50 | flash_scope = None 51 | try: 52 | flash_scope = getattr(request, CONTEXT_VAR) 53 | if not isinstance(flash_scope, FlashScope): 54 | raise SuspiciousOperation('Invalid flash: %s' % repr(flash_scope)) 55 | except AttributeError: 56 | # Exposes an empty flash when none is available 57 | flash_scope = FlashScope() 58 | return {CONTEXT_VAR: flash_scope} 59 | -------------------------------------------------------------------------------- /doc/source/getting_involved.rst: -------------------------------------------------------------------------------- 1 | Getting Involved 2 | ================ 3 | 4 | As with any open source project, there are several ways you can help: 5 | 6 | * Report bugs, feature requests and other issues in the 7 | `issue tracking system `_; 8 | * Submit patches to reported issues (both those you find, or that others have 9 | filed); 10 | * Help with the documentation by pointing out areas that are lacking or unclear, 11 | and if you are so inclined, submitting patches to correct it; 12 | * Improve the overall project quality by suggesting refactorings and improving 13 | the test cases. A great way to learn -- and in turn give value back to the 14 | community -- is to review someone else's code. So, we invite you to review 15 | ours; 16 | * Create and share packages to make it even easier to distribute Django-Flash 17 | to other users of your favourite Distribution or Operating System; 18 | * Write about Django-Flash in your blog or personal web site. Let your friends 19 | know about this project. 20 | 21 | Your participation is much appreciated. Keep up with Django-Flash development on 22 | `Github `_. 23 | 24 | 25 | How do I join the team? 26 | ----------------------- 27 | 28 | Django-Flash is a very mature project and it's probably not going to get lots of 29 | new features. But those that the developers notice participating to a high 30 | extent will be invited to join the team as a committer. 31 | 32 | This is as much based on personality and ability to work with other developers 33 | and the community as it is with proven technical ability. Being unhelpful to 34 | other users, or obviously looking to become a committer for bragging rights and 35 | nothing else is frowned upon, as is asking to be made a committer without having 36 | contributed sufficiently to be invited. 37 | 38 | 39 | Contact information 40 | ------------------- 41 | 42 | :Author: Daniel Fernandes Martins 43 | :Company: `Destaquenet Technology Solutions`_ 44 | 45 | 46 | .. _Destaquenet Technology Solutions: http://www.destaquenet.com/ 47 | 48 | -------------------------------------------------------------------------------- /src/djangoflash/codec/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package provides some built-in flash serialization codecs. 4 | """ 5 | 6 | import base64 7 | 8 | from django.conf import settings 9 | from django.utils.hashcompat import md5_constructor 10 | 11 | 12 | class BaseCodec(object): 13 | """Base codec implementation. All codec implementations must extend this 14 | class. 15 | """ 16 | def __init__(self): 17 | """Returns a new :class:`BaseCodec` object. 18 | """ 19 | pass 20 | 21 | def encode(self, flash): 22 | """Empty implementation that raises :class:`NotImplementedError`. 23 | """ 24 | raise NotImplementedError 25 | 26 | def decode(self, encoded_flash): 27 | """Empty implementation that raises :class:`NotImplementedError`. 28 | """ 29 | raise NotImplementedError 30 | 31 | def encode_and_sign(self, flash): 32 | """Returns an encoded-and-signed version of the given *flash*. 33 | """ 34 | encoded = self.encode(flash) 35 | encoded_md5 = md5_constructor(encoded + settings.SECRET_KEY).hexdigest() 36 | return base64.encodestring(encoded + encoded_md5) 37 | 38 | def decode_signed(self, encoded_flash): 39 | """Restores the *flash* object from the given encoded-and-signed data. 40 | """ 41 | decoded_flash = base64.decodestring(encoded_flash) 42 | encoded, tamper_check = decoded_flash[:-32], decoded_flash[-32:] 43 | hex_digest = md5_constructor(encoded + settings.SECRET_KEY).hexdigest() 44 | if hex_digest != tamper_check: 45 | from django.core.exceptions import SuspiciousOperation 46 | raise SuspiciousOperation('User tampered with data.') 47 | try: 48 | return self.decode(encoded) 49 | except: 50 | # Errors might happen when decoding. Return None if that's the case 51 | return None 52 | 53 | 54 | # Alias for use in settings file --> name of module in "codec" directory. 55 | # Any codec that is not in this dictionary is treated as a Python import 56 | # path to a custom codec. 57 | 58 | # This config style is deprecated in Django 1.2, but we'll continue to support 59 | # these alias for some more time. 60 | CODECS = { 61 | 'json': 'json_impl', 62 | 'json_zlib': 'json_zlib_impl', 63 | 'pickle': 'pickle_impl', 64 | } 65 | 66 | def get_codec(module): 67 | """Creates and returns the codec defined in the given module path 68 | (ex: ``"myapp.mypackage.mymodule"``). The argument can also be an alias to 69 | a built-in codec, such as ``"json"``, ``"json_zlib"`` or ``"pickle"``. 70 | """ 71 | if module in CODECS: 72 | # The "_codec" suffix is to avoid conflicts with built-in module names 73 | mod = __import__('djangoflash.codec.%s' % CODECS[module], \ 74 | {}, {}, ['']) 75 | else: 76 | mod = __import__(module, {}, {}, ['']) 77 | return getattr(mod, 'CodecClass')() 78 | 79 | # Get the codec specified in the project's settings. Use the json codec by 80 | # default, for security reasons: http://nadiana.com/python-pickle-insecure 81 | codec = get_codec(getattr(settings, 'FLASH_CODEC', 'json')) 82 | -------------------------------------------------------------------------------- /src/djangoflash/tests/decorators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """djangoflash.decorators test cases. 4 | """ 5 | 6 | from unittest import TestCase 7 | 8 | from django.http import HttpRequest 9 | 10 | from djangoflash.decorators import keep_messages 11 | from djangoflash.models import FlashScope 12 | 13 | 14 | # Only exports test cases 15 | __all__ = ['KeepMessagesDecoratorTestCase'] 16 | 17 | 18 | def view_method(request): 19 | """Function that simulates a Django view. 20 | """ 21 | if hasattr(request, 'flash'): 22 | request.flash.update() 23 | return True 24 | return False 25 | 26 | 27 | class KeepMessagesDecoratorTestCase(TestCase): 28 | """Tests the keep_messages decorator. 29 | """ 30 | def setUp(self): 31 | """Create a request with an used message inside the flash. 32 | """ 33 | self.request = HttpRequest() 34 | self.request.flash = self.flash = FlashScope() 35 | self.flash['message'] = 'Message' 36 | self.flash.update() 37 | 38 | def test_decorator_with_no_flash(self): 39 | """Decorators: keep_messages should not break when there's no flash scope attached to the request. 40 | """ 41 | self.request = HttpRequest() 42 | view = keep_messages(view_method) 43 | self.assertFalse(view(self.request)) 44 | 45 | def test_decorator_with_no_args(self): 46 | """Decorators: keep_messages with no args should avoid the removal of all flash-scoped values. 47 | """ 48 | view = keep_messages(view_method) 49 | self.assertEqual('Message', self.flash['message']) 50 | 51 | self.assertTrue(view(self.request)) 52 | self.assertEqual('Message', self.flash['message']) 53 | 54 | view_method(self.request) 55 | self.assertFalse('message' in self.flash) 56 | 57 | def test_decorator_with_empty_args(self): 58 | """Decorators: keep_messages with empty args should avoid the removal of all flash-scoped values. 59 | """ 60 | view = keep_messages()(view_method) 61 | self.assertEqual('Message', self.flash['message']) 62 | 63 | self.assertTrue(view(self.request)) 64 | self.assertEqual('Message', self.flash['message']) 65 | 66 | view_method(self.request) 67 | self.assertFalse('message' in self.flash) 68 | 69 | def test_decorator_with_args(self): 70 | """Decorators: keep_messages should avoid the removal of specific flash-scoped values. 71 | """ 72 | view = keep_messages('message', 'another_message')(view_method) 73 | self.assertEqual('Message', self.flash['message']) 74 | 75 | self.assertTrue(view(self.request)) 76 | self.assertEqual('Message', self.flash['message']) 77 | 78 | view_method(self.request) 79 | self.assertFalse('message' in self.flash) 80 | 81 | def test_decorator_with_invalid_arg(self): 82 | """Decorators: keep_messages should not avoid the removal of flash-scoped values. 83 | """ 84 | view = keep_messages('another_message')(view_method) 85 | self.assertEqual('Message', self.flash['message']) 86 | 87 | self.assertTrue(view(self.request)) 88 | self.assertFalse('message' in self.flash) 89 | -------------------------------------------------------------------------------- /src/djangoflash/middleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This module provides the :class:`FlashMiddleware` class, which manages the 5 | *flash* whenever a HTTP request hits the server. 6 | 7 | To plug this middleware to your Django project, edit your project's 8 | ``settings.py`` file as follows:: 9 | 10 | MIDDLEWARE_CLASSES = ( 11 | 'djangoflash.middleware.FlashMiddleware', 12 | ) 13 | """ 14 | 15 | from urlparse import urlparse 16 | 17 | from django.conf import settings 18 | from django.core.exceptions import SuspiciousOperation 19 | from django.core import urlresolvers 20 | from django.views.static import serve 21 | 22 | from djangoflash.context_processors import CONTEXT_VAR 23 | from djangoflash.models import FlashScope 24 | from djangoflash.storage import storage 25 | 26 | 27 | # This middleware integrates gracefully with CommonMiddleware 28 | _COMMON_MIDDLEWARE_CLASS = 'django.middleware.common.CommonMiddleware' 29 | 30 | 31 | class FlashMiddleware(object): 32 | """This middleware uses the flash storage backend specified by the 33 | project's ``settings.py`` file in order to store and retrieve 34 | :class:`djangoflash.models.FlashScope` objects, being also responsible for 35 | expiring old flash-scoped objects. 36 | 37 | .. note:: 38 | This class is designed to be used by the Django framework itself. 39 | """ 40 | 41 | def process_request(self, request): 42 | """This method is called by the Django framework when a *request* hits 43 | the server. 44 | """ 45 | flash = _get_flash_from_storage(request) 46 | if _should_update_flash(request): 47 | flash.update() 48 | 49 | def process_response(self, request, response): 50 | """This method is called by the Django framework when a *response* is 51 | sent back to the user. 52 | """ 53 | flash = _get_flash_from_request(request) 54 | if flash: 55 | storage.set(flash, request, response) 56 | else: 57 | _get_flash_from_storage(request) 58 | 59 | return response 60 | 61 | 62 | def _get_flash_from_storage(request): 63 | """Gets the flash from the storage, adds it to the given request and 64 | returns it. A new :class:`FlashScope` is used if the storage is empty. 65 | """ 66 | flash = storage.get(request) or FlashScope() 67 | setattr(request, CONTEXT_VAR, flash) 68 | return flash 69 | 70 | def _get_flash_from_request(request): 71 | """Returns the :class:`FlashScope` object from the given request. If it 72 | couldn't be found, returns None. 73 | """ 74 | flash = None 75 | if hasattr(request, CONTEXT_VAR): 76 | flash = getattr(request, CONTEXT_VAR) 77 | if not isinstance(flash, FlashScope): 78 | raise SuspiciousOperation('Invalid flash: %s' % repr(flash)) 79 | return flash 80 | 81 | def _should_update_flash(request): 82 | """Returns True if the flash should be updated, False otherwise. 83 | """ 84 | return not _is_trailing_slash_missing(request) and \ 85 | not _is_request_to_serve(request) 86 | 87 | def _is_request_to_serve(request): 88 | """Returns True if *request* resolves to the built-in ``serve`` view, 89 | False othersise. 90 | """ 91 | # Are we running in debug mode? 92 | debug = getattr(settings, 'DEBUG', False) 93 | 94 | # Uses the value of DEBUG as default value to FLASH_IGNORE_MEDIA 95 | if getattr(settings, 'FLASH_IGNORE_MEDIA', debug): 96 | try: 97 | return urlresolvers.resolve(request.path_info)[0] == serve 98 | except urlresolvers.Resolver404: 99 | pass 100 | return False 101 | 102 | def _is_trailing_slash_missing(request): 103 | """Returns True if the requested URL are elegible to be intercepted by the 104 | CommonMiddleware (if it's being used), which issues a HttpRedirect when a 105 | trailing slash is missing. Returns False otherwise. 106 | """ 107 | if _COMMON_MIDDLEWARE_CLASS in settings.MIDDLEWARE_CLASSES: 108 | path = request.path 109 | if getattr(settings, 'APPEND_SLASH', False) and not path.endswith('/'): 110 | if not _is_valid_path(path) and _is_valid_path('%s/' % path): 111 | return True 112 | return False 113 | 114 | def _is_valid_path(path): 115 | """Returns True if *path* resolves against the default URL resolver, 116 | False otherwise. 117 | """ 118 | try: 119 | urlresolvers.resolve(path) 120 | return True 121 | except urlresolvers.Resolver404: 122 | pass 123 | return False 124 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Build script used to test, build and deploy django-flash using several 4 | Python versions. 5 | 6 | In order to test and build django-flash in these different environments, 7 | this script requires different virtualenvs, each one targeted to a specific 8 | Python version: 9 | 10 | * django-flash-py2.7 - for Python 2.7 11 | * django-flash-py2.6 - for Python 2.6 12 | * django-flash-py2.5 - for Python 2.5 13 | 14 | Also, each one of these virtualenvs must have the following packages 15 | installed: 16 | 17 | * Django (version 1.0+) 18 | * Pysqlite (version recommended by the current Django version) 19 | 20 | Finally, to use this script, you must install the packages below to your 21 | default Python installation: 22 | 23 | * Fabric 0.9+ 24 | 25 | That's it. You can now see all available targets provided by this build 26 | script by running the command line below: 27 | 28 | $ cd /path/to/django-flash 29 | $ fab -l 30 | """ 31 | 32 | import os 33 | import re 34 | import sys 35 | 36 | from fabric.api import * 37 | 38 | 39 | # Adds the 'src' to the Python path 40 | sys.path += ('src',) 41 | 42 | # Supported Python versions 43 | env.versions = ('2.7', '2.6', '2.5') 44 | env.default_version = env.versions[0] 45 | 46 | # Environment info 47 | env.project = 'django-flash' 48 | env.virtualenv_dir = os.environ['WORKON_HOME'] or '~/.virtualenvs' 49 | env.default_editor = os.environ['EDITOR'] or 'vi' 50 | 51 | # Files that contain version information 52 | env.new_version_files = ( 53 | 'setup.py', 54 | 'src/djangoflash/__init__.py', 55 | 'doc/source/conf.py', 56 | 'doc/source/changelog.rst', 57 | ) 58 | 59 | # Information needed to build the documentation 60 | env.sphinx_output = 'build/sphinx' 61 | env.sphinx_latex = '%s/latex' % env.sphinx_output 62 | env.sphinx_html = '%s/html' % env.sphinx_output 63 | env.doc_output = 'djangoflash' 64 | 65 | # Host where the documentation website lives 66 | env.hosts = ['destaquenet.com'] 67 | env.doc_folder = '/home/destaquenet/public_html' 68 | 69 | 70 | def setup(command, version=env.default_version): 71 | """Executes the given setup command with a virtual Python installation. 72 | """ 73 | local('%s/%s-py%s/bin/python setup.py %s' % 74 | (env.virtualenv_dir, env.project, version, command)) 75 | 76 | def test(): 77 | """Runs all tests in different Python versions. 78 | """ 79 | for version in env.versions: 80 | setup('test', version) 81 | 82 | def clean(): 83 | """Removes the build directory. 84 | """ 85 | local('rm -fR build') 86 | 87 | def build_docs(): 88 | """Builds the documentation in PDF and HTML. 89 | """ 90 | clean() 91 | setup('build_sphinx') 92 | setup('build_sphinx -b latex') 93 | local('make -C ' + env.sphinx_latex) 94 | 95 | def zip_docs(): 96 | """Creates a zip file with the complete documentation. 97 | """ 98 | build_docs() 99 | local('cp %s/%s.pdf %s' % 100 | (env.sphinx_latex, env.project, env.sphinx_html)) 101 | local('cd %s; mv html %s; zip -r9 %s.zip %s' % 102 | ((env.sphinx_output,) + (env.doc_output,)*3)) 103 | 104 | def register_pypi(): 105 | """Register the current version on PyPI. 106 | """ 107 | setup('register') 108 | 109 | def deploy_src(): 110 | """Deploy the source code to PyPI. 111 | """ 112 | setup('sdist upload') 113 | 114 | def deploy_eggs(): 115 | """Upload Python Eggs to PyPI. 116 | """ 117 | for version in env.versions: 118 | setup('bdist_egg upload', version) 119 | 120 | def deploy_pypi(): 121 | """Deploys all artifacts to PyPI. 122 | """ 123 | test() 124 | register_pypi() 125 | deploy_src() 126 | deploy_eggs() 127 | 128 | def deploy_website(): 129 | """Deploys the documentation website. 130 | """ 131 | zip_docs() 132 | put('%s/%s.zip' % 133 | (env.sphinx_output, env.doc_output), env.doc_folder) 134 | run('cd %s; rm -R %s; unzip %s.zip; rm %s.zip' % 135 | ((env.doc_folder,) + (env.doc_output,)*3)) 136 | 137 | def deploy(): 138 | """Deploys the application to PyPI and updates the documentation website. 139 | """ 140 | deploy_pypi() 141 | deploy_website() 142 | 143 | def tag_new_version(): 144 | """Updates the version number, pushing the changes and tagging afterwards. 145 | """ 146 | # Checks if there are changed or untracked files 147 | git_status_file = 'build/git_status' 148 | local('git status > %s' % git_status_file, fail='ignore') 149 | if re.search(r'(Changed|Untracked)', file(git_status_file, 'r').read()): 150 | print 'There are changed or untracked files. Aborting...' 151 | return 152 | 153 | # Brings up the text editor with the files to be changed 154 | for f in env.new_version_files: 155 | local('%s %s' % (env.default_editor, f)) 156 | 157 | # Asks for confirmation 158 | prompt('tag_proceed', 'You are about to commit and push the version ' 159 | 'changes. Continue?', default='y') 160 | 161 | if env.tag_proceed.upper() != 'Y': 162 | print 'Aborting...' 163 | return 164 | 165 | # Commits and tags the new release 166 | from djangoflash import __version__ 167 | local('git commit -am "Updated version number."; git push', fail='ignore') 168 | local('git tag -am "Tagged version %s." %s; git push --tags' % 169 | ((__version__,)*2), fail='ignore') 170 | local('git push --tags') 171 | 172 | -------------------------------------------------------------------------------- /doc/source/configuration.rst: -------------------------------------------------------------------------------- 1 | .. _configuration: 2 | 3 | Configuration 4 | ------------- 5 | 6 | In order to plug Django-Flash to your project, open your project's 7 | ``settings.py`` file and do the following changes:: 8 | 9 | TEMPLATE_CONTEXT_PROCESSORS = ( 10 | 'djangoflash.context_processors.flash', 11 | ) 12 | 13 | MIDDLEWARE_CLASSES = ( 14 | 'django.contrib.sessions.middleware.SessionMiddleware', 15 | 'djangoflash.middleware.FlashMiddleware', 16 | ) 17 | 18 | 19 | That's all the required configuration. 20 | 21 | .. warning:: 22 | The :class:`djangoflash.middleware.FlashMiddleware` class must be declared 23 | after the :class:`SessionMiddleware` class. 24 | 25 | 26 | Django-Flash and requests to media files 27 | ```````````````````````````````````````` 28 | 29 | Django itself doesn’t serve static (media) files, such as images, style sheets, 30 | or video. It leaves that job to whichever web server you choose. But, *during 31 | development*, you can use the :meth:`django.views.static.serve` view to serve 32 | media files. 33 | 34 | The problem with it is that, as a regular view, requests to 35 | :meth:`django.views.static.serve` trigger the installed middlewares. And since 36 | the *flash* gets updated by :ref:`a middleware `, messages might be 37 | removed from the *flash* by accident if the response causes the web browser to 38 | issue requests to fetch static files. 39 | 40 | To make Django-Flash work well with the :meth:`django.views.static.serve` view, 41 | you can add the setting ``FLASH_IGNORE_MEDIA`` to your project's 42 | ``settings.py`` file:: 43 | 44 | FLASH_IGNORE_MEDIA = True # Optional. Default: DEBUG 45 | 46 | Set the ``FLASH_IGNORE_MEDIA`` setting to ``True``, and Django-Flash won't 47 | remove any message from the *flash* if the request URL resolves to 48 | :meth:`django.views.static.serve`. Otherwise, every request will trigger the 49 | :class:`djangoflash.middleware.FlashMiddleware` as usual. 50 | 51 | .. note:: 52 | This setting is optional; its default value is ``DEBUG``. So, if you adjust 53 | the ``DEBUG`` setting according to the environment in which the application 54 | runs (as you *should*), you don't have to worry about this setting at all, 55 | things will just work. 56 | 57 | 58 | Flash storage backends 59 | `````````````````````` 60 | 61 | Since :ref:`version 1.5`, Django-Flash supports custom flash 62 | storage backends. 63 | 64 | By default, Django-Flash provides two built-in storage backends: 65 | 66 | * :mod:`djangoflash.storage.session` -- Session-based storage (default); 67 | * :mod:`djangoflash.storage.cookie` -- Cookie-based storage; 68 | 69 | .. seealso:: 70 | :ref:`custom_storages` 71 | 72 | 73 | Using the session-based storage 74 | ''''''''''''''''''''''''''''''' 75 | 76 | Django-Flash uses the :ref:`session-based storage ` by default, 77 | so you don't need to do anything else to use it. 78 | 79 | *Although you are not required to do so*, you can add the following setting to 80 | your project's ``settings.py`` file to make it clear about what flash storage 81 | backend is being used:: 82 | 83 | FLASH_STORAGE = 'session' # Optional 84 | 85 | 86 | This storage backend *doesn't* rely on codecs to serialize and de-serialize the 87 | flash data; it lets Django handle this. 88 | 89 | 90 | Using the cookie-based storage 91 | '''''''''''''''''''''''''''''' 92 | 93 | If you want to use the :ref:`cookie-based storage ` instead the 94 | default one, then add the following setting to the ``settings.py`` file:: 95 | 96 | FLASH_STORAGE = 'cookie' 97 | 98 | 99 | Since cookies will be used to store the contents of the flash scope, 100 | Django-Flash doesn't require you to add the :class:`SessionMiddleware` class 101 | to the ``MIDDLEWARE_CLASSES`` section of your project's settings anymore. 102 | 103 | This storage backend relies on codecs to serialize and de-serialize the flash 104 | data. 105 | 106 | 107 | Flash serialization codecs 108 | `````````````````````````` 109 | 110 | Since :ref:`version 1.7`, Django-Flash supports custom flash 111 | serialization codecs. 112 | 113 | By default, Django-Flash provides three built-in codecs: 114 | 115 | * :mod:`djangoflash.codec.json_impl` -- JSON-based codec (default); 116 | * :mod:`djangoflash.codec.json_zlib_impl` -- JSON/zlib-based codec; 117 | * :mod:`djangoflash.codec.pickle_impl` -- Pickle-based codec; 118 | 119 | .. seealso:: 120 | :ref:`custom_codecs` 121 | 122 | 123 | Using the JSON-based codec implementation 124 | ''''''''''''''''''''''''''''''''''''''''' 125 | 126 | For security reasons, Django-flash uses the 127 | :ref:`JSON-based codec implementation ` by default, so you don't 128 | need to do anything else to use it. 129 | 130 | *Although you are not required to do so*, you can add the following setting to 131 | your project's ``settings.py`` file to make it clear about what codec 132 | implementation is being used:: 133 | 134 | FLASH_CODEC = 'json' # Optional 135 | 136 | 137 | There's also an :ref:`alternative version ` of this codec that 138 | uses the :mod:`zlib` module to reduce the encoded flash footprint. This is 139 | particularly useful when the flash storage backend in use (such as the 140 | :ref:`cookie-based storage `) cannot handle the amount of data 141 | in the *flash*:: 142 | 143 | FLASH_CODEC = 'json_zlib' 144 | 145 | 146 | Using the Pickle-based codec implementation 147 | ''''''''''''''''''''''''''''''''''''''''''' 148 | 149 | If you want to use the :ref:`Pickle-based codec implementation ` 150 | instead the default one, then add the following setting to the ``settings.py`` 151 | file:: 152 | 153 | FLASH_CODEC = 'pickle' 154 | 155 | 156 | .. warning:: 157 | The use of this codec is not recommended since the 158 | `Pickle documentation `_ itself 159 | clearly states that it's not intended to be secure against erroneous or 160 | maliciously constructed data. 161 | 162 | -------------------------------------------------------------------------------- /doc/source/changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changelog: 2 | 3 | Changelog 4 | ========= 5 | 6 | Like any other piece of software, Django-Flash is evolving at each release. 7 | Here you can track our progress: 8 | 9 | **Version 1.8** *(Feb 12, 2011)* 10 | 11 | * **Notice:** *breaks backwards compatibility;* 12 | * Removed :meth:`djangoflash.models.FlashScope.__call__` in order to avoid 13 | problems in Django 1.3; 14 | * Removed deprecated (since version 1.7.1) method 15 | :meth:`djangoflash.models.FlashScope.put_immediate` in favor of 16 | ``flash.now[key] = value``; 17 | 18 | **Version 1.7.2** *(May 20, 2010)* 19 | 20 | * **Notice:** Django 1.2 already provides a built-in user "messages" framework, 21 | but `we'll continue to support Django-Flash `_; 22 | * Updated test code to make it work properly on post-1.2 versions of Django; 23 | 24 | **Version 1.7.1** *(March 20, 2010)* 25 | 26 | * **Notice:** *breaks backwards compatibility;* 27 | * Removed deprecated (since version 1.4.2) method 28 | :meth:`djangoflash.models.FlashScope.has_key`; 29 | * Deprecating method :meth:`djangoflash.models.FlashScope.put_immediate` in 30 | favor of ``flash.now[key] = value``; 31 | * Deprecating method :meth:`djangoflash.models.FlashScope.put` in favor of 32 | ``flash(key=value)``; 33 | * Method :meth:`djangoflash.models.FlashScope.add` can now append several values 34 | to the given key; 35 | * Added a method :meth:`add` to :attr:`djangoflash.models.FlashScope.now` that 36 | simplifies the storage of multiple immediate values under the same key; 37 | 38 | **Version 1.7** *(October 25, 2009)* 39 | 40 | * Added support for custom flash serialization codecs; 41 | * Three built-in codec implementations: JSON, JSON/zlib and Pickle; 42 | * Module :mod:`djangoflash.storage.base` removed; 43 | 44 | **Version 1.6.3** *(October 07, 2009)* 45 | 46 | * Using the ``DEBUG`` setting as the default value of ``FLASH_IGNORE_MEDIA``; 47 | 48 | **Version 1.6.2** *(September 18, 2009)* 49 | 50 | * Done some work to avoid the loss of messages when the 51 | :class:`CommonMiddleware` returns a :class:`HttpResponseRedirect` due to a 52 | missing trailing slash; 53 | 54 | **Version 1.6.1** *(August 19, 2009)* 55 | 56 | * Now the middleware checks if the request resolves to 57 | :meth:`django.views.static.serve` instead of relying on the ``MEDIA_URL`` 58 | setting; 59 | 60 | **Version 1.6** *(August 13, 2009)* 61 | 62 | * Fixed a bug in which messages are prematurely removed from the flash when 63 | they are replaced using ``flash.now`` in some circumstances; 64 | * Added the ``FLASH_IGNORE_MEDIA`` setting to let the user choose whether 65 | requests to static files should be ignored; 66 | 67 | **Version 1.5.3** *(July 22, 2009)* 68 | 69 | * Fixed a bug in the middleware which causes flash data to be dicarded after 70 | requests to static files; 71 | 72 | **Version 1.5.2** *(July 15, 2009)* 73 | 74 | * Added a :meth:`djangoflash.decorators.keep_messages` decorator for keeping 75 | flash messages; 76 | * New ``AUTHORS`` file; 77 | 78 | **Version 1.5.1** *(June 26, 2009)* 79 | 80 | * Added a method :meth:`djangoflash.models.FlashScope.add` that simplifies the 81 | storage of multiple values under the same key; 82 | 83 | **Version 1.5** *(June 24, 2006)* 84 | 85 | * License changed from LGPL to BSD to give uses more freedom; 86 | * Added support for custom flash storage backends; 87 | * Added a cookie-based flash storage; 88 | * Default session-based storage was factored out to an independent class; 89 | * Added a few more sanity checks; 90 | 91 | **Version 1.4.4** *(June 09, 2009)* 92 | 93 | * Fixed a critical bug in the middleware; 94 | 95 | **Version 1.4.3** *(June 08, 2009)* 96 | 97 | * Added a few more sanity checks; 98 | 99 | **Version 1.4.2** *(February 13, 2009)* 100 | 101 | * Deprecating method :meth:`djangoflash.models.FlashScope.has_key` in favor of 102 | ``key in flash``; 103 | * Documentation improvements; 104 | * Internals refactoring; 105 | 106 | **Version 1.4.1** *(February 06, 2009)* 107 | 108 | * Immediate values (:attr:`djangoflash.models.FlashScope.now`) can be 109 | manipulated using a dict-like syntax; 110 | * Unit test improvements; 111 | * Documentation improvements; 112 | 113 | **Version 1.4** *(February 05, 2009)* 114 | 115 | * **Notice:** *breaks backwards compatibility;* 116 | * Now Django-Flash works pretty much like the original `Ruby on Rails`_' flash; 117 | * Several code optmizations; 118 | * Several improvements on the test suite; 119 | 120 | **Version 1.3.5** *(February 03, 2009)* 121 | 122 | * Several documentation improvements; 123 | * Improvements on source code comments and unit tests; 124 | 125 | **Version 1.3.4** *(February 01, 2009)* 126 | 127 | * Added Sphinx_-based documentation; 128 | * Source code changed to improve the Pylint_ score; 129 | * :mod:`djangoflash` module now have a ``__version__`` property, which is 130 | very useful when you need to know what version of the Django-Flash is 131 | installed in your machine; 132 | 133 | **Version 1.3.3** *(January 31, 2009)* 134 | 135 | * *Critical Bug Fixed*: Django-Flash creates several useless session 136 | entries when the cookie support in user's browser is disabled; 137 | * Small improvements on unit tests; 138 | 139 | **Version 1.3.2** *(December 07, 2008)* 140 | 141 | * Small fixes; 142 | 143 | **Version 1.3.1** *(December 07, 2008)* 144 | 145 | * Added some sanity checks; 146 | 147 | **Version 1.3** *(December 07, 2008)* 148 | 149 | * **Notice:** *breaks backwards compatibility;* 150 | * Django-Flash now controls the expiration of flash-scoped values 151 | individually, which means that only expired values are removed from the 152 | session (and not the whole flash context); 153 | * Unit testing code was completely rewritten and now a real Django 154 | application is used in integration tests; 155 | * Huge source code review to make it easier to read and to assure the use 156 | of Python conventions; 157 | * Project renamed to **Django-Flash** (it was previously called 158 | **djangoflash**, without the hyphen); 159 | 160 | **Version 1.2** *(November 01, 2008)* 161 | 162 | * **Notice:** *breaks backwards compatibility;* 163 | * Improvements on the test comments; 164 | * Now the flash scope works pretty much like a :class:`dict`, although 165 | still there's no value-based expiration (the whole flash scope expires at 166 | the end of the request); 167 | 168 | **Version 1.1** *(November 01, 2008)* 169 | 170 | * Now using SetupTools_ to make the project easier to distribute; 171 | 172 | **Version 1.0** *(October 22, 2008)* 173 | 174 | * First (very simple) version; 175 | 176 | .. _Ruby on Rails: http://www.rubyonrails.org/ 177 | .. _SetupTools: http://pypi.python.org/pypi/setuptools/ 178 | .. _Sphinx: http://sphinx.pocoo.org/ 179 | .. _Pylint: http://www.logilab.org/857 180 | 181 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-flash documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 1 01:49:12 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # The contents of this file are pickled, so don't put values in the namespace 9 | # that aren't pickleable (module imports are okay, they're removed automatically). 10 | # 11 | # Note that not all possible configuration values are present in this 12 | # autogenerated file. 13 | # 14 | # All configuration values have a default; values that are commented out 15 | # serve to show the default. 16 | 17 | import sys, os 18 | 19 | # If your extensions are in another directory, add it here. If the directory 20 | # is relative to the documentation root, use os.path.abspath to make it 21 | # absolute, like shown here. 22 | sys.path.append('./src') 23 | 24 | # General configuration 25 | # --------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | #source_encoding = 'utf-8' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'Django-Flash' 45 | copyright = u'2008-2010, Destaquenet Technology Solutions' 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | version = '1.8' 53 | # The full version, including alpha/beta/rc tags. 54 | release = '1.8' 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | # language = None 59 | 60 | # There are two options for replacing |today|: either, you set today to some 61 | # non-false value, then it is used: 62 | #today = '' 63 | # Else, today_fmt is used as the format for a strftime call. 64 | #today_fmt = '%B %d, %Y' 65 | 66 | # List of documents that shouldn't be included in the build. 67 | #unused_docs = [] 68 | 69 | # List of directories, relative to source directory, that shouldn't be searched 70 | # for source files. 71 | exclude_trees = [] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | 91 | # Options for HTML output 92 | # ----------------------- 93 | 94 | # The style sheet to use for HTML and HTML Help pages. A file of that name 95 | # must exist either in Sphinx' static/ path, or in one of the custom paths 96 | # given in html_static_path. 97 | html_style = 'default.css' 98 | 99 | # The name for this set of Sphinx documents. If None, it defaults to 100 | # " v documentation". 101 | #html_title = None 102 | 103 | # A shorter title for the navigation bar. Default is the same as html_title. 104 | #html_short_title = None 105 | 106 | # The name of an image file (relative to this directory) to place at the top 107 | # of the sidebar. 108 | #html_logo = None 109 | 110 | # The name of an image file (within the static path) to use as favicon of the 111 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 112 | # pixels large. 113 | #html_favicon = None 114 | 115 | # Add any paths that contain custom static files (such as style sheets) here, 116 | # relative to this directory. They are copied after the builtin static files, 117 | # so a file named "default.css" will overwrite the builtin "default.css". 118 | html_static_path = ['_static'] 119 | 120 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 121 | # using the given strftime format. 122 | #html_last_updated_fmt = '%b %d, %Y' 123 | 124 | # If true, SmartyPants will be used to convert quotes and dashes to 125 | # typographically correct entities. 126 | #html_use_smartypants = True 127 | 128 | # Custom sidebar templates, maps document names to template names. 129 | #html_sidebars = {} 130 | 131 | # Additional templates that should be rendered to pages, maps page names to 132 | # template names. 133 | #html_additional_pages = {} 134 | 135 | # If false, no module index is generated. 136 | #html_use_modindex = True 137 | 138 | # If false, no index is generated. 139 | #html_use_index = True 140 | 141 | # If true, the index is split into individual pages for each letter. 142 | #html_split_index = False 143 | 144 | # If true, the reST sources are included in the HTML build as _sources/. 145 | #html_copy_source = True 146 | 147 | # If true, an OpenSearch description file will be output, and all pages will 148 | # contain a tag referring to it. The value of this option must be the 149 | # base URL from which the finished HTML is served. 150 | #html_use_opensearch = '' 151 | 152 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 153 | #html_file_suffix = '' 154 | 155 | # Output file base name for HTML help builder. 156 | htmlhelp_basename = 'django-flashdoc' 157 | 158 | 159 | # Options for LaTeX output 160 | # ------------------------ 161 | 162 | # The paper size ('letter' or 'a4'). 163 | #latex_paper_size = 'letter' 164 | 165 | # The font size ('10pt', '11pt' or '12pt'). 166 | #latex_font_size = '10pt' 167 | 168 | # Grouping the document tree into LaTeX files. List of tuples 169 | # (source start file, target name, title, author, document class [howto/manual]). 170 | latex_documents = [ 171 | ('index', 'django-flash.tex', ur'Django-flash Documentation', 172 | ur'Destaquenet Technology Solutions', 'manual'), 173 | ] 174 | 175 | # The name of an image file (relative to this directory) to place at the top of 176 | # the title page. 177 | #latex_logo = None 178 | 179 | # For "manual" documents, if this is true, then toplevel headings are parts, 180 | # not chapters. 181 | #latex_use_parts = False 182 | 183 | # Additional stuff for the LaTeX preamble. 184 | #latex_preamble = '' 185 | 186 | # Documents to append as an appendix to all manuals. 187 | #latex_appendices = [] 188 | 189 | # If false, no module index is generated. 190 | #latex_use_modindex = True 191 | 192 | # Fix import errors 193 | from django.conf import settings 194 | settings.configure() 195 | -------------------------------------------------------------------------------- /src/djangoflash/tests/storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """djangoflash.storage test cases. 4 | """ 5 | 6 | from unittest import TestCase 7 | 8 | from django.http import HttpRequest, HttpResponse 9 | 10 | from djangoflash.models import FlashScope 11 | from djangoflash import storage 12 | from djangoflash.storage import session, cookie 13 | 14 | 15 | class StorageTestCase(TestCase): 16 | """Tests methods used to parse flash storage URIs and create flash storage 17 | objects. 18 | """ 19 | def test_get_session_storage_by_alias(self): 20 | """Storage: 'session' should resolve to session flash storage. 21 | """ 22 | storage_impl = storage.get_storage('session') 23 | self.assertTrue(isinstance(storage_impl, session.FlashStorageClass)) 24 | 25 | def test_get_cookie_storage_by_alias(self): 26 | """Storage: 'cookie' should resolve to cookie flash storage. 27 | """ 28 | storage_impl = storage.get_storage('cookie') 29 | self.assertTrue(isinstance(storage_impl, cookie.FlashStorageClass)) 30 | 31 | def test_get_storage_by_module_name(self): 32 | """Storage: 'djangoflash.storage.cookie' should resolve to cookie flash storage. 33 | """ 34 | storage_impl = storage.get_storage('djangoflash.storage.cookie') 35 | self.assertTrue(isinstance(storage_impl, cookie.FlashStorageClass)) 36 | 37 | def test_get_storage_by_invalid_module_name(self): 38 | """Storage: Should raise an error when resolving a module name that doesn't exists. 39 | """ 40 | operation = lambda: storage.get_storage('invalid.module.path') 41 | self.assertRaises(ImportError, operation) 42 | 43 | def test_get_storage_by_invalid_module(self): 44 | """Storage: Should raise an error when module doesn't provide a storage class. 45 | """ 46 | operation = lambda: storage.get_storage('djangoflash.models') 47 | self.assertRaises(AttributeError, operation) 48 | 49 | 50 | class SessionFlashStorageTestCase(TestCase): 51 | """Tests the session-based flash storage class. 52 | """ 53 | def setUp(self): 54 | """Creates a cookie-based flash storage for testing. 55 | """ 56 | self.request = HttpRequest() 57 | self.request.session = {} 58 | self.response = HttpResponse('') 59 | self.flash = FlashScope() 60 | self.storage = session.FlashStorageClass() 61 | 62 | def _get_flash(self): 63 | """Returns the flash contents from the session. 64 | """ 65 | return self.request.session[self.storage._key] 66 | 67 | def test_set_null_object(self): 68 | """SessionStorage: should not store null values. 69 | """ 70 | self.storage.set(None, self.request, self.response) 71 | self.assertEqual(0, len(self.request.session)) 72 | 73 | def test_set_empty_object(self): 74 | """SessionStorage: should not store empty objects. 75 | """ 76 | self.storage.set(self.flash, self.request, self.response) 77 | self.assertEqual(0, len(self.request.session)) 78 | 79 | def test_clear_storage(self): 80 | """SessionStorage: should remove flash contents from the session. 81 | """ 82 | self.flash['message'] = 'Message' 83 | self.storage.set(self.flash, self.request, self.response) 84 | self.assertEqual('Message', self._get_flash()['message']) 85 | 86 | # The flash should be completely removed from the session 87 | del self.flash['message'] 88 | self.storage.set(self.flash, self.request, self.response) 89 | self.assertRaises(KeyError, self._get_flash) 90 | 91 | def test_set_object(self): 92 | """Session storage: should store valid objects. 93 | """ 94 | self.flash['message'] = 'Message' 95 | self.storage.set(self.flash, self.request, self.response) 96 | self.assertEqual(1, len(self.request.session)) 97 | 98 | def test_get_empty(self): 99 | """SessionStorage: should return nothing when empty. 100 | """ 101 | self.assertEqual(None, self.storage.get(self.request)) 102 | 103 | def test_get(self): 104 | """SessionStorage: should return the stored object. 105 | """ 106 | self.flash['message'] = 'Message' 107 | self.storage.set(self.flash, self.request, self.response) 108 | self.assertEqual('Message', self.storage.get(self.request)['message']) 109 | 110 | 111 | class CookieFlashStorageTestCase(TestCase): 112 | """Tests the cookie-based flash storage class. 113 | """ 114 | def setUp(self): 115 | """Creates a cookie-based flash storage for testing. 116 | """ 117 | self.request = HttpRequest() 118 | self.response = HttpResponse('') 119 | self.flash = FlashScope() 120 | self.storage = cookie.FlashStorageClass() 121 | 122 | def _transfer_cookies_from_response_to_request(self): 123 | """Transfers the cookies set in the response to the request. 124 | """ 125 | for key, cookie in self.response.cookies.items(): 126 | self.request.COOKIES[key] = cookie.value 127 | 128 | def _get_cookie(self): 129 | """Returns the cookie used to store the flash contents. 130 | """ 131 | return self.response.cookies[self.storage._key] 132 | 133 | def test_set_null_object(self): 134 | """CookieStorage: should not store null values. 135 | """ 136 | self.storage.set(None, self.request, self.response) 137 | self.assertEqual(0, len(self.response.cookies)) 138 | 139 | def test_set_empty_object(self): 140 | """CookieStorage: should not store an empty object. 141 | """ 142 | self.storage.set(self.flash, self.request, self.response) 143 | self.assertEqual(0, len(self.response.cookies)) 144 | 145 | def test_clear_storage(self): 146 | """CookieStorage: should set an empty/expired cookie. 147 | """ 148 | self.flash['message'] = 'Message' 149 | self.storage.set(self.flash, self.request, self.response) 150 | 151 | # Simulates a request-response cycle 152 | self._transfer_cookies_from_response_to_request() 153 | del self.flash['message'] 154 | 155 | # Cookie should be empty/expired 156 | self.storage.set(self.flash, self.request, self.response) 157 | self.assertEqual(0, self._get_cookie()['max-age']) 158 | self.assert_(not self._get_cookie().value) 159 | 160 | def test_set_object(self): 161 | """CookieStorage: should store valid objects. 162 | """ 163 | self.flash['message'] = 'Message' 164 | self.storage.set(self.flash, self.request, self.response) 165 | self.assertEqual(1, len(self.response.cookies)) 166 | 167 | def test_get_empty(self): 168 | """CookieStorage: should return nothing when empty. 169 | """ 170 | self.assertEqual(None, self.storage.get(self.request)) 171 | 172 | def test_get(self): 173 | """CookieStorage: should return the stored object. 174 | """ 175 | self.flash['message'] = 'Message' 176 | self.storage.set(self.flash, self.request, self.response) 177 | self.assertEqual(None, self.storage.get(self.request)) 178 | 179 | # Simulates a request-response cycle 180 | self._transfer_cookies_from_response_to_request() 181 | self.assertEqual('Message', self.storage.get(self.request)['message']) 182 | -------------------------------------------------------------------------------- /src/djangoflash/tests/codec.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """djangoflash.codec test cases. 4 | """ 5 | 6 | from unittest import TestCase 7 | 8 | from django.core.exceptions import SuspiciousOperation 9 | 10 | from djangoflash import codec 11 | from djangoflash.codec import pickle_impl, json_impl, json_zlib_impl, BaseCodec 12 | from djangoflash.models import FlashScope 13 | 14 | 15 | class CodecTestCase(TestCase): 16 | """Tests methods used to parse flash storage URIs and create flash storage 17 | objects. 18 | """ 19 | def test_get_pickle_codec_by_alias(self): 20 | """Codec: 'pickle' should resolve to Pickle-based codec. 21 | """ 22 | codec_impl = codec.get_codec('pickle') 23 | self.assertTrue(isinstance(codec_impl, pickle_impl.CodecClass)) 24 | 25 | def test_get_json_codec_by_alias(self): 26 | """Codec: 'json' should resolve to JSON-based codec. 27 | """ 28 | codec_impl = codec.get_codec('json') 29 | self.assertTrue(isinstance(codec_impl, json_impl.CodecClass)) 30 | 31 | def test_get_json_zlib_codec_by_alias(self): 32 | """Codec: 'json_zlib' should resolve to JSON/zlib-based codec. 33 | """ 34 | codec_impl = codec.get_codec('json_zlib') 35 | self.assertTrue(isinstance(codec_impl, json_impl.CodecClass)) 36 | self.assertTrue(isinstance(codec_impl, json_zlib_impl.CodecClass)) 37 | 38 | def test_get_codec_by_module_name(self): 39 | """Codec: 'djangoflash.codec.json_impl' should resolve to JSON-based codec. 40 | """ 41 | codec_impl = codec.get_codec('djangoflash.codec.json_impl') 42 | self.assertTrue(isinstance(codec_impl, json_impl.CodecClass)) 43 | 44 | def test_get_codec_by_invalid_module_name(self): 45 | """Codec: Should raise an error when resolving a module name that doesn't exists. 46 | """ 47 | operation = lambda: codec.get_codec('invalid.module.path') 48 | self.assertRaises(ImportError, operation) 49 | 50 | def test_get_codec_by_invalid_module(self): 51 | """Codec: Should raise an error when module doesn't provide a codec class. 52 | """ 53 | operation = lambda: codec.get_codec('djangoflash.models') 54 | self.assertRaises(AttributeError, operation) 55 | 56 | 57 | class BaseCodecTestCase(TestCase): 58 | """Tests the tampered checks and signing logic. 59 | """ 60 | def setUp(self): 61 | """Creates a codec and a sample flash. 62 | """ 63 | self.codec = json_impl.CodecClass() 64 | self.flash = FlashScope() 65 | self.flash['info'] = 'Info' 66 | self.flash.update() 67 | self.expected = 'eyJfc2Vzc2lvbiI6IHsiaW5mbyI6ICJJbmZvIn0sICJfdXNlZCI6' \ 68 | 'IHsiaW5mbyI6IG51bGx9fWZk\nNDViYTljMmU3MWJlZjBjYjcxOW' \ 69 | 'EwYjdlYzJlZjUx\n' 70 | 71 | def test_encode_and_sign(self): 72 | """Codec: BaseCodec should return an encoded and signed version of the flash. 73 | """ 74 | encoded_and_signed = self.codec.encode_and_sign(self.flash) 75 | self.assertEqual(self.expected, encoded_and_signed) 76 | 77 | def test_decoded_signed(self): 78 | """Codec: BaseCodec should decode an encoded and signed version of the flash. 79 | """ 80 | flash = self.codec.decode_signed(self.expected) 81 | self.assertEqual('Info', flash['info']) 82 | flash.update() 83 | self.assertFalse('info' in flash) 84 | 85 | def test_decoded_tampered(self): 86 | """Codec: BaseCodec should not decode a tampered version of the flash. 87 | """ 88 | tampered = 'eyJfc2Vzc2lvbiI6IHsiaW6mbyI6ICJJbmZvIn0sICJfdXNlZCI6IHsia' \ 89 | 'W5mbyI6IG51bGx9fWZk\nNDViYTljMmU3MWJlZjBjYjcxOWEwYjdlYzJl' \ 90 | 'ZjUx\n' 91 | operation = lambda: self.codec.decode_signed(tampered) 92 | self.assertRaises(SuspiciousOperation, operation) 93 | 94 | 95 | class PickleCodecTestCase(TestCase): 96 | """Tests the Pickle-based serialization codec implementation. 97 | """ 98 | def setUp(self): 99 | """Creates a Pickle-based codec and a sample flash. 100 | """ 101 | self.codec = pickle_impl.CodecClass() 102 | self.flash = FlashScope() 103 | self.flash['info'] = 'Info' 104 | self.flash.update() 105 | self.expected = '\x80\x02cdjangoflash.models\nFlashScope\nq\x01)\x81q' \ 106 | '\x02}q\x03(U\x03nowq\x04cdjangoflash.models\n_Immedi' \ 107 | 'ateFlashScopeAdapter\nq\x05)\x81q\x06}q\x07U\x08dele' \ 108 | 'gateq\x08h\x02sbU\x08_sessionq\t}q\nU\x04infoq\x0bU' \ 109 | '\x04Infoq\x0csU\x05_usedq\r}q\x0eh\x0bNsub.' 110 | 111 | def test_encode(self): 112 | """Codec: Pickle-based codec should return a Pickle dump of the flash. 113 | """ 114 | self.assertEqual(self.expected, self.codec.encode(self.flash)) 115 | 116 | def test_decode(self): 117 | """Codec: Pickle-based codec should restore the flash from a Pickle dump string. 118 | """ 119 | flash = self.codec.decode(self.expected) 120 | self.assertEqual('Info', flash['info']) 121 | flash.update() 122 | self.assertFalse('info' in flash) 123 | 124 | 125 | class JSONCodecTestCase(TestCase): 126 | """Tests the JSON-based serialization codec implementation. 127 | """ 128 | def setUp(self): 129 | """Creates a JSON-based codec and a sample flash. 130 | """ 131 | self.expected = '{"_session": {"info": "Info"}, ' \ 132 | '"_used": {"info": null}}' 133 | self.codec = json_impl.CodecClass() 134 | self.flash = FlashScope() 135 | self.flash['info'] = 'Info' 136 | self.flash.update() 137 | 138 | def test_encode(self): 139 | """Codec: JSON-based codec should return a JSON version of the flash. 140 | """ 141 | self.assertEqual(self.expected, self.codec.encode(self.flash)) 142 | 143 | def test_decode(self): 144 | """Codec: JSON-based codec should restore the flash from a JSON string. 145 | """ 146 | flash = self.codec.decode(self.expected) 147 | self.assertEqual('Info', flash['info']) 148 | flash.update() 149 | self.assertFalse('info' in flash) 150 | 151 | 152 | class JSONZlibCodecTestCase(TestCase): 153 | """Tests the JSON/zlib-based serialization codec implementation. 154 | """ 155 | def setUp(self): 156 | """Creates a JSON\zlib-based codec and a sample flash. 157 | """ 158 | self.expected = 'x\x9c\xabV\x8a/N-.\xce\xcc\xcfS\xb2R\xa8V\xca\xccK' \ 159 | '\xcb\x072\x94 199 | 200 | My template 201 | 202 | 203 | {% if flash.message %} 204 | 205 | 206 |
207 |

{{ flash.message }}

208 |
209 | {% endif %} 210 | 211 | 212 | 213 | 214 | It's also possible to iterate over all flash-scoped objects using the 215 | ``{% for %}`` tag if you want to: 216 | 217 | .. code-block:: html+django 218 | 219 | 220 | 221 | My template 222 | 223 | 224 | {% if flash %} 225 | 226 | 227 | {% for key, value in flash.items %} 228 |
229 |

{{ value }}

230 |
231 | {% endfor %} 232 | {% endif %} 233 | 234 | 235 | -------------------------------------------------------------------------------- /src/djangoflash/tests/testproj/app/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Integration test cases. 4 | """ 5 | 6 | from django.conf import settings 7 | from django.core.exceptions import SuspiciousOperation 8 | from django.core.urlresolvers import reverse 9 | from django.test import TestCase 10 | 11 | from djangoflash.context_processors import CONTEXT_VAR 12 | from djangoflash.middleware import FlashScope 13 | 14 | from testproj.app import views 15 | 16 | 17 | class IntegrationTestCase(TestCase): 18 | """Test the middleware and the context processors working within a real 19 | Django application. 20 | """ 21 | def _flash(self): 22 | """Shortcut to get the flash from the view context. 23 | """ 24 | return self.response.context[CONTEXT_VAR] 25 | 26 | def test_default_lifecycle(self): 27 | """Integration: a value should be automatically removed from the flash. 28 | """ 29 | self.response = self.client.get(reverse(views.set_flash_var)) 30 | self.assertEqual('Message', self._flash()['message']) 31 | 32 | self.response = self.client.get(reverse(views.render_template)) 33 | self.assertEqual('Message', self._flash()['message']) 34 | 35 | # Flash value will be removed when this request hits the app 36 | self.response = self.client.get(reverse(views.render_template)) 37 | self.assertFalse('message' in self._flash()) 38 | 39 | def test_value_in_template(self): 40 | """Integration: a value should be accessible by the templating system. 41 | """ 42 | def _assert_content(content, exists=True): 43 | if exists: 44 | matcher = self.assertTrue 45 | else: 46 | matcher = self.assertFalse 47 | matcher(self.response.content.find(content) > 0) 48 | 49 | self.response = self.client.get(reverse(views.set_flash_var)) 50 | _assert_content('Flash context: Message', exists=True) 51 | 52 | self.response = self.client.get(reverse(views.render_template)) 53 | _assert_content('Flash context: Message', exists=True) 54 | 55 | # Flash value will be removed when this request hits the app 56 | self.response = self.client.get(reverse(views.render_template)) 57 | _assert_content('Flash context: Message', exists=False) 58 | 59 | def test_keep_lifecycle(self): 60 | """Integration: a value shouldn't be removed from the flash when it is kept. 61 | """ 62 | self.response = self.client.get(reverse(views.set_flash_var)) 63 | self.assertEqual('Message', self._flash()['message']) 64 | 65 | self.response = self.client.get(reverse(views.keep_var)) 66 | self.assertEqual('Message', self._flash()['message']) 67 | 68 | # Flash value won't be removed now because it was explicitely kept 69 | self.response = self.client.get(reverse(views.render_template)) 70 | self.assertEqual('Message', self._flash()['message']) 71 | 72 | # Flash value will be removed when this request hits the app 73 | self.response = self.client.get(reverse(views.render_template)) 74 | self.assertFalse('message' in self._flash()) 75 | 76 | def test_keep_decorator(self): 77 | """Integration: keep_messages decorator should behave exactly like keep. 78 | """ 79 | self.response = self.client.get(reverse(views.set_flash_var)) 80 | self.assertEqual('Message', self._flash()['message']) 81 | 82 | self.response = self.client.get(reverse(views.keep_var_decorator)) 83 | self.assertEqual('Message', self._flash()['message']) 84 | 85 | # Flash value won't be removed now because it was explicitely kept 86 | self.response = self.client.get(reverse(views.render_template)) 87 | self.assertEqual('Message', self._flash()['message']) 88 | 89 | # Flash value will be removed when this request hits the app 90 | self.response = self.client.get(reverse(views.render_template)) 91 | self.assertFalse('message' in self._flash()) 92 | 93 | def test_now_lifecycle(self): 94 | """Integration: an immediate value shouldn't survive the next request. 95 | """ 96 | self.response = self.client.get(reverse(views.set_now_var)) 97 | self.assertEqual('Message', self._flash()['message']) 98 | 99 | # Flash value will be removed when this request hits the app 100 | self.response = self.client.get(reverse(views.render_template)) 101 | self.assertFalse('message' in self._flash()) 102 | 103 | def test_discard_lifecycle(self): 104 | """Integration: a discarded value shouldn't survive to the next request. 105 | """ 106 | self.response = self.client.get(reverse(views.discard_var)) 107 | self.assertEqual('Message', self._flash()['message']) 108 | 109 | # Flash value will be removed when this request hits the app 110 | self.response = self.client.get(reverse(views.render_template)) 111 | self.assertFalse('message' in self._flash()) 112 | 113 | def test_multiple_variables_lifecycle(self): 114 | """Integration: the flash should control several values independently. 115 | """ 116 | self.response = self.client.get(reverse(views.set_flash_var)) 117 | self.assertEqual('Message', self._flash()['message']) 118 | 119 | self.response = self.client.get(reverse(views.set_another_flash_var)) 120 | self.assertEqual('Message', self._flash()['message']) 121 | self.assertEqual('Another message', self._flash()['anotherMessage']) 122 | 123 | # 'message' will be removed when this request hits the app 124 | self.response = self.client.get(reverse(views.render_template)) 125 | self.assertFalse('message' in self._flash()) 126 | self.assertEqual('Another message', self._flash()['anotherMessage']) 127 | 128 | # 'anotherMessage' will be removed when this request hits the app 129 | self.response = self.client.get(reverse(views.render_template)) 130 | self.assertFalse('message' in self._flash()) 131 | self.assertFalse('anotherMessage' in self._flash()) 132 | 133 | def test_remove_flash(self): 134 | """Integration: an empty flash should be provided when none is available. 135 | """ 136 | self.response = self.client.get(reverse(views.remove_flash)) 137 | self.assertTrue(isinstance(self._flash(), FlashScope)) 138 | 139 | def test_replace_flash_with_invalid_object(self): 140 | """Integration: an exception should be raised when exposing an invalid object as being the flash. 141 | """ 142 | self.assertRaises(SuspiciousOperation, self.client.get, reverse(views.replace_flash)) 143 | 144 | def test_request_to_serve_view_without_ignore(self): 145 | """Integration: request to static resources should trigger the flash update. 146 | """ 147 | # Requests to static resources should trigger the flash update 148 | settings.FLASH_IGNORE_MEDIA = False 149 | 150 | self.response = self.client.get(reverse(views.set_flash_var)) 151 | self.assertEqual('Message', self._flash()['message']) 152 | 153 | self.response = self.client.get(settings.MEDIA_URL + 'test.css') 154 | self.assertEqual(200, self.response.status_code) 155 | 156 | # Flash value will be removed when this request hits the app 157 | self.response = self.client.get(reverse(views.render_template)) 158 | self.assertFalse('message' in self._flash()) 159 | 160 | def test_request_to_serve_view_with_ignore(self): 161 | """Integration: request to static resources should not trigger the flash update, if properly configured. 162 | """ 163 | # Requests to static resources should not trigger the flash update 164 | settings.FLASH_IGNORE_MEDIA = True 165 | 166 | self.response = self.client.get(reverse(views.set_flash_var)) 167 | self.assertEqual('Message', self._flash()['message']) 168 | 169 | self.response = self.client.get(settings.MEDIA_URL + 'test.css') 170 | self.assertEqual(200, self.response.status_code) 171 | 172 | self.response = self.client.get(reverse(views.render_template)) 173 | self.assertEqual('Message', self._flash()['message']) 174 | 175 | # Flash value will be removed when this request hits the app 176 | self.response = self.client.get(reverse(views.render_template)) 177 | self.assertFalse('message' in self._flash()) 178 | 179 | def test_request_to_serve_view_with_default_value(self): 180 | """Integration: request to static resources should not trigger the flash update in debug mode. 181 | """ 182 | # Deletes the setting, let the middleware figure out the default value 183 | if hasattr(settings, 'FLASH_IGNORE_MEDIA'): 184 | del settings.FLASH_IGNORE_MEDIA 185 | 186 | self.response = self.client.get(reverse(views.set_flash_var)) 187 | self.assertEqual('Message', self._flash()['message']) 188 | 189 | self.response = self.client.get(settings.MEDIA_URL + 'test.css') 190 | self.assertEqual(200, self.response.status_code) 191 | 192 | self.response = self.client.get(reverse(views.render_template)) 193 | self.assertEqual('Message', self._flash()['message']) 194 | 195 | # Flash value will be removed when this request hits the app 196 | self.response = self.client.get(reverse(views.render_template)) 197 | self.assertFalse('message' in self._flash()) 198 | 199 | def test_flash_with_common_middleware_and_missing_trailing_slash(self): 200 | """Integration: missing trailing slash in URL should not affect the flash lifecycle when using the CommonMiddleware. 201 | """ 202 | self.response = self.client.get(reverse(views.set_flash_var)) 203 | self.assertEqual('Message', self._flash()['message']) 204 | 205 | # This request should be intercepted by CommonMiddleware and the flash should not be updated 206 | self.response = self.client.get('/default') 207 | 208 | self.response = self.client.get(reverse(views.render_template)) 209 | self.assertEqual('Message', self._flash()['message']) 210 | 211 | # Flash value will be removed when this request hits the app 212 | self.response = self.client.get(reverse(views.render_template)) 213 | self.assertFalse('message' in self._flash()) 214 | -------------------------------------------------------------------------------- /src/djangoflash/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides the :class:`FlashScope` class, which provides a simple 4 | way to pass temporary objects between views. 5 | """ 6 | 7 | 8 | # Map keys used when exporting/importing a FlashScope to/from a dict 9 | _SESSION_KEY = '_session' 10 | _USED_KEY = '_used' 11 | 12 | 13 | class FlashScope(object): 14 | """The purpose of this class is to implement the *flash*, which is a 15 | temporary storage mechanism that looks like a Python dictionary, so you 16 | can store values associated with keys and later retrieve them. 17 | 18 | It has one special property: by default, values stored into the *flash* 19 | during the processing of a request will be available during the processing 20 | of the immediately following request. Once that second request has been 21 | processed, those values are removed automatically from the storage. 22 | 23 | The following operations are supported by :class:`FlashScope` instances: 24 | 25 | .. describe:: len(flash) 26 | 27 | Returns the number of items in *flash*. 28 | 29 | .. describe:: flash[key] 30 | 31 | Returns the item of *flash* with key *key*. Raises a :exc:`KeyError` if 32 | *key* is not found. 33 | 34 | .. describe:: flash[key] = value 35 | 36 | Sets ``flash[key]`` to *value*. 37 | 38 | .. describe:: del flash[key] 39 | 40 | Removes ``flash[key]``. Raises a :exc:`KeyError` if *key* is not found. 41 | 42 | .. describe:: key in flash 43 | 44 | Returns ``True`` if *flash* has a key *key*, else ``False``. 45 | 46 | .. describe:: key not in flash 47 | 48 | Equivalent to ``not key in flash``. 49 | 50 | .. describe:: flash.now[key] = value 51 | 52 | Sets ``flash[key]`` to *value* and marks it as *used*. 53 | 54 | .. describe:: flash.now(**items) 55 | 56 | Puts *items* into *flash* and marks those items as *used*. 57 | 58 | .. describe:: flash.now.add(key, *values) 59 | 60 | Appends one or more *values* to *key* in *flash*. 61 | """ 62 | 63 | def __init__(self, data=None): 64 | """Returns a new flash. If *data* is not provided, an empty flash is 65 | returned. Otherwise, the given *data* will be used to pre-populate 66 | this flash. 67 | """ 68 | self.now = _ImmediateFlashScopeAdapter(self) 69 | if data: 70 | self._import_data(data) 71 | else: 72 | self._session, self._used = {}, {} 73 | 74 | def __contains__(self, key): 75 | """Returns ``True`` if there's a value under the given *key*. 76 | """ 77 | return key in self._session 78 | 79 | def __getitem__(self, key): 80 | """Retrieves a value. Raises a :exc:`KeyError` if *key* does not exists. 81 | """ 82 | return self._session[key] 83 | 84 | def __setitem__(self, key, value): 85 | """Puts a *value* under the given *key*. 86 | """ 87 | self._session[key] = value 88 | self._update_status(key, is_used=False) 89 | 90 | def __delitem__(self, key): 91 | """Removes the value under the given *key*. 92 | """ 93 | if key in self: 94 | del self._session[key] 95 | if key in self._used: 96 | del self._used[key] 97 | 98 | def __len__(self): 99 | """Returns the number of values inside this flash. 100 | """ 101 | return len(self._session) 102 | 103 | def _update_status(self, key=None, is_used=True): 104 | """Updates the status of a given value (or all values if no *key* 105 | is given). The *is_used* argument tells if that value should be marked 106 | as *used* (should be discarded) or *unused* (should be kept). 107 | 108 | If a *used* value is being marked as *used* again, it is automatically 109 | removed from this flash. 110 | """ 111 | if not key: 112 | for existing_key in self.keys(): 113 | self._update_status(existing_key, is_used) 114 | else: 115 | if not is_used: 116 | if key in self._used: 117 | del self._used[key] 118 | else: 119 | if key in self._used: 120 | del self[key] 121 | else: 122 | self._used[key] = None 123 | 124 | def keys(self): 125 | """Returns the list of keys. 126 | """ 127 | return self._session.keys() 128 | 129 | def values(self): 130 | """Returns the list of values. 131 | """ 132 | return self._session.values() 133 | 134 | def items(self): 135 | """Returns the list of items as tuples ``(key, value)``. 136 | """ 137 | return self._session.items() 138 | 139 | def iterkeys(self): 140 | """Returns an iterator over the keys. 141 | """ 142 | return self._session.iterkeys() 143 | 144 | def itervalues(self): 145 | """Returns an iterator over the values. 146 | """ 147 | return self._session.itervalues() 148 | 149 | def iteritems(self): 150 | """Returns an iterator over the ``(key, value)`` items. 151 | """ 152 | return self._session.iteritems() 153 | 154 | def get(self, key, default=None): 155 | """Gets the value under the given *key*. If the *key* is not found, 156 | *default* is returned instead. 157 | """ 158 | return self._session.get(key, default) 159 | 160 | def pop(self, key, default=None): 161 | """Removes the specified *key* and returns the corresponding value. If 162 | *key* is not found, *default* is returned instead. 163 | """ 164 | value = self._session.pop(key, default) 165 | if key in self._used: 166 | del self._used[key] 167 | return value 168 | 169 | def put(self, **kwargs): 170 | """Puts one or more values into this flash. 171 | """ 172 | for key, value in kwargs.items(): 173 | self[key] = value 174 | 175 | def add(self, key, *values): 176 | """Appends one or more *values* to *key* in this flash. 177 | """ 178 | if key in self: 179 | current_value = self[key] 180 | if not isinstance(current_value, list): 181 | self[key] = [current_value] 182 | self[key].extend(values) 183 | else: 184 | current_value.extend(values) 185 | self[key] = current_value 186 | else: 187 | self[key] = list(values) 188 | 189 | def clear(self): 190 | """Removes all items from this flash. 191 | """ 192 | self._session.clear() 193 | self._used.clear() 194 | 195 | def discard(self, *keys): 196 | """Marks the entire current flash or a single value as *used*, so when 197 | the next request hit the server, those values will be automatically 198 | removed from this flash by :class:`FlashMiddleware`. 199 | """ 200 | self._update_status(*keys) 201 | 202 | def keep(self, *keys): 203 | """Prevents specific values from being removed on the next request. 204 | If this method is called with no args, the entire flash is preserved. 205 | """ 206 | if not keys: 207 | self._update_status(is_used=False) 208 | else: 209 | for key in keys: 210 | self._update_status(key, is_used=False) 211 | 212 | def update(self): 213 | """Mark for removal entries that were kept, and delete unkept ones. 214 | 215 | .. note:: 216 | This method is called automatically by 217 | :class:`djangoflash.middleware.FlashMiddleware` when a HTTP 218 | request hits the server, so never call this method yourself, unless 219 | you have a very good reason to do so. 220 | """ 221 | self._update_status() 222 | 223 | def to_dict(self): 224 | """Exports this flash to a :class:`dict`. 225 | """ 226 | return {_SESSION_KEY: self._session.copy(), 227 | _USED_KEY : self._used.copy()} 228 | 229 | def _import_data(self, data): 230 | """Imports the given :class:`dict` to this flash. 231 | """ 232 | if not isinstance(data, dict): 233 | raise TypeError('Expected a dictionary') 234 | 235 | if not _SESSION_KEY in data or not _USED_KEY in data: 236 | raise ValueError("Dictionary doesn't contains the expected data") 237 | 238 | if not isinstance(data[_SESSION_KEY], dict): 239 | raise ValueError("data['%s'] must be a dict." % _SESSION_KEY) 240 | 241 | if not isinstance(data[_USED_KEY], dict): 242 | raise ValueError("data['%s'] must be a dict." % _USED_KEY) 243 | 244 | self._session = data[_SESSION_KEY].copy() 245 | self._used = data[_USED_KEY].copy() 246 | 247 | 248 | class _ImmediateFlashScopeAdapter(object): 249 | """This class is used to add support for immediate flash values to an 250 | existing instance of :class:`FlashScope`. An immediate flash value is a 251 | value that is available to this request, but not to the next. 252 | """ 253 | 254 | def __init__(self, delegate): 255 | """Returns a new flash wrapper which delegates certain calls to the 256 | given *delegate*. 257 | """ 258 | self.delegate = delegate 259 | 260 | def __getitem__(self, key): 261 | """Retrieves a value. Raises a :exc:`KeyError` if *key* does 262 | not exists. 263 | """ 264 | return self.delegate[key] 265 | 266 | def __contains__(self, key): 267 | """Returns ``True`` if there's a value under the given *key*. 268 | """ 269 | return key in self.delegate 270 | 271 | def __setitem__(self, key, value): 272 | """Puts a *value* into this flash under the given *key*. 273 | """ 274 | self.delegate[key] = value 275 | self.delegate.discard(key) 276 | 277 | def put(self, **kwargs): 278 | """Puts one or more values into this flash. 279 | """ 280 | for key, value in kwargs.items(): 281 | self[key] = value 282 | 283 | def add(self, key, *values): 284 | """Appends one or more values to a key in this flash. 285 | """ 286 | self.delegate.add(key, *values) 287 | self.delegate.discard(key) 288 | -------------------------------------------------------------------------------- /ez_setup.py: -------------------------------------------------------------------------------- 1 | #!python 2 | """Bootstrap setuptools installation 3 | 4 | If you want to use setuptools in your package's setup.py, just include this 5 | file in the same directory with it, and add this to the top of your setup.py:: 6 | 7 | from ez_setup import use_setuptools 8 | use_setuptools() 9 | 10 | If you want to require a specific version of setuptools, set a download 11 | mirror, or use an alternate download directory, you can do so by supplying 12 | the appropriate options to ``use_setuptools()``. 13 | 14 | This file can also be run as a script to install or upgrade setuptools. 15 | """ 16 | import sys 17 | DEFAULT_VERSION = "0.6c11" 18 | DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] 19 | 20 | md5_data = { 21 | 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 22 | 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 23 | 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 24 | 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 25 | 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 26 | 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 27 | 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 28 | 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 29 | 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 30 | 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 31 | 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', 32 | 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', 33 | 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', 34 | 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', 35 | 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', 36 | 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', 37 | 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', 38 | 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', 39 | 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 40 | 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 41 | 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 42 | 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 43 | 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 44 | 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 45 | 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 46 | 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 47 | 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 48 | 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 49 | 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 50 | 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 51 | 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 52 | 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 53 | 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 54 | 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 55 | 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', 56 | 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 57 | 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 58 | 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', 59 | 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', 60 | 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', 61 | 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', 62 | 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', 63 | } 64 | 65 | import sys, os 66 | try: from hashlib import md5 67 | except ImportError: from md5 import md5 68 | 69 | def _validate_md5(egg_name, data): 70 | if egg_name in md5_data: 71 | digest = md5(data).hexdigest() 72 | if digest != md5_data[egg_name]: 73 | print >>sys.stderr, ( 74 | "md5 validation of %s failed! (Possible download problem?)" 75 | % egg_name 76 | ) 77 | sys.exit(2) 78 | return data 79 | 80 | def use_setuptools( 81 | version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, 82 | download_delay=15 83 | ): 84 | """Automatically find/download setuptools and make it available on sys.path 85 | 86 | `version` should be a valid setuptools version number that is available 87 | as an egg for download under the `download_base` URL (which should end with 88 | a '/'). `to_dir` is the directory where setuptools will be downloaded, if 89 | it is not already available. If `download_delay` is specified, it should 90 | be the number of seconds that will be paused before initiating a download, 91 | should one be required. If an older version of setuptools is installed, 92 | this routine will print a message to ``sys.stderr`` and raise SystemExit in 93 | an attempt to abort the calling script. 94 | """ 95 | was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules 96 | def do_download(): 97 | egg = download_setuptools(version, download_base, to_dir, download_delay) 98 | sys.path.insert(0, egg) 99 | import setuptools; setuptools.bootstrap_install_from = egg 100 | try: 101 | import pkg_resources 102 | except ImportError: 103 | return do_download() 104 | try: 105 | pkg_resources.require("setuptools>="+version); return 106 | except pkg_resources.VersionConflict, e: 107 | if was_imported: 108 | print >>sys.stderr, ( 109 | "The required version of setuptools (>=%s) is not available, and\n" 110 | "can't be installed while this script is running. Please install\n" 111 | " a more recent version first, using 'easy_install -U setuptools'." 112 | "\n\n(Currently using %r)" 113 | ) % (version, e.args[0]) 114 | sys.exit(2) 115 | else: 116 | del pkg_resources, sys.modules['pkg_resources'] # reload ok 117 | return do_download() 118 | except pkg_resources.DistributionNotFound: 119 | return do_download() 120 | 121 | def download_setuptools( 122 | version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, 123 | delay = 15 124 | ): 125 | """Download setuptools from a specified location and return its filename 126 | 127 | `version` should be a valid setuptools version number that is available 128 | as an egg for download under the `download_base` URL (which should end 129 | with a '/'). `to_dir` is the directory where the egg will be downloaded. 130 | `delay` is the number of seconds to pause before an actual download attempt. 131 | """ 132 | import urllib2, shutil 133 | egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) 134 | url = download_base + egg_name 135 | saveto = os.path.join(to_dir, egg_name) 136 | src = dst = None 137 | if not os.path.exists(saveto): # Avoid repeated downloads 138 | try: 139 | from distutils import log 140 | if delay: 141 | log.warn(""" 142 | --------------------------------------------------------------------------- 143 | This script requires setuptools version %s to run (even to display 144 | help). I will attempt to download it for you (from 145 | %s), but 146 | you may need to enable firewall access for this script first. 147 | I will start the download in %d seconds. 148 | 149 | (Note: if this machine does not have network access, please obtain the file 150 | 151 | %s 152 | 153 | and place it in this directory before rerunning this script.) 154 | ---------------------------------------------------------------------------""", 155 | version, download_base, delay, url 156 | ); from time import sleep; sleep(delay) 157 | log.warn("Downloading %s", url) 158 | src = urllib2.urlopen(url) 159 | # Read/write all in one block, so we don't create a corrupt file 160 | # if the download is interrupted. 161 | data = _validate_md5(egg_name, src.read()) 162 | dst = open(saveto,"wb"); dst.write(data) 163 | finally: 164 | if src: src.close() 165 | if dst: dst.close() 166 | return os.path.realpath(saveto) 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | def main(argv, version=DEFAULT_VERSION): 204 | """Install or upgrade setuptools and EasyInstall""" 205 | try: 206 | import setuptools 207 | except ImportError: 208 | egg = None 209 | try: 210 | egg = download_setuptools(version, delay=0) 211 | sys.path.insert(0,egg) 212 | from setuptools.command.easy_install import main 213 | return main(list(argv)+[egg]) # we're done here 214 | finally: 215 | if egg and os.path.exists(egg): 216 | os.unlink(egg) 217 | else: 218 | if setuptools.__version__ == '0.0.1': 219 | print >>sys.stderr, ( 220 | "You have an obsolete version of setuptools installed. Please\n" 221 | "remove it from your system entirely before rerunning this script." 222 | ) 223 | sys.exit(2) 224 | 225 | req = "setuptools>="+version 226 | import pkg_resources 227 | try: 228 | pkg_resources.require(req) 229 | except pkg_resources.VersionConflict: 230 | try: 231 | from setuptools.command.easy_install import main 232 | except ImportError: 233 | from easy_install import main 234 | main(list(argv)+[download_setuptools(delay=0)]) 235 | sys.exit(0) # try to force an exit 236 | else: 237 | if argv: 238 | from setuptools.command.easy_install import main 239 | main(argv) 240 | else: 241 | print "Setuptools version",version,"or greater has been installed." 242 | print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' 243 | 244 | def update_md5(filenames): 245 | """Update our built-in md5 registry""" 246 | 247 | import re 248 | 249 | for name in filenames: 250 | base = os.path.basename(name) 251 | f = open(name,'rb') 252 | md5_data[base] = md5(f.read()).hexdigest() 253 | f.close() 254 | 255 | data = [" %r: %r,\n" % it for it in md5_data.items()] 256 | data.sort() 257 | repl = "".join(data) 258 | 259 | import inspect 260 | srcfile = inspect.getsourcefile(sys.modules[__name__]) 261 | f = open(srcfile, 'rb'); src = f.read(); f.close() 262 | 263 | match = re.search("\nmd5_data = {\n([^}]+)}", src) 264 | if not match: 265 | print >>sys.stderr, "Internal error!" 266 | sys.exit(2) 267 | 268 | src = src[:match.start(1)] + repl + src[match.end(1):] 269 | f = open(srcfile,'w') 270 | f.write(src) 271 | f.close() 272 | 273 | 274 | if __name__=='__main__': 275 | if len(sys.argv)>2 and sys.argv[1]=='--md5update': 276 | update_md5(sys.argv[2:]) 277 | else: 278 | main(sys.argv[1:]) 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /src/djangoflash/tests/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """djangoflash.models test cases. 4 | """ 5 | 6 | from unittest import TestCase 7 | 8 | from djangoflash.models import FlashScope, _SESSION_KEY, _USED_KEY 9 | 10 | 11 | class FlashScopeTestCase(TestCase): 12 | """Tests the FlashScope object. 13 | """ 14 | def setUp(self): 15 | """Create a FlashScope object to be used by the test methods. 16 | """ 17 | self.flash = FlashScope() 18 | self.flash['info'] = 'Info' 19 | 20 | def test_restore(self): 21 | """FlashScope: Should restore the flash using a dict. 22 | """ 23 | data = {_SESSION_KEY: {'info' : 'Info', 24 | 'error': 'Error'}, 25 | _USED_KEY : {'error': None}} 26 | self.flash = FlashScope(data) 27 | self.assertEqual(2, len(self.flash)) 28 | self.assertEqual('Info', self.flash['info']) 29 | self.assertEqual('Error', self.flash['error']) 30 | self.flash.update() 31 | self.assertEqual('Info', self.flash['info']) 32 | self.assertFalse('error' in self.flash) 33 | 34 | def test_restore_immutability(self): 35 | """FlashScope: Should restore the flash using a shallow copy of a dict. 36 | """ 37 | data = {_SESSION_KEY: {'info' : 'Info', 38 | 'error': 'Error'}, 39 | _USED_KEY : {'error': None}} 40 | self.flash = FlashScope(data) 41 | self.assertEqual('Info', self.flash['info']) 42 | del data[_SESSION_KEY]['info'] 43 | self.assertTrue('info' in self.flash) 44 | 45 | def test_restore_with_invalid_type(self): 46 | """FlashScope: Should not restore the flash using an invalid object. 47 | """ 48 | self.assertRaises(TypeError, lambda: FlashScope('invalid_data')) 49 | 50 | def test_restore_with_invalid_keys(self): 51 | """FlashScope: Should not restore the flash using a dict with invalid keys. 52 | """ 53 | data = {_SESSION_KEY: None} 54 | self.assertRaises(ValueError, lambda: FlashScope(data)) 55 | data = {_USED_KEY: None} 56 | self.assertRaises(ValueError, lambda: FlashScope(data)) 57 | 58 | def test_restore_with_invalid_values(self): 59 | """FlashScope: Should not restore the flash using a dict with invalid values. 60 | """ 61 | data = {_SESSION_KEY: {}, _USED_KEY: None} 62 | self.assertRaises(ValueError, lambda: FlashScope(data)) 63 | data = {_SESSION_KEY: None, _USED_KEY: {}} 64 | self.assertRaises(ValueError, lambda: FlashScope(data)) 65 | 66 | def test_contains(self): 67 | """FlashScope: "key in flash" syntax should be supported. 68 | """ 69 | self.assertFalse('error' in self.flash) 70 | self.assertEqual('Info', self.flash['info']) 71 | 72 | def test_get_invalid_item(self): 73 | """FlashScope: Should raise KeyError if trying to get an invalid value. 74 | """ 75 | self.assertRaises(KeyError, lambda: self.flash['error']); 76 | 77 | def test_set_item(self): 78 | """FlashScope: flash[key] = value" syntax should be supported. 79 | """ 80 | self.flash['error'] = 'Error' 81 | self.assertEqual('Error', self.flash['error']); 82 | 83 | def test_del_item(self): 84 | """FlashScope: "del flash[key]" syntax should be supported. 85 | """ 86 | self.assertEqual('Info', self.flash['info']) 87 | del self.flash['info'] 88 | self.assertFalse('info' in self.flash) 89 | 90 | def test_clear(self): 91 | """FlashScope: flash.clear() should remove all items from the flash scope. 92 | """ 93 | self.flash['error'] = 'Error' 94 | self.assertEqual(2, len(self.flash)) 95 | self.flash.clear() 96 | self.assertEqual(0, len(self.flash)) 97 | 98 | def test_len(self): 99 | """FlashScope: "len(flash)" syntax should be supported. 100 | """ 101 | self.assertEqual(1, len(self.flash)) 102 | 103 | def test_keys(self): 104 | """FlashScope: Should return the list of keys stored in the flash scope. 105 | """ 106 | self.assertEqual(['info'], self.flash.keys()) 107 | 108 | def test_values(self): 109 | """FlashScope: Should return the list of values stored in the flash scope. 110 | """ 111 | self.assertEqual(['Info'], self.flash.values()) 112 | 113 | def test_items(self): 114 | """FlashScope: Should return the list of items stored in the flash scope. 115 | """ 116 | self.assertEqual([('info', 'Info')], self.flash.items()) 117 | 118 | def test_iterkeys(self): 119 | """FlashScope: Should return an iterator to the keys stored in the flash scope. 120 | """ 121 | iterator = self.flash.iterkeys() 122 | self.assertEqual('info', iterator.next()) 123 | self.assertRaises(StopIteration, iterator.next) 124 | 125 | def test_itervalues(self): 126 | """FlashScope: Should return an iterator to the values stored in the flash scope. 127 | """ 128 | iterator = self.flash.itervalues() 129 | self.assertEqual('Info', iterator.next()) 130 | self.assertRaises(StopIteration, iterator.next) 131 | 132 | def test_iteritems(self): 133 | """FlashScope: Should return an iterator to the items stored in the flash scope. 134 | """ 135 | iterator = self.flash.iteritems() 136 | self.assertEqual(('info', 'Info'), iterator.next()) 137 | self.assertRaises(StopIteration, iterator.next) 138 | 139 | def test_add_with_existing_non_list_value(self): 140 | """FlashScope: Should append a value to a key even if the current value is not a list. 141 | """ 142 | self.flash.add('info', 'Error') 143 | self.assertEqual(['Info', 'Error'], self.flash['info']) 144 | 145 | def test_add_with_existing_list_value(self): 146 | """FlashScope: Should append a value if the current value is a list. 147 | """ 148 | self.flash['error'] = ['Error 1'] 149 | self.flash.add('error', 'Error 2') 150 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 151 | 152 | def test_add_with_non_existing_value(self): 153 | """FlashScope: Should add a value even if the given key doesn't exists. 154 | """ 155 | self.flash.add('error', 'Error') 156 | self.assertEqual(['Error'], self.flash['error']) 157 | 158 | def test_add_across_requests(self): 159 | """FlashScope: Should keep a key when a value is appended to it. 160 | """ 161 | self.flash['error'] = 'Error 1' 162 | self.flash.update() 163 | self.flash.add('error', 'Error 2') 164 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 165 | self.flash.update() 166 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 167 | self.flash.update() 168 | self.assertFalse('error' in self.flash) 169 | 170 | def test_get(self): 171 | """FlashScope: Should return a default value if the given key doesn' exists. 172 | """ 173 | self.assertEqual('Oops', self.flash.get('error', 'Oops')) 174 | self.assertEqual('Info', self.flash.get('info', 'Something')) 175 | self.assertEqual(None, self.flash.get('error')) 176 | 177 | def test_pop(self): 178 | """FlashScope: Should pop a value from the flash scope. 179 | """ 180 | self.assertEqual(None, self.flash.pop('error')) 181 | self.assertEqual('Info', self.flash.pop('info')) 182 | self.assertFalse('info' in self.flash) 183 | 184 | def test_pop_used_value(self): 185 | """FlashScope: Should pop a used value from the flash scope. 186 | """ 187 | self.flash.update() 188 | self.assertEqual('Info', self.flash.pop('info')) 189 | self.assertFalse('info' in self.flash) 190 | 191 | def test_put(self): 192 | """FlashScope: Should put several keys into the flash scope at the same time. 193 | """ 194 | self.flash.put(warn='Warning', error='Error') 195 | self.assertEqual('Warning', self.flash['warn']) 196 | self.assertEqual('Error', self.flash['error']) 197 | 198 | def test_discard(self): 199 | """FlashScope: Should mark a value for removal. 200 | """ 201 | self.flash.discard() 202 | self.flash.update() 203 | self.assertFalse('info' in self.flash) 204 | 205 | def test_keep(self): 206 | """FlashScope: Should avoid the removal of specific values. 207 | """ 208 | self.flash.update() 209 | self.flash.keep('info') 210 | self.flash.update() 211 | self.assertEqual('Info', self.flash['info']) 212 | self.flash.update() 213 | self.assertFalse('info' in self.flash) 214 | 215 | def test_keep_all(self): 216 | """FlashScope: Should avoid the removal of all values. 217 | """ 218 | self.flash.update() 219 | self.flash.keep() 220 | self.flash.update() 221 | self.assertEqual('Info', self.flash['info']) 222 | self.flash.update() 223 | self.assertFalse('info' in self.flash) 224 | 225 | def test_replace_used_value(self): 226 | """FlashScope: Should keep a key when its value is replaced. 227 | """ 228 | self.flash.update() 229 | self.assertEqual('Info', self.flash['info']) 230 | self.flash['info'] = 'Error' 231 | self.assertEqual('Error', self.flash['info']) 232 | self.flash.update() 233 | self.assertEqual('Error', self.flash['info']) 234 | self.flash.update() 235 | self.assertFalse('info' in self.flash) 236 | 237 | def test_empty_to_dict(self): 238 | """FlashScope: Should export the flash data to a dict even if it's empty. 239 | """ 240 | self.flash = FlashScope() 241 | expected_data = {_SESSION_KEY: {}, _USED_KEY:{}} 242 | data = self.flash.to_dict() 243 | self.assertEqual(expected_data, data) 244 | 245 | def test_to_dict(self): 246 | """FlashScope: Should export the flash data to a dict. 247 | """ 248 | self.flash.update() 249 | self.flash['error'] = 'Error' 250 | expected_data = {_SESSION_KEY: {'info' : 'Info', 251 | 'error': 'Error'}, 252 | _USED_KEY : {'info' : None}} 253 | data = self.flash.to_dict() 254 | self.assertEqual(expected_data, data) 255 | 256 | def test_to_dict_immutability(self): 257 | """FlashScope: Should export a copy of the flash data as a dict. 258 | """ 259 | data = self.flash.to_dict() 260 | del self.flash['info'] 261 | self.assertEqual('Info', data[_SESSION_KEY]['info']) 262 | 263 | 264 | class ImmediateFlashScope(TestCase): 265 | """Tests the ``Flashscope.now``. 266 | """ 267 | def setUp(self): 268 | """Create a FlashScope object to be used by the test methods. 269 | """ 270 | self.flash = FlashScope() 271 | self.flash.now['info'] = 'Info' 272 | 273 | def test_now(self): 274 | """FlashScope.now: "flash.now[key] = value" syntax should be supported. 275 | """ 276 | self.assertEqual('Info', self.flash['info']) 277 | self.flash.update() 278 | self.assertFalse('info' in self.flash) 279 | 280 | def test_alternative_now(self): 281 | """FlashScope.now: Immediate values (flash.now) should be supported. 282 | """ 283 | self.flash.now.put(error='Error') 284 | self.assertEqual('Error', self.flash['error']) 285 | self.flash.update() 286 | self.assertFalse('error' in self.flash) 287 | 288 | def test_contains(self): 289 | """FlashScope.now: "key in flash.now" syntax should be supported. 290 | """ 291 | self.assertFalse('error' in self.flash.now) 292 | self.flash.now['error'] = 'Error' 293 | self.assertTrue('error' in self.flash.now) 294 | 295 | def test_get_invalid_item(self): 296 | """FlashScope.now: Should raise KeyError if trying to get an invalid item. 297 | """ 298 | self.assertRaises(KeyError, lambda: self.flash.now['error']); 299 | 300 | def test_add_with_non_existing_value(self): 301 | """FlashScope.now: Should append an immediate value even if the given key doesn't exists. 302 | """ 303 | self.flash.now.add('error', 'Error 1') 304 | self.flash.now.add('error', 'Error 2', 'Error 3') 305 | self.assertEqual(['Error 1', 'Error 2', 'Error 3'], self.flash['error']) 306 | 307 | def test_add_with_existing_non_list_value(self): 308 | """FlashScope.now: Should append immediate values to a key even if the current value is not a list. 309 | """ 310 | self.flash.now.add('info', 'Error 1') 311 | self.flash.now.add('info', 'Error 2', 'Error 3') 312 | self.assertEqual(['Info', 'Error 1', 'Error 2', 'Error 3'], self.flash['info']) 313 | 314 | def test_add_with_existing_list_value(self): 315 | """FlashScope.now: Should append an immediate value if the current value is a list. 316 | """ 317 | self.flash.now['error'] = ['Error 1'] 318 | self.flash.now.add('error', 'Error 2') 319 | self.flash.now.add('error', 'Error 3', 'Error 4') 320 | self.assertEqual(['Error 1', 'Error 2', 'Error 3', 'Error 4'], self.flash['error']) 321 | 322 | 323 | class MixedFlashScope(TestCase): 324 | """Tests mixing regular and immediate values. 325 | """ 326 | def setUp(self): 327 | """Create a FlashScope object to be used by the test methods. 328 | """ 329 | self.flash = FlashScope() 330 | 331 | def test_replace_with_immediate_value(self): 332 | """FlashScope: Should replace a regular value by an immediate value. 333 | """ 334 | self.flash['info'] = 'Info' 335 | self.flash.update() 336 | self.assertEqual('Info', self.flash['info']) 337 | self.flash.now['info'] = 'Error' 338 | self.assertEqual('Error', self.flash['info']) 339 | self.flash.update() 340 | self.assertFalse('info' in self.flash) 341 | 342 | def test_replace_immediate_with_regular_value(self): 343 | """FlashScope: Should replace an immediate value with a regular value. 344 | """ 345 | self.flash.now['info'] = 'Info' 346 | self.assertEqual('Info', self.flash['info']) 347 | self.flash['info'] = 'Error' 348 | self.flash.update() 349 | self.assertEqual('Error', self.flash['info']) 350 | self.flash.update() 351 | self.assertFalse('info' in self.flash) 352 | 353 | def test_add_immediate_with_existing_regular_value(self): 354 | """FlashScope.now: Should add an immediate value to a regular key, expiring on the current request. 355 | """ 356 | self.flash['error'] = 'Error 1' 357 | self.flash.now.add('error', 'Error 2') 358 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 359 | self.flash.update() 360 | self.assertFalse('error' in self.flash) 361 | 362 | def test_add_immediate_with_existing_regular_list(self): 363 | """FlashScope.now: Should add an immediate value to a regular list, expiring on the current request. 364 | """ 365 | self.flash['error'] = ['Error 1'] 366 | self.flash.now.add('error', 'Error 2') 367 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 368 | self.flash.update() 369 | self.assertFalse('error' in self.flash) 370 | 371 | def test_add_regular_with_existing_immediate_value(self): 372 | """FlashScope: Should add a regular value to an immediate key, expiring on the next request. 373 | """ 374 | self.flash.now['error'] = 'Error 1' 375 | self.flash.add('error', 'Error 2') 376 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 377 | self.flash.update() 378 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 379 | self.flash.update() 380 | self.assertFalse('error' in self.flash) 381 | 382 | def test_add_regular_with_existing_immediate_list(self): 383 | """FlashScope: Should add a regular value to an immediate list, expiring on the next request. 384 | """ 385 | self.flash.now['error'] = ['Error 1'] 386 | self.flash.add('error', 'Error 2') 387 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 388 | self.flash.update() 389 | self.assertEqual(['Error 1', 'Error 2'], self.flash['error']) 390 | self.flash.update() 391 | self.assertFalse('error' in self.flash) 392 | --------------------------------------------------------------------------------