File: reporting.rst

package info (click to toggle)
camelot 10.07.02-c2-4
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 35,864 kB
  • ctags: 28,700
  • sloc: python: 14,067; makefile: 125; sh: 32
file content (208 lines) | stat: -rw-r--r-- 8,181 bytes parent folder | download
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
.. _tutorial-reporting:

###############################
 Creating a Report with Camelot
###############################

:Release: |version|
:Date: |today|

With the Movie Database Application as our starting point, we're going to use the reporting framework in this tutorial.
We will create a report of each movie, which we can access from the movie detail page.

Massaging the model
===================

First of all we need to create a button to access our report. This is easily done by specifying a form_action, right in the Admin subclass of the model. Our appended code will be::
 	
	form_actions = [MovieSummary('Summary')] 

The action is described in the MovieSummary class, which we'll discuss next. Note that it needs to imported, obviously::

	from movie_summary import MovieSummary

So the movie model admin will look like this::

	class Admin(EntityAdmin):
		from movie_summary import MovieSummary
		verbose_name = 'Movie'
		list_display = [
			'title',
			'short_description',
			'release_date',
			'genre',
			'director'
		]
		form_display = [
			'title',
			'cover_image',
			'short_description',
			'release_date',
			'genre',
			'director'
		]
		form_actions = [
			MovieSummary('Summary')
		]


The Summary class
=================
In the MovieSummary class, which is a child class of camelot.admin.form_action.PrintHtmlFormAction, we need to override just one method;
the html method, which takes the Movie object as its argument. This makes accessing its members very easy as we'll see in a minute.
The html method will return ..., have a guess.... Exactly, html. This will be displayed in a print preview::

	from camelot.admin.form_action import PrintHtmlFormAction
	class MovieSummary(PrintHtmlFormAction):
		def html(self, o):
			return "<h1>This will become the movie report of %s!</h1>" % o.title

You can already test this. You should see a button in the "Actions" section, on the right of the Movie detail page. Click this and a print preview should open with the text you let the html method return.

.. image:: ../_static/action_button.png


.. image:: ../_static/simple_report.png

Now let's make it a bit fancier.

Using Jinja templates
=====================

Install and add Jinja2 to your PYTHONPATH. You can find it here: http://jinja.pocoo.org/2/ or at the cheeseshop http://pypi.python.org/pypi/Jinja2 . Now let's use its awesome powers.

First we'll make a base template. This will determine our look and feel for all the report pages.
This is basically html and css with block definitions. 
Later we'll create the page movie summary template which will contain our model data. The movie summary template will inherit the base template, and provide content for the aforementioned blocks.
The base template could look something like::

	<html>
	<head>
	  <title>{% block page_head_title %}{% endblock %}</title>
	  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	  <style type="text/css">
	body, html {
		font-family: Verdana, Arial, sans-serif;
	}
	{% block styles %}{% endblock %}
	  </style>
	</head>
	<body>

	<table id="page_header" width="100%">
	  <tr>
	    <td><h1>{% block page_header %}{% endblock %}</h1></td>
	    <td align="right">{% block page_header_right %}{% endblock %}</td>
	  </tr>
  
	</table>
	<hr>
	<h2 id="page_title"><center>{% block page_title %}{% endblock %}</center></h2>
	<hr>
	{% block page_content %}{% endblock %}
	<hr>
	<div id="page_footer">{% block page_footer %}{% endblock %}</div>

	</body>
	</html>

We'll save this file as base.html in a directory called templates in our videostore.
Like this base template, the movie summary template is html and css. Take a look at the example first::

	{% extends 'base.html' %}
	{% block styles %}{{ style }}{% endblock %}
	{% block page_head_title %}{{ title }}{% endblock %}
	{% block page_title %}{{ title }}{% endblock %}
	{% block page_header %}{{ header }}{% endblock %}
	{% block page_header_right %}
	{% if header_right %}
		<img src="media/covers/{{ header_right }}" alt="">
	{% else %}
		(no cover) 
	{% endif %}	
	{% endblock %}
	{% block page_content %}{{ content }}{% endblock %}
	{% block page_footer %}{{ footer }}{% endblock %}

First we extend the base template, that way we don't need to worry about the boilerplate stuff, and keep our pages consistent, provided we create more reports of course.
We can now fill in the blanks, erm blocks from the base template. We do that with placeholders which we'll define in the html method of our MovieSummary class. This way we can even add style to the page::

	{% block styles %}{{ style }}{% endblock %}
	
We'll define this later. The templating language also allows basic flow control::

	{% if cover %}
		<img src="media/covers/{{ cover }}" alt="">
	{% else %}
		(no cover) 
	{% endif %}

If there is no cover image, we'll show the string "(no cover)".
We'll save this file as movie_summary.html in the templates directory.

Like i said earlier, we now need to define which values will go in the placeholders, so let's update our html method in the MovieSummary class.
First, we import the needed elements::

	import datetime
	from jinja import Environment, FileSystemLoader
	from pkg_resources import resource_filename
	import videostore

We'll be printing a date, so we'll need datetime. The Jinja classes to make use of our templates. And to locate our templates, we'll use the resource module, with our videostore. And load up the Jinja environment ... ::

	fileloader = FileSystemLoader(resource_filename(videostore.__name__, 'templates'))
	e = Environment(loader=fileloader)

Now we need to create a context dictionary to provide data to the templates. The keys of this dictionary are the placeholders we used in our movie_summary template, the values we can use from the model, which is passed as the o argument in the html method::

	context = {
        'header':o.title,
        'title':'Movie Summary',
        'style':'.label { font-weight:bold; }',
        'content':'<span class="label">Description:</span> %s<br>\
                <span class="label">Release date:</span> %s<br>\
                <span class="label">Genre:</span> %s<br>\
                <span class="label">Director:</span> %s'
                % (o.short_description, o.release_date, o.genre, o.director),
        'cover': os.path.join( resource_filename(videostore.__name__, 'media'), 'covers', o.cover_image.name ),
        'footer':'<br>copyright %s - Camelot' % datetime.datetime.now().year
	}

Plain old Python dictionary. Check it out, we can even pass css in our setup.

Finally, we'll get the template from the Jinja environment and return the rendered result of our context::

	t = e.get_template('movie_summary.html')
	return t.render(context)

So our finished method eventually looks like this::

	from camelot.admin.form_action import PrintHtmlFormAction
	class MovieSummary(PrintHtmlFormAction):
	    def html(self, o):
	        import datetime
	        import os
	        from jinja import Environment, FileSystemLoader
	        from pkg_resources import resource_filename
	        import videostore
	        fileloader = FileSystemLoader(resource_filename(videostore.__name__, 'templates'))
	        e = Environment(loader=fileloader)
	        context = {
	                'header':o.title,
	                'title':'Movie Summary',
	                'style':'.label { font-weight:bold; }',
	                'content':'<span class="label">Description:</span> %s<br>\
	                        <span class="label">Release date:</span> %s<br>\
	                        <span class="label">Genre:</span> %s<br>\
	                        <span class="label">Director:</span> %s'
	                        % (o.short_description, o.release_date, o.genre, o.director),
	                'cover': os.path.join( resource_filename(videostore.__name__, 'media'), 'covers', o.cover_image.name ),
	                'footer':'<br>copyright %s - Camelot' % datetime.datetime.now().year
	        }
	        t = e.get_template('movie_summary.html')
	        return t.render(context)

What are you waiting for? Go try it out!
You should see something like this:

.. image:: ../_static/final_report.png