Index: django/contrib/messages/restrictions.py
===================================================================
--- django/contrib/messages/restrictions.py	(revision 0)
+++ django/contrib/messages/restrictions.py	(revision 0)
@@ -0,0 +1,102 @@
+import time as time_provider
+
+
+class Restriction (object):
+    JSON_SEPARATOR = '@'
+
+    def __init__(self, name):
+        self.type = name
+
+    def is_expired(self):
+        """
+        indicates whether given message should be removed
+        """
+        raise NotImplementedError ()
+
+    def on_display (self):
+        """
+        called when iterated - does nothing by default
+        """
+        pass
+
+    def to_json(self):
+        """
+        returns json representation of restriction
+        """
+        raise NotImplementedError
+
+    def from_json_param(self, *args):
+        """
+        returns restriction on the basis of data encoded in json
+        """
+        raise NotImplementedError
+
+
+    def __cmp__(self, other):
+        if self.__eq__(other): return 0
+        return -1
+
+class TimeRestriction (Restriction):
+    JSON_TYPE_CODE = 't'
+
+    def __init__(self, seconds):
+        """
+        seconds - expiration time since now
+        """
+        Restriction.__init__(self, 'time')
+        created = time_provider.time()
+        self.expires = created + int(seconds)
+
+    def set_expirity_time(self, expiration_time):
+        """
+        Sets expilcity expiration time
+        """
+        self.expires = int(expiration_time)
+
+    def is_expired (self):
+        return self.expires < time_provider.time()
+
+    def __eq__(self, other):
+        return self.type == other.type and not bool(self.expires ^ other.expires)
+
+    def __hash__(self):
+        return self.expires
+
+    def to_json(self):
+        return '%s%s%s' % (self.JSON_TYPE_CODE, self.JSON_SEPARATOR, self.expires)
+
+    @classmethod
+    def from_json_param(cls, expirity_time):
+        ret = TimeRestriction(0)
+        ret.set_expirity_time(expirity_time)
+        return ret
+
+
+
+class AmountRestriction (Restriction):
+    JSON_TYPE_CODE = 'a'
+
+    def __init__(self, amount):
+        assert int(amount) >= 0
+        Restriction.__init__(self, 'amount')
+        self.can_be_shown = int(amount)
+
+    def on_display (self):
+        self.can_be_shown -= 1
+
+    def is_expired(self):
+        return int(self.can_be_shown) <= 0
+
+    def __eq__(self, other):
+        return self.type == other.type and not bool(self.can_be_shown ^ other.can_be_shown)
+
+
+    def __hash__(self):
+        return self.can_be_shown
+
+    def to_json(self):
+        return '%s%s%s' % (self.JSON_TYPE_CODE, self.JSON_SEPARATOR, self.can_be_shown)
+
+    @classmethod
+    def from_json_param(cls, amount):
+        return AmountRestriction(amount)
Index: django/contrib/messages/api.py
===================================================================
--- django/contrib/messages/api.py	(revision 16578)
+++ django/contrib/messages/api.py	(working copy)
@@ -12,12 +12,12 @@
     pass
 
 
-def add_message(request, level, message, extra_tags='', fail_silently=False):
+def add_message(request, level, message, extra_tags='', fail_silently=False, restrictions=[]):
     """
     Attempts to add a message to the request using the 'messages' app.
     """
     if hasattr(request, '_messages'):
-        return request._messages.add(level, message, extra_tags)
+        return request._messages.add(level, message, extra_tags, restrictions = restrictions)
     if not fail_silently:
         raise MessageFailure('You cannot add messages without installing '
                     'django.contrib.messages.middleware.MessageMiddleware')
Index: django/contrib/messages/tests/restrictions.py
===================================================================
--- django/contrib/messages/tests/restrictions.py	(revision 0)
+++ django/contrib/messages/tests/restrictions.py	(revision 0)
@@ -0,0 +1,30 @@
+from django.test import TestCase
+from django.contrib.messages import restrictions
+from django.contrib.messages.restrictions import AmountRestriction, TimeRestriction
+from django.contrib.messages.tests.time_provider import TestTimeProvider
+
+restrictions.time_provider = TestTimeProvider ()
+
+class RestrictionsTest(TestCase):
+    def __check_expired(self, amount_restriction, iterations_amount):
+        """
+        Checks whether after iterations_amount of on_displayate given restriction will become expired
+        But before iterations_amount given amount_restriction must not indicate is_expired
+        """
+        for i in range(iterations_amount):
+            self.assertFalse(amount_restriction.is_expired())
+            amount_restriction.on_display()
+        self.assertTrue(amount_restriction.is_expired())
+
+    def test_amount_restrictions(self):
+        res = AmountRestriction(4)
+        self.__check_expired(res, 4)
+
+    def test_amount_restrictions_invalid_argument(self):
+        self.assertRaises(AssertionError, AmountRestriction, -1)
+
+    def test_equal(self):
+        self.assertEqual(AmountRestriction(5), AmountRestriction(5))
+        self.assertFalse(AmountRestriction(1) == AmountRestriction(3))
+        self.assertEqual(TimeRestriction(2), TimeRestriction(2))
+        self.assertFalse(TimeRestriction(3) == TimeRestriction(4))
Index: django/contrib/messages/tests/time_provider.py
===================================================================
--- django/contrib/messages/tests/time_provider.py	(revision 0)
+++ django/contrib/messages/tests/time_provider.py	(revision 0)
@@ -0,0 +1,9 @@
+class TestTimeProvider(object):
+    def __init__(self, act_time = 0):
+        self.act_time = act_time
+    def set_act_time(self, act_time):
+        self.act_time = act_time
+    def time(self):
+        return self.act_time
+    def inc_act_time(self):
+        self.act_time += 1
Index: django/contrib/messages/tests/cookie.py
===================================================================
--- django/contrib/messages/tests/cookie.py	(revision 16578)
+++ django/contrib/messages/tests/cookie.py	(working copy)
@@ -1,5 +1,8 @@
 from django.contrib.messages import constants
 from django.contrib.messages.tests.base import BaseTest
+from django.contrib.messages.tests.time_provider import TestTimeProvider
+from django.contrib.messages import restrictions
+from django.contrib.messages.restrictions import TimeRestriction, AmountRestriction
 from django.contrib.messages.storage.cookie import (CookieStorage,
     MessageEncoder, MessageDecoder)
 from django.contrib.messages.storage.base import Message
@@ -56,10 +59,12 @@
     def test_get(self):
         storage = self.storage_class(self.get_request())
         # Set initial data.
-        example_messages = ['test', 'me']
+        example_messages = [Message(constants.INFO, 'test'), Message(constants.INFO, 'me')]
+        expected_messages = [Message(constants.INFO, 'test', restrictions = [AmountRestriction(0),]),\
+                                     Message(constants.INFO, 'me', restrictions = [AmountRestriction(0),])]
         set_cookie_data(storage, example_messages)
         # Test that the message actually contains what we expect.
-        self.assertEqual(list(storage), example_messages)
+        self.assertEqual(list(storage), expected_messages)
 
     def test_domain(self):
         """
@@ -126,10 +131,13 @@
         instances is properly encoded/decoded by the custom JSON
         encoder/decoder classes.
         """
+        restrictions.time_provider = TestTimeProvider()
+        restrictions.time_provider.set_act_time(0)
         messages = [
             {
                 'message': Message(constants.INFO, 'Test message'),
-                'message_list': [Message(constants.INFO, 'message %s') \
+                'message_list': [Message(constants.INFO, 'message %s', \
+                                 restrictions = [TimeRestriction(5), AmountRestriction(3), TimeRestriction(2), AmountRestriction(10)]) \
                                  for x in xrange(5)] + [{'another-message': \
                                  Message(constants.ERROR, 'error')}],
             },
@@ -139,3 +147,6 @@
         value = encoder.encode(messages)
         decoded_messages = json.loads(value, cls=MessageDecoder)
         self.assertEqual(messages, decoded_messages)
+
+    def transport_storage(self, response, request):
+        request.COOKIES['messages'] = response.cookies['messages'].value
Index: django/contrib/messages/tests/base.py
===================================================================
--- django/contrib/messages/tests/base.py	(revision 16578)
+++ django/contrib/messages/tests/base.py	(working copy)
@@ -3,12 +3,15 @@
 from django.conf import settings
 from django.utils.translation import ugettext_lazy
 from django.utils.unittest import skipIf
-from django.contrib.messages import constants, utils, get_level, set_level
+from django.contrib.messages import constants, utils, get_level, set_level, restrictions
 from django.contrib.messages.api import MessageFailure
+from django.contrib.messages.restrictions import AmountRestriction, TimeRestriction
 from django.contrib.messages.storage import default_storage, base
 from django.contrib.messages.storage.base import Message
+from django.contrib.messages.tests.time_provider import TestTimeProvider
 from django.core.urlresolvers import reverse
 
+restrictions.time_provider = TestTimeProvider()
 
 def skipUnlessAuthIsInstalled(func):
     return skipIf(
@@ -95,6 +98,13 @@
         storage._loaded_data = data or []
         return storage
 
+    def read_storage(self, storage):
+        """
+        Simulates reading all messages from given storage
+        Returns list of read messages
+        """
+        return list(storage)
+
     def test_add(self):
         storage = self.get_storage()
         self.assertFalse(storage.added_new)
@@ -170,7 +180,7 @@
             response = self.client.post(add_url, data, follow=True)
             self.assertRedirects(response, show_url)
             self.assertTrue('messages' in response.context)
-            messages = [Message(self.levels[level], msg) for msg in
+            messages = [Message(self.levels[level], msg, restrictions = [AmountRestriction(0),]) for msg in
                                                          data['messages']]
             self.assertEqual(list(response.context['messages']), messages)
             for msg in data['messages']:
@@ -203,7 +213,7 @@
         """
         settings.MESSAGE_LEVEL = constants.DEBUG
         data = {
-            'messages': ['Test message %d' % x for x in xrange(10)],
+            'messages': ['%d' % x for x in xrange(10)],
         }
         show_url = reverse('django.contrib.messages.tests.urls.show')
         messages = []
@@ -214,8 +224,12 @@
                               args=(level,))
             self.client.post(add_url, data)
         response = self.client.get(show_url)
+        new_messages = []
+        for level in ('debug', 'info', 'success', 'warning', 'error'):
+            new_messages.extend([Message(self.levels[level], msg, \
+                                         restrictions = [AmountRestriction(0),]) for msg in data['messages']])
         self.assertTrue('messages' in response.context)
-        self.assertEqual(list(response.context['messages']), messages)
+        self.assertEqual(list(response.context['messages']), new_messages)
         for msg in data['messages']:
             self.assertContains(response, msg)
 
@@ -392,3 +406,188 @@
             # Ensure the level tags constant is put back like we found it.
             self.restore_setting('MESSAGE_TAGS')
             base.LEVEL_TAGS = utils.get_level_tags()
+
+    def test_storing_restrictoins(self):
+        storage = self.get_storage()
+        response = self.get_response()
+
+        get_res = lambda ar1, ar2, tr1, tr2: [AmountRestriction(ar1), AmountRestriction(ar2), TimeRestriction(tr1), TimeRestriction(tr2)]
+
+        restrictions.time_provider.set_act_time(0)
+        storage.add(constants.WARNING, 'First msg', restrictions = get_res(10, 20, 30, 40))
+        messages = self.__read_messages(storage, response)
+        self.assertEqual(len(messages), 1)
+        rest = messages[0].restrictions
+        for a in (9, 19):
+            self.assertTrue(a in [r.can_be_shown for r in rest if r.type == 'amount'])
+        for t in (30, 40):
+            self.assertTrue(t in [r.expires for r in rest if r.type == 'time'])
+
+        restrictions.time_provider.set_act_time(4)
+        storage = self.get_storage()
+        response = self.get_response()
+        storage.add(constants.WARNING, 'First msg', restrictions = get_res(10, 20, 30, 40))
+        messages = self.__read_messages(storage, response)
+        rest = messages[0].restrictions
+        for a in (9, 19):
+            self.assertTrue(a in [r.can_be_shown for r in rest if r.type == 'amount'])
+        for t in (34, 44):
+            self.assertTrue(t in [r.expires for r in rest if r.type == 'time'])
+
+    def test_expire_default(self):
+        """
+        Tests against message expiration in default behaviour (no explicity defined restrictions on expire -> show only once)
+        """
+        storage = self.get_existing_storage()
+        response = self.get_response()
+
+        list (storage)
+        storage.update(response)
+
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 0)
+
+    def test_expire_after_showing_given_amount_of_times(self):
+        """
+        Tests against expiring message after beeing shown given amount of times
+        """
+        storage = self.get_existing_storage()
+        response = self.get_response()
+
+        read_messages = lambda: self.__read_messages(storage, response)
+
+        storage.add(constants.WARNING, 'Third message, should be shown four times', restrictions = [AmountRestriction(4),])
+        storage.add(constants.ERROR, "Four'th message, should be shown five times", restrictions = [AmountRestriction(5),])
+
+
+        messages = read_messages ()
+        self.assertEqual(len(messages), 4)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 2)
+
+
+        for i in range(2):
+            messages = read_messages ()
+            self.assertEqual(len(messages), 2)
+            storing = self.stored_messages_count(storage, response)
+            self.assertEqual(storing, 2)
+            [self.assertTrue(level in [x.level for x in messages]) for level in (constants.ERROR, constants.WARNING)]
+
+
+        messages = read_messages ()
+        self.assertEqual(len(messages), 2)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 1)
+
+
+        messages = read_messages()
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(messages[0].level, constants.ERROR)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 0)
+
+
+        messages = read_messages()
+        self.assertEqual(len(messages), 0)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 0)
+
+    def test_expire_after_time(self):
+        storage = self.get_storage()
+        response = self.get_response()
+        res = restrictions
+        res.time_provider.set_act_time(0)
+
+        storage.add(constants.WARNING, 'Third message, should be shown four times', restrictions = [TimeRestriction(4),])
+        storage.add(constants.ERROR, "Four'th message, should be shown five times", restrictions = [TimeRestriction(6),])
+
+        for i in range(10):
+            messages = self.__read_messages(storage, response)
+            self.assertEqual(len(messages), 2)
+            storing = self.stored_messages_count(storage, response)
+            self.assertEqual(storing, 2)
+
+        res.time_provider.set_act_time(5)
+        messages = self.__read_messages(storage, response)
+        self.assertEqual(len(messages), 1)
+        self.assertEqual(messages[0].level, constants.ERROR)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 1)
+
+        res.time_provider.set_act_time(7)
+        messages = self.__read_messages(storage, response)
+        self.assertEqual(len(messages), 0)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 0)
+
+    def test_expire_fixed_restrictions(self):
+        """
+        Check message expirity when defining few sort of restrictions
+        of different kind
+        """
+        get_storage_and_response = lambda: (self.get_storage(), self.get_response())
+        storage, response = get_storage_and_response()
+        get_res = lambda ar1, ar2, tr1, tr2: [AmountRestriction(ar1), AmountRestriction(ar2), TimeRestriction(tr1), TimeRestriction(tr2)]
+
+
+        def check_storage(storage, iterations, expected_amount, fnct = None):
+            """
+            Checks whether given storage would contain expected_amount of messages before each of iterations iterations
+            Performs iterations amount of iterations
+            Calls fnct at the end of each iteration
+            """
+            for i in range(iterations): #read 2 times
+                messages = self.__read_messages(storage, response)
+                self.assertEqual(len(messages), expected_amount)
+                storing = self.stored_messages_count(storage, response)
+                self.assertEqual(storing, expected_amount)
+                if fnct: fnct()
+
+        # first - expires after amount of showing
+        restrictions.time_provider.set_act_time(0)
+        storage.add(constants.ERROR, "Some message", restrictions = get_res(7, 30, 5, 20))
+        storage.add(constants.INFO, "another message", restrictions = get_res(3, 10, 4, 11))
+        check_storage(storage, 2, 2)
+        messages = self.__read_messages(storage, response) # 3 times read
+        self.assertEqual(len(messages), 2)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 1)
+        check_storage(storage, 3, 1)
+
+
+        storage, response = get_storage_and_response()
+        restrictions.time_provider.set_act_time(0)
+        storage.add(constants.ERROR, "Some message", restrictions = get_res(7, 30, 5, 20))
+        storage.add(constants.INFO, "another message", restrictions = get_res(3, 10, 4, 11))
+        restrictions.time_provider.set_act_time(2)
+        check_storage(storage, 2, 2, lambda:restrictions.time_provider.set_act_time(3))
+
+        # second - expires after amount of time
+        storage, response = get_storage_and_response()
+        restrictions.time_provider.set_act_time(0)
+        storage.add(constants.ERROR, "Some message", restrictions = get_res(10, 30, 5, 20))
+        storage.add(constants.INFO, "another message", restrictions = get_res(10, 20, 3, 11))
+        check_storage(storage, 2, 2)
+        restrictions.time_provider.set_act_time(2)
+        check_storage(storage, 2, 2)
+        restrictions.time_provider.set_act_time(4)
+        check_storage(storage, 2, 1)
+
+    def transport_storage(self, response, request):
+        """
+        Transports storage from response to request
+        This method can be overriden by subclasses
+        """
+
+    def __read_messages(self, storage, response):#simulate next call to render messages
+        """
+        Simulates iterating storage in template
+        Returns messages from that iteration that would be returned
+        """
+        messages = self.read_storage(storage)
+        storage.update(response)
+        self.transport_storage (response, storage.request)
+        storage.used = False
+        storage._queued_messages = []
+        del storage._loaded_data
+        return messages
Index: django/contrib/messages/tests/__init__.py
===================================================================
--- django/contrib/messages/tests/__init__.py	(revision 16578)
+++ django/contrib/messages/tests/__init__.py	(working copy)
@@ -2,3 +2,5 @@
 from django.contrib.messages.tests.fallback import FallbackTest
 from django.contrib.messages.tests.middleware import MiddlewareTest
 from django.contrib.messages.tests.session import SessionTest
+from django.contrib.messages.tests.restrictions import RestrictionsTest
+from django.contrib.messages.tests.message import MessageTest
Index: django/contrib/messages/tests/fallback.py
===================================================================
--- django/contrib/messages/tests/fallback.py	(revision 16578)
+++ django/contrib/messages/tests/fallback.py	(working copy)
@@ -1,4 +1,6 @@
 from django.contrib.messages import constants
+from django.contrib.messages.storage.base import Message
+from django.contrib.messages.restrictions import AmountRestriction
 from django.contrib.messages.storage.fallback import (FallbackStorage,
     CookieStorage)
 from django.contrib.messages.tests.base import BaseTest
@@ -44,7 +46,8 @@
         cookie_storage = self.get_cookie_storage(storage)
 
         # Set initial cookie data.
-        example_messages = [str(i) for i in range(5)]
+        example_messages = [Message(constants.INFO, str(i)) for i in range(5)]
+        expected_messages = [Message(constants.INFO, str(i), restrictions = [AmountRestriction(0),]) for i in range(5)]
         set_cookie_data(cookie_storage, example_messages)
 
         # Overwrite the _get method of the fallback storage to prove it is not
@@ -52,7 +55,7 @@
         self.get_session_storage(storage)._get = None
 
         # Test that the message actually contains what we expect.
-        self.assertEqual(list(storage), example_messages)
+        self.assertEqual(list(storage), expected_messages)
 
     def test_get_empty(self):
         request = self.get_request()
@@ -72,13 +75,14 @@
         session_storage = self.get_session_storage(storage)
 
         # Set initial cookie and session data.
-        example_messages = [str(i) for i in range(5)]
+        example_messages = [Message(constants.INFO, str(i)) for i in range(5)]
+        expected_messages = [Message(constants.INFO, str(i), restrictions = [AmountRestriction(0),]) for i in range(5)]
         set_cookie_data(cookie_storage, example_messages[:4] +
                         [CookieStorage.not_finished])
         set_session_data(session_storage, example_messages[4:])
 
         # Test that the message actually contains what we expect.
-        self.assertEqual(list(storage), example_messages)
+        self.assertEqual(list(storage), expected_messages)
 
     def test_get_fallback_only(self):
         request = self.get_request()
@@ -87,7 +91,7 @@
         session_storage = self.get_session_storage(storage)
 
         # Set initial cookie and session data.
-        example_messages = [str(i) for i in range(5)]
+        example_messages = [Message(constants.INFO, str(i)) for i in range(5)]
         set_cookie_data(cookie_storage, [CookieStorage.not_finished],
                         encode_empty=True)
         set_session_data(session_storage, example_messages)
@@ -102,8 +106,8 @@
         session_storage = self.get_session_storage(storage)
 
         # Set initial cookie and session data.
-        set_cookie_data(cookie_storage, ['cookie', CookieStorage.not_finished])
-        set_session_data(session_storage, ['session'])
+        set_cookie_data(cookie_storage, [Message(constants.INFO, 'cookie'), CookieStorage.not_finished])
+        set_session_data(session_storage, [Message(constants.INFO, 'session')])
 
         # When updating, previously used but no longer needed backends are
         # flushed.
@@ -173,3 +177,6 @@
         self.assertEqual(cookie_storing, 0)
         session_storing = self.stored_session_messages_count(storage, response)
         self.assertEqual(session_storing, 1)
+
+    def transport_storage(self, response, request):
+        request.COOKIES['messages'] = response.cookies['messages'].value
Index: django/contrib/messages/tests/session.py
===================================================================
--- django/contrib/messages/tests/session.py	(revision 16578)
+++ django/contrib/messages/tests/session.py	(working copy)
@@ -1,5 +1,6 @@
 from django.contrib.messages.tests.base import BaseTest
 from django.contrib.messages.storage.session import SessionStorage
+from django.contrib.messages.storage.base import Message
 
 
 def set_session_data(storage, messages):
@@ -32,7 +33,7 @@
     def test_get(self):
         storage = self.storage_class(self.get_request())
         # Set initial data.
-        example_messages = ['test', 'me']
+        example_messages = [Message(1, 'test') , Message(1, 'me')]
         set_session_data(storage, example_messages)
         # Test that the message actually contains what we expect.
         self.assertEqual(list(storage), example_messages)
Index: django/contrib/messages/tests/message.py
===================================================================
--- django/contrib/messages/tests/message.py	(revision 0)
+++ django/contrib/messages/tests/message.py	(revision 0)
@@ -0,0 +1,67 @@
+from django.test import TestCase
+from django.contrib.messages.storage.base import Message
+from django.contrib.messages import restrictions
+from django.contrib.messages.restrictions import AmountRestriction, TimeRestriction, time_provider
+from django.contrib.messages.tests.time_provider import TestTimeProvider
+from django.contrib.messages import constants
+
+
+class MessageTest(TestCase):
+    def setUp(self):
+        self.tp = restrictions.time_provider = TestTimeProvider ()
+    def __check_active(self, msg, iterations):
+        """
+        Reads msg given amount of iterations, and after each read
+        checks whether before each read message is active
+        """
+        for i in range(iterations):
+            self.assertTrue(msg.active())
+            msg.on_display()
+        self.assertFalse(msg.active())
+        msg.on_display()
+        self.assertFalse(msg.active())
+
+    def test_active_default(self):
+        msg = Message(constants.INFO, "Test message")
+        self.__check_active(msg, 1)
+
+    def test_active_custom_one_amount_restriction(self):
+        msg = Message(constants.INFO, "Test message", restrictions = [AmountRestriction(3),])
+        self.__check_active(msg, 3)
+
+    def test_active_custom_few_amount_restriction(self):
+        msg = Message(constants.INFO, "Test message", restrictions = [AmountRestriction(x) for x in (2, 3, 5)])
+        self.__check_active(msg, 2)
+
+    def test_active_custom_one_time_restriction(self):
+        msg = Message(constants.INFO, "Test message", restrictions = [TimeRestriction(3),])
+        def check_iter():
+            for i in range(10): # iteration doesn't have direct impact for TimeRestriction
+                self.assertTrue(msg.active())
+                msg.on_display()
+        check_iter()
+        self.tp.set_act_time(3)
+        check_iter()
+        self.tp.set_act_time(4)
+        self.assertFalse(msg.active())
+
+
+    def test_mixed_restrictions(self):
+        get_restrictions = lambda:[TimeRestriction(3), TimeRestriction(5), AmountRestriction(2), AmountRestriction(3)]
+        get_msg = lambda:Message(constants.INFO, "Test message", restrictions = get_restrictions())
+
+        msg = get_msg()
+        for i in range(2):
+            self.assertTrue(msg.active())
+            msg.on_display()
+        self.assertFalse(msg.active())
+
+        msg = get_msg()
+        self.assertTrue(msg.active())
+        msg.on_display()
+        self.assertTrue(msg.active())
+        self.tp.set_act_time(4)
+        self.assertFalse(msg.active())
+        for i in range(10):
+            self.assertFalse(msg.active())
+            msg.on_display()
Index: django/contrib/messages/storage/cookie.py
===================================================================
--- django/contrib/messages/storage/cookie.py	(revision 16578)
+++ django/contrib/messages/storage/cookie.py	(working copy)
@@ -1,8 +1,9 @@
 from django.conf import settings
-from django.contrib.messages.storage.base import BaseStorage, Message
+from django.contrib.messages.storage.base import BaseStorage, Message, RestrictionsContainer
 from django.http import SimpleCookie
 from django.utils import simplejson as json
 from django.utils.crypto import salted_hmac, constant_time_compare
+from django.contrib.messages.restrictions import AmountRestriction, TimeRestriction
 
 
 class MessageEncoder(json.JSONEncoder):
@@ -11,11 +12,13 @@
     """
     message_key = '__json_message'
 
+
     def default(self, obj):
         if isinstance(obj, Message):
             message = [self.message_key, obj.level, obj.message]
             if obj.extra_tags:
                 message.append(obj.extra_tags)
+            message.append (obj.restrictions.to_json_obj())
             return message
         return super(MessageEncoder, self).default(obj)
 
@@ -25,10 +28,18 @@
     Decodes JSON that includes serialized ``Message`` instances.
     """
 
+
+    def create_message(self, vars):
+        ''' creates message on the basis of encoded data '''
+        args = vars[:-1]
+        restrictions = vars[-1]
+        restrictions = RestrictionsContainer.create_from_josn(restrictions)
+        return Message(*args, **{'restrictions': restrictions})
+
     def process_messages(self, obj):
         if isinstance(obj, list) and obj:
             if obj[0] == MessageEncoder.message_key:
-                return Message(*obj[1:])
+                return self.create_message (obj[1:])
             return [self.process_messages(item) for item in obj]
         if isinstance(obj, dict):
             return dict([(key, self.process_messages(value))
Index: django/contrib/messages/storage/base.py
===================================================================
--- django/contrib/messages/storage/base.py	(revision 16578)
+++ django/contrib/messages/storage/base.py	(working copy)
@@ -1,11 +1,30 @@
 from django.conf import settings
 from django.utils.encoding import force_unicode, StrAndUnicode
-from django.contrib.messages import constants, utils
+from django.contrib.messages import constants, utils, restrictions as res
 
 
 LEVEL_TAGS = utils.get_level_tags()
 
 
+class RestrictionsContainer(list):
+    res_map = {res.AmountRestriction.JSON_TYPE_CODE: res.AmountRestriction,
+                res.TimeRestriction.JSON_TYPE_CODE: res.TimeRestriction}
+
+    def to_json_obj(self):
+        return [r.to_json() for r in self]
+
+    @classmethod
+    def create_from_josn(cls, enc_restrictions):
+        #set_trace()
+        ret = []
+        for r in enc_restrictions:
+            restriction_type, values = r.split(res.Restriction.JSON_SEPARATOR)
+            ret.append(cls.res_map[restriction_type].from_json_param(values))
+        return RestrictionsContainer(ret)
+
+    def __eq__(self, other):
+        return set(self) == set(other)
+
 class Message(StrAndUnicode):
     """
     Represents an actual message that can be stored in any of the supported
@@ -13,12 +32,16 @@
     or template.
     """
 
-    def __init__(self, level, message, extra_tags=None):
+    def __init__(self, level, message, extra_tags=None, restrictions = []):
         self.level = int(level)
         self.message = message
         self.extra_tags = extra_tags
 
-    def _prepare(self):
+        self.restrictions = restrictions or list([res.AmountRestriction(1)])
+        self.restrictions = RestrictionsContainer(self.restrictions)
+        # if not given any restriction - one show by default
+
+    def _prepare(self): # todo: slef.restrictions =
         """
         Prepares the message for serialization by forcing the ``message``
         and ``extra_tags`` to unicode in case they are lazy translations.
@@ -31,11 +54,13 @@
 
     def __eq__(self, other):
         return isinstance(other, Message) and self.level == other.level and \
-                                              self.message == other.message
+                                              self.message == other.message and \
+                                              self.restrictions == other.restrictions
 
     def __unicode__(self):
         return force_unicode(self.message)
 
+
     def _get_tags(self):
         label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''),
                                   strings_only=True)
@@ -50,6 +75,17 @@
     tags = property(_get_tags)
 
 
+    def active (self):
+        for r in self.restrictions:
+            if r.is_expired(): return False
+        return True
+
+
+    def on_display (self):
+        for r in self.restrictions:
+            r.on_display ()
+
+
 class BaseStorage(object):
     """
     This is the base backend for temporary message storage.
@@ -60,21 +96,33 @@
 
     def __init__(self, request, *args, **kwargs):
         self.request = request
+        self.used = False
         self._queued_messages = []
-        self.used = False
         self.added_new = False
         super(BaseStorage, self).__init__(*args, **kwargs)
 
     def __len__(self):
-        return len(self._loaded_messages) + len(self._queued_messages)
+        # in case that there was a call for render template which would cause iterating throught messages,
+        # and then (e.g. in some middleware, would be call for iterating through messages (e.g. by iterating of context['messages'])
+        # TODO: implement a way to access messages without affecting callign __iter__ method
+        all_msgs = set(self._loaded_messages + self._queued_messages)
+        return len(all_msgs)
+        #return len(self._loaded_messages) + len(self._queued_messages)
 
     def __iter__(self):
-        self.used = True
-        if self._queued_messages:
-            self._loaded_messages.extend(self._queued_messages)
-            self._queued_messages = []
-        return iter(self._loaded_messages)
+        if not self.used:
+            self.used = True
+            if self._queued_messages:
+                self._loaded_messages.extend(self._queued_messages)
+                self._queued_messages = []
 
+            active_messages = [x for x in self._loaded_messages if x.active ()]
+            for x in active_messages:
+                x.on_display ()
+            self._queued_messages.extend (m for m in active_messages if m not in self._queued_messages)
+        return iter(self._queued_messages)
+
+
     def __contains__(self, item):
         return item in self._loaded_messages or item in self._queued_messages
 
@@ -104,6 +152,12 @@
         """
         raise NotImplementedError()
 
+    def filter_store(self, messages, response, *args, **kwargs):
+        ''' stores only active messages from given messages in storage '''
+        filtered_messages = [x for x in messages if x.active ()]
+        return self._store(filtered_messages, response, *args, **kwargs)
+
+
     def _store(self, messages, response, *args, **kwargs):
         """
         Stores a list of messages, returning a list of any messages which could
@@ -130,14 +184,16 @@
         be stored again. Otherwise, only messages added after the last
         iteration will be stored.
         """
+        # if used or used and added, then _queued_messages contains all messages that should be saved
+        # if added, then save: all messages currently stored and added ones
         self._prepare_messages(self._queued_messages)
         if self.used:
-            return self._store(self._queued_messages, response)
+            return self.filter_store(self._queued_messages, response)
         elif self.added_new:
             messages = self._loaded_messages + self._queued_messages
-            return self._store(messages, response)
+            return self.filter_store(messages, response)
 
-    def add(self, level, message, extra_tags=''):
+    def add(self, level, message, extra_tags='', restrictions = []):
         """
         Queues a message to be stored.
 
@@ -152,7 +208,7 @@
             return
         # Add the message.
         self.added_new = True
-        message = Message(level, message, extra_tags=extra_tags)
+        message = Message(level, message, extra_tags=extra_tags, restrictions = restrictions)
         self._queued_messages.append(message)
 
     def _get_level(self):
