turbogears

Caching in TurboGears 2

Thursday, August 20th, 2009 | Web | Comments

TurboGears 2 has a quite good and complete caching support inherited from Pylons, as it is a pylons feature it is not really available by itself, but you can import it.

All you need is those three little things:

from pylons.decorators.cache import beaker_cache from pylons.controllers.util import etag_cache from pylons import cache

The first imports a decorator which makes possible to cache entire controller methods, the second imports a function to use client side caching and the third makes available a caching repository where to store whatever data you might want.

The easiest caching mechanism is etag_cache, this tells to the browser to use its own cached version of the page if it has any available instead of requesting it again to the server. etag_cache requires only one parameter: the caching key. By placing as the first instruction inside your controller action etag_cache(’mykey’) you will tell to the browser to use its own cached version if it has any. You can use the same key inside each action as the browser will check both for key and url, so different urls won’t collide. Keep in mind that this will let the browser keep using the cached version of the page until the browser won’t be restarted and this is usually something that you don’t want. To avoid this behaviour I suggest to keep changing the key argument constantly each time you want the cache to decay, using a timestamp as key might be a good idea.

For example you can add to your lib.base.BaseController.__call__ method something like

app_globals = tg.config['pylons.app_globals'] if app_globals.caching_key+datetime.timedelta(0,10) < datetime.datetime.now(): app_globals.caching_key = datetime.datetime.now() self.caching_key = str(app_globals.caching_key)

and then use etag_cache(self.caching_key) inside the controller action, this will let your cache expire every 10 seconds.

This might be enough in some situations where you want to completely cache your page, but often you might want to cache only your controller and render your view again. This can be achieved by using the @beaker_cache decorator. This will use Beaker to perform caching of the values returned by your controller, if it finds any available data for your controller it will return it without calling the controller method.

@expose()
@beaker_cache(expire=10)
def index(self):
    #Long and slow operation here
    return 'OK'

This way you will keep your action cached for 10 seconds and will cache different versions if the action parameters change.

For more complex things you might want to cache only parts of an action, this can be achieved by directly using the cache object.

c = cache.get_cache(’my_function’)
result = c.get_value(key=function_args, createfunc=slow_function, type=”memory”, expiretime=10)

This will get the caching namespace for the current function and will retrieve the available value with the given key if available (you might see that I have called the key “function_args”, this is because it is usually a good idea to build the key by using function arguments that have any effect on the result). If it isn’t found any value (or the value has expired) slow_function will be called to calculate the new value.

New versions of beaker have a nice decorator @cache.cache which prevent you from having to get the cache namespace and the cache value by yourself, by applying @cache.cache to slow_function each call to slow_function will return the available cached value by itself. More information can be found on the relative beaker documentation section. Keep in mind that @cache.cache decorator can only be used by passing arguments as a list, it won’t work for keyword arguments.

Tags: , ,

Personalize your Error pages in Turbogears 2

Friday, August 7th, 2009 | Web | Comments

I was looking for a way to propagate exceptions from my tg2 app to the ErrorController to permit to propagate errors from controllers to the user. To generate errors and show them you usually have to redirect to /error/document and pass as parameters the error message and error code, but this isn’t really flexible and also modern languages have a really good feature to propagate errors: Exceptions.

So I was looking for a way to raise a webob.exc.HTTPForbidden, place a message inside it and let the ErrorController render the message. Usually you don’t want to tell to the user what went wrong with your 500 server side exception, but you might want to tell to the user why he can’t do what he is trying to do, let him know why it is Forbidden to him.

First thing you can easily do is check for resp.status_int inside your ErrorController.document and fetch only 403 error (the forbidden one). This permits to create a specific message for Forbidden errors, but doesn’t tell much to the user about why it is forbidden. webob.exc.HTTPForbidden permits to set a detail message and also generates an error page, but the turbogears stack when gets a status code different from 200 hooks the response and calls the ErrorController.document to generate a new response. This way your HTTPForbidden exception is lost forever.

Actually it isn’t really lost, as you can access the previous error page from request.environ.get(‘pylons.original_response’). If you  want a quick solution you can hook ErrorController if status_int is 403 and return the original_response instead of rendering ErrorController.document template.

But the original response isn’t really nice and you usually want to adapt it.

My solution has been to create a new ApplicationError(webob.exc.Forbidden) class defined as the following one:

from webob.exc import HTTPForbidden

try:
  from string import Template
except ImportError:
  from webob.util.stringtemplate import Template

class HTTPMyAppError(HTTPForbidden):
  body_template_obj = Template('''<div>${detail}</div>''')

  def __init__(self, msg):
    super(HTTPMyAppError, self).__init__(msg)

This by itself doesn’t change a lot as you will get the same ugly page with simply a div around your error. But now by using BeautifoulSoup you are able to get only your message from the original_response inside your ErrorController.document action.

if resp.status_int == 403: title = "Application Error" message = "We can't perform this, reason was:" details = str(BeautifulSoup(resp.body).find('div'))

Simply personalize your ErrorController.document template and display your details somewhere and you will be able to report errors to your users by simply doing something like raise HTTPMyAppError(’You have already used this registration code’)

I know that this isn’t a really great solution as you have to parse your already generated error page to fetch only the error message and generate a new error page, if anyone has a better solution that permits to directly access the exception instance feel free to tell me!

Tags: ,

ACR got Google Maps view support

Wednesday, July 15th, 2009 | Opensource, Web | Comments

We have recently put inside the ACR svn the support for the GMap view, this means that now you will be able to display both static and dynamic google maps by using ACR.

Using MapView is as simple as specifying the location to display and set map as the view of the slice.

Tags: , ,

ACR Slice Preview support, remote disk, Comment and File views.

Monday, June 29th, 2009 | Opensource, Web | Comments

Latest version of ACR, our opensource cms for turbogears, got some new interesting features:

Now each view can have a “preview mode” which shows a minimized version of the slice to which is binded. For example you can show in your home page a short version of a news linking to the complete one, or you can show the thumbnail of an image and link to the full version. This can be quite useful in some situations and can be triggered by setting preview=1 inside a slice group. Each slice inside the group will render in preview mode.

Also the Remote Disk feature has been implemented and can be accessed as /rdisk. Inside this file manager you will be able to upload any file which can be referred and used inside you web page. The File View itself has been implemented to permit to quickly link to files and serve them, it will show images or videos or it will provide a link to download files of unknown types.

Also cloned from our iJamix project now ACR has a Comment view, which permits to insert a comments thread inside any page.

Tags: , ,

ACR TurboGears2 CMS

Thursday, June 4th, 2009 | Web | Comments

We recently developed a CMF as a base for some sites for some of our client, facing the fact that actually there are really few CMS and CMF developed in turbogears we decided to release it opensource so that it can be useful to other people.

ACR (Advanced Content Repository) it is more a developer oriented CMS than an end-user oriented one, we actually used it already as the base for 3 of our sites currently under development and we found the content-slice-view separation quite powerful, but also quite hard for our users to face with. This can be worked around by implementing alternative administration tools specific for the site domain, but ACR also has a few quick tools like HTML Node editor and HTML content editor built inside which might suffice in most cases.

ACR is free software under GPL license and If you want to give it a try you can find it as usual on labs at http://labs.axant.it/acr

Tags: ,

Using Elixir with TG2

Monday, May 4th, 2009 | Software Development, Web | Comments

I had to spend some time to permit to a project of ours to use Elixir inside TG2. Maybe someone with more experience than me might have a better answer, but I have been able to make Elixir work this way:

First of all I had to make Elixir use my TG2 metadata and session by adding to each model file that has a class inheriting from elixir.Entity this line:

from project_name.model import metadata as __metadata__, DBSession as __session__

Then I had to switch to model __init__.py and add elixir.setup_all() to init_model function just after DBSession.configure. This is really important as makes Elixir create all the SQLAlchemy tables and without this you won’t see anything happen for your elixir based models.

Also we can now import inside your model scope every elixir.Entity inherited class like we usually do for DeclarativeBase children.

Tags: , , ,

Search