My turbogears.flash() alternative

36 views
Skip to first unread message

ans...@gmail.com

unread,
Jun 10, 2007, 3:46:03 AM6/10/07
to TurboGears
Hi everyone,

I've done some more work on my alternative to turbogears.flash(), and
it's pretty decent as this point (at least I think so), and it might
be useful for others.

What it does:
* Multiple messages
* Different classes of message (warning, error, etc.), which can be
styled accordingly.
* HTML in messages (only if explicitly allowed, and not the default)
* Logger-like invocation: f.warning("The sky is falling!")
* Hideable messages (tell a message to go away once and you'll never
see it again)

It works based on sessions, rather than a cookie. To use it in any
controller, you just instantiate a Flash2 object and call one of the
methods to add a message:

f = Flash2()
f.info("Hello, world!")

You can even do it all at once if you're only going to have one
message:
Flash2().info("Hello, world -- in one line!")

Here's a more complete example controller:

@expose(template=".templates.test_flash2")
def test_flash2(self):
f2 = Flash2()
f2.info("This is the first message. It's an info.")
f2.warning("This is the second message. It's a warning.",
hideable=True)
f2.error("This is the third message. It's an error!", growl=True)
f2.info("This is the fourth message. It's an info.",
hideable=True)
f2.warning("This is the fifth message. It has some <strong>HTML</
strong> in it! <a href='http://www.google.com/'>Google</a> rocks.",
html=True)
f2.warning("This is the sixth message. It is NOT an HTML message,
but yet it has some <strong>HTML</strong> in it! <a href='http://
www.google.com/'>Google</a> rocks.")
f2.ok("Super!")
f2.add_message("There's a <strong>bug</strong> on this page.",
'bug', True)
f2.add_message("Heavy construction underway!", 'construction')
f2.add_message("There's a feed here somewhere.", 'feed')
return dict()

You can see the results in a screenshot here: http://www.anseljh.com/code/flash2/flash2.png
(Note that the fourth message doesn't show up; the "Hide" link was
previously clicked.)

Here's how to implement it.

(1) Add the Flash2Message and Flash2 classes to your controllers.py:

class Flash2Message:
def __init__(self, msg, cls='info', html=False, hideable=False):
self.message = msg
self.css = cls
self.html = html
self.hideable = hideable
self.md5 = md5.md5(self.message).hexdigest()

class Flash2:
def __init__(self):
self.messages = []
self.messages_dict = {'info':[], 'warning':[], 'error':[], 'ok':
[]}
self.generator = self._generator() # use f2.generator.next() to
get messages
self.next = self._generator # next() method to make class iterable
def __iter__(self):
return self.generator
def add_message(self, msg, cls, html=False, growl=False,
hideable=False):
m = Flash2Message(msg, cls, html, hideable)
self.messages.append(m)
if cls not in self.messages_dict:
self.messages_dict[cls] = []
self.messages_dict[cls].append(m)
cherrypy.session['flash2'] = self # stick the generator in the TG/
CP session; use it in master template
def _generator(self):
"""
Build a generator for getting messages from a Flash2 instance
"""
while(1):
try:
m = self.messages.pop(0) # pop the first Flash2Message in the
list
yield m
except IndexError:
raise StopIteration
def info(self, msg, html=False, hideable=False, growl=False):
self.add_message(msg, 'info', html, growl, hideable)
def warning(self, msg, html=False, hideable=False, growl=False):
self.add_message(msg, 'warning', html, growl, hideable)
def error(self, msg, html=False, hideable=False, growl=False):
self.add_message(msg, 'error', html, growl, hideable)
def ok(self, msg, html=False, hideable=False, growl=False):
self.add_message(msg, 'ok', html, growl, hideable)

# ignore all the 'growl' stuff -- that's remnants from debugging

(2) Add this at the top of your master.kid template -- after the
DOCTYPE but before the <html> tag:

<?python
from cherrypy import request as cp_request
from cherrypy import session as cp_session
?>

(3) Add this to your master template wherever you want the flash
messages to show up:

<div py:if="cp_session.has_key('flash2')" id="flash2">
<div py:for="message in cp_session.get('flash2')"
class="message ${message.css}" id="flash2_${message.md5}"
py:if="'flash2_hide_'+message.md5 not in cp_request.simple_cookie">
<div py:if="message.hideable" class="hide">
<a href="#" onclick="javascript:
MochiKit.Visual.fade('flash2_${message.md5}', {'duration':0.25});
document.cookie='flash2_hide_${message.md5}=True';">Hide</a>
</div>
<span py:if="message.html"
py:content="XML(message.message)" />
<span py:if="not message.html"
py:content="message.message" />
</div>
</div>

(4) Put some styles in your master stylesheet. Here's what I have:

#flash2 .message {
/* base styles for all flash2 messages */
border-width: 3px;
border-style: solid;
margin: .5em;
padding: .5em;
}
#flash2 .error {
/*background: url(/static/images/warning.gif) left center no-
repeat;*/
background-color: #fdd; /* red */
border-color: #f00;
}
#flash2 .warning {
background-color: #ffd; /* yellow? */
border-color: #ff0;
}
#flash2 .info {
padding-left: 50px;
background: url(/static/images/info.png) left center no-repeat;
background-color: #eeeeff;
border-color: #bbd;
}
#flash2 .ok {
padding-left: 50px;
background: url(/static/images/ok.png) left center no-repeat;
background-color: #cceecc;
border-color: #81db28;
}
#flash2 .bug {
padding-left: 50px;
background: url(/static/images/bug001.gif) .2em center no-repeat;
background-color: #fdd; /* red */
border-color: #f00;
}
#flash2 .construction {
padding-left: 50px;
/*background: url(/static/images/construction1.gif) .2em center no-
repeat;*/
background-color: #ffd; /* yellow? */
border-color: #ff0;
}
#flash2 .feed {
padding-left: 50px;
background: url(/static/images/feed-icon-24x24.png) .2em center no-
repeat;
background-color: #ffd; /* yellow? */
border-color: #ff8509;
}
#flash2 .message .hide {
float: right;
font-size: 85%;
}


And that's it.

Comments? Suggestions for improvement?

-Ansel

P.S. The "Hide" link includes a MochiKit 1.4 call
(MochiKit.Visual.fade()), which I don't think will work on a stock TG
1.0.x installation. You can replace that part with some other DOM
scripting to hide the message element, but that's left as an exercise
for the reader.

P.P.S. Because it depends on sessions, you'll have to enable them in
your config files. See: http://docs.turbogears.org/1.0/Sessions

Christoph Zwerschke

unread,
Jun 10, 2007, 5:19:07 AM6/10/07
to turbo...@googlegroups.com
ans...@gmail.com wrote:
> I've done some more work on my alternative to turbogears.flash(), and
> it's pretty decent as this point (at least I think so), and it might
> be useful for others.

You should add this to the recipe section of the wiki. Something like
this should eventually be included in TG.

Christopher Arndt

unread,
Jun 10, 2007, 6:27:45 AM6/10/07
to turbo...@googlegroups.com
Christoph Zwerschke schrieb:

Or even better, turn it into an installable package and add it to the
cheeseshop/cogbin!

You could also turn this into a widget that adds itself to
turboears.view.variable_providers in its initialization method. Then you
can put the template and JS/CSS stuff in it as well.

Chris

Marco Mariani

unread,
Jun 11, 2007, 6:17:32 AM6/11/07
to turbo...@googlegroups.com
ans...@gmail.com ha scritto:

> Hi everyone,
>
> I've done some more work on my alternative to turbogears.flash(), and
> it's pretty decent as this point (at least I think so), and it might
> be useful for others.
>

Thank you, it's just like what I needed, the way I would have done it :-)

I suggest you to bind a Flash() instance to the current request, and
wrap inner methods:


> def get_flash_service():
> try:
> ret = cherrypy.request.flash_service
> except AttributeError:
> ret = cherrypy.request.flash_service = Flash()
> return ret
>
>
> def info(*args, **kw):
> get_flash_service().info(*args, **kw)
>
> def warning(*args, **kw):
> get_flash_service().warning(*args, **kw)
>
> def error(*args, **kw):
> get_flash_service().error(*args, **kw)
>
> def ok(*args, **kw):
> get_flash_service().ok(*args, **kw)
>
> def add_message(*args, **kw):
> get_flash_service().add_message(*args, **kw)


This way, you can just use

import flash

flash.warning('whatever')

ans...@gmail.com

unread,
Jun 12, 2007, 10:34:27 AM6/12/07
to TurboGears
That's an interesting suggestion. I'm not a CherryPy expert, so how
do I bind something to a request like that?


On Jun 11, 3:17 am, Marco Mariani <marco.mari...@prometeia.it> wrote:
> anse...@gmail.com ha scritto:

Marco Mariani

unread,
Jun 12, 2007, 11:10:27 AM6/12/07
to turbo...@googlegroups.com
ans...@gmail.com ha scritto:

> That's an interesting suggestion. I'm not a CherryPy expert, so how
> do I bind something to a request like that?
>

This way:

>>> def get_flash_service():
>>> try:
>>> ret = cherrypy.request.flash_service
>>> except AttributeError:
>>> ret = cherrypy.request.flash_service = Flash()
>>> return ret
>>>
>>> def info(*args, **kw):
>>> get_flash_service().info(*args, **kw)
>>>

I use this method to attach any kind of object to the request (even
short-lived db or ldap connections)

I've already switched to your class and it's cute :)


ans...@gmail.com

unread,
Jun 13, 2007, 4:39:04 AM6/13/07
to TurboGears
Thanks, Marco; glad you like it. I think it would be a good idea to
package this up a little better for distribution, and then, in theory,
people could do:

import flash2
flash2.info('Yadda yadda')

...and it would just work by binding to the request like you suggest.

Ansel


On Jun 12, 8:10 am, Marco Mariani <marco.mari...@prometeia.it> wrote:
> anse...@gmail.com ha scritto:

Reply all
Reply to author
Forward
0 new messages