Slavus programing blog

My random programing (and other) tips.

Django FormMediaContext

| Comments

Django has great abstraction for forms. Feature that I find very useful in forms, is media meta class.

For example:

1
2
3
4
5
6
class SimpleForm(forms.Form):
    class Media:
        css = {
            'all': ('pretty.css',)
        }
        js = ('animations.js', 'actions.js')

Than you can do get javascripts and CSS, rendered simple with something like this:

1
2
3
4
5
>>> form = SimpleForm()
>>> print form.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>

Ok, that is really nice, but when you put your form in template than you get this html fragment in the middle of you html document, and that is not a good practice. A good recommendation is to put all your CSS in head of your document (for example in HEAD), and all your javascripts on end of your html.

So what do we want? We want to all our javascripts and CSS in a context variable so we can write a our base template like:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  {\% for css in forms_css \%}  {\% endfor \%}
</head>
<body>
{ %block body\%} {\%endblock\%}
{\% for js in forms_js %}  {\% endfor \%}
</body>
</html>

With this template we get all our javascripts and CSS injected in html. Nice. But I don’t want to manually put my media files in context every time I want to render template, so I wrote a simple extension of Context (and RequestContext) class, that looks in a context dictionary and looks for any forms that are in it. And adds all javacripts and CSS to context variables forms_js (for javascript), and forms_css(for css).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class FormMediaContext(Context):
    '''
     Context that automaticly for all Forms in context adds
     media to context
    '''
    def __init__(self, dict=None, processors=None):
        Context.__init__(self, dict)

        contexts_values = []
        map(lambda d:contexts_values.extend(d.values()), self.dicts)
        forms_in_context = [ form for form in contexts_values
                             if isinstance(form, forms.Form)]
        forms_js =  []
        forms_css = []
        for form in forms_in_context:
            forms_js += [mark_safe(js) for js in form.media.render_js()]
            forms_css += [mark_safe(css) for css in form.media.render_css()]

        self.update({ 'forms_js' : list(set(forms_js)),
                       'forms_css': list(set(forms_css)) })

Or if you like RequestContext more (like I do), than you can use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class FormMediaRequestContext(RequestContext):
    '''
     Context that automaticly for all Forms in context adds
     media to context
    '''
    def __init__(self, request, dict=None, processors=None):
        RequestContext.__init__(self, request, dict, processors)

        contexts_values = []
        map(lambda d:contexts_values.extend(d.values()), self.dicts)
        forms_in_context = [ form for form in contexts_values
                             if isinstance(form, forms.Form)]
        forms_js =  []
        forms_css = []
        for form in forms_in_context:
            forms_js += [mark_safe(js) for js in form.media.render_js()]
            forms_css += [mark_safe(css) for css in form.media.render_css()]

        self.update( { 'forms_js' : list(set(forms_js)),
                       'forms_css': list(set(forms_css)) })

Now all that you need to do is to render your template with FormMediaContext(or FormMediaRequestContext), and have now worries about your media files, if you have multiple entries of same file it will be rendered only once.

Comments