Login

Extended rails flash implementation

Author:
miracle2k
Posted:
July 20, 2007
Language:
Python
Version:
.96
Score:
5 (after 5 ratings)

This is an extendend version of the Rails Flash implementation by Sean Patrick Hogan that supports different message types.

Setting a flash message:

request.flash.error = 'Item could not be saved'

request.flash['error'] = 'Item could not be saved'

request.flash['foo'] = 'bar'

Displaying a flash in the view:

<!-- show the error message -->
{% if flash.error %}An error occured:{{ flash.error }}{% endif %}

<!-- just show the first message found -->
{% if flash %}An error occured:{{ flash }}{% endif %}

<!-- show all messages -->
{% for msg in flash %}{{ msg.type }}: {{ msg.msg }}{% endfor %}

Note that it still works with simple strings as well. Feel free to just use it like this:

request.flash = "Message"

And:

{% if flash %}{{ flash }}{% endif %}

However, be aware that once you did this, you destroyed the Flash() dict and thus lost the extended functionality.

You can use request.flash.clear() to remove all messages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# based on the implementation by Sean Patrick Hogan: http://code.djangoproject.net/ticket/4604 

class Flash(dict):
    # make two commonly used flashes available as attributes
    error = property(lambda s: s.get('error', ""), lambda s, v: s.update({'error': v}))
    notice = property(lambda s: s.get('notice', ""), lambda s, v: s.update({'notice': v}))
    
    # if used as a string, return the first message found
    def __str__(self):        
        if len(self) > 0:
            return self.values()[0]
        return ""
    
    # allow {% for msg in flash %}{{ msg.type }}: {{ msg.msg }}{% endfor %}
    # this may not be necessary in newer versions of django where you should be
    # able to do: {% for key, value in flash %}{{ key }}: {{ value }}{% endfor %}
    def __iter__(self):
        for item in self.keys():
            yield {'type': item, 'msg': self[item]}
    
    # evaluate to True if there is at least one non-empty message
    def __nonzero__(self):
        return len(str(self)) > 0

# Add to MIDDLEWARE_CLASSES
class Middleware:
    def process_response(self, request, response):        
        try:
            # make sure we never override an existing flash with an empty one.
            # otherwise non-pageview requests (like views.static.serve) will
            # override a previously set flash with the empty object created in
            # process_request(). Note that we use len(), so it is still possible
            # to clear a flash by setting the dict-item to ""
            if len(request.flash):
                request.session['flash'] = request.flash            
        except:
            pass
        return response
    
    def process_request(self, request): 
        # Initialize a Flash dict that can be used in views      
        request.flash = Flash()

# Add to TEMPLATE_CONTEXT_PROCESSORS
def context_processor(request):
    if 'flash' in request.session:        
        flash = request.session['flash']
        del request.session['flash']
        return {'flash': flash}
    return {'flash': None}

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 1 week ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 9 months, 4 weeks ago
  5. Help text hyperlinks by sa2812 10 months, 3 weeks ago

Comments

sebastianmacias (on July 23, 2007):

Hello,

I just found an issue. It doesn't work unless I redirect the user after I set the flash message. In other works it seems that the session value that is set is not accessible in the same request. For example: if a user submits a form and I want to display a flash message it doesn't get displayed unless I refresh the browser.

Any thoughts?

Thanks,

Sebastian Macias

#

miracle2k (on July 24, 2007):

Sebastian,

you're right - I never used it that way? How does Rails even work win that regard?

Anyway, I am not 100% sure of the implication or possible side effects of this, but I think it should work just fine. Try this context processor instead:

def context_processor(request):
    # start with flash from current request and reset it (so it won't
    # be stored in the session)    
    flash = request.flash
    request.flash = Flash()
    # add flash from session (e.g. from before a redirect), if any
    if 'flash' in request.session:        
        flash.update(request.session['flash'])
        del request.session['flash']
    # make available in template
    return {'flash': flash}

#

miracle2k (on July 24, 2007):

Hm, that should read "How does Rails work in that regard" - just were are my glasses? ;)

#

sphogan (on October 18, 2007):

Rails works by doing:

flash.now[:notice] = 'Message'

Basically, it (this snippet) doesn't work (for this case) because the response is being processed after the view is. So, you've done your view and Django has the page rendered and it is at that point, right before sending it to the browser, that it calls process_response. So, there is nothing in the session's flash when the context processor is being called unless you've redirected.

But this can be fixed (and rather easily). Since we aren't redirecting in this scenario, we don't even have to bother storing it at all! Everything is resident in memory for the entire time we need it!

So, if we do something like:

request.flash['now'] = 'Heya!'

we have something that differentiates it - a "now" case.

Now, the context processor can say: hi! Is there a flash in this user's session? If so, I want to display it. Well, maybe there's a "now" flash that is in the request object itself that I want to display if there is no session flash.

That would look like this context processor:

if 'flash' in request.session:
    flash = request.session['flash']
    del request.session['flash']
    return {'flash': flash}
elif 'now' in request.flash:
    flash = request.flash['now']
    del request.flash['now']
    return {'flash': flash}
return {'flash': None}

What we've done is look to see if there is a session flash. Ok, there isn't. Then we look to see if there is a "now" flash. Yes! So, just put that in the view and delete it so that the process_response doesn't add it to the session for the next pageview.

Anyway, just ping me if this needs clarification.

#

ChloeO (on November 4, 2007):

Hello, this probably is a dumb question, but where do i put class Flash(dict)? Thanks!

#

miracle2k (on November 10, 2007):

ChloeO, put it in the same file as "class Middleware". As a matter of fact, you can put everything in one file (this is how I do it).

#

romain-hardouin (on June 11, 2008):

thanx for this RoR borrowing, pretty interesting

#

Please login first before commenting.