root/trunk/doc/sphinx/source/tutorial/videostore.rst @ 638

Revision 638, 13.5 KB (checked in by victorn, 13 months ago)

* removes glossary (definitions will point to specific third party docs)

Line 
1.. _tutorial-videostore:
2
3########################################
4 Creating a Movie Database Application
5########################################
6
7:Release: |version|
8:Date: |today|
9
10In this tutorial we will create a fully functional movie database application
11with Camelot. We assume Camelot is properly :ref:`installed <doc-install>`.
12
13Starting a New Project
14======================
15
16We begin with the creation of a new project. Typing the following command in
17your favorite command prompt (or shell) creates one::
18
19  python PTC\camelot\bin\camelot_admin.py startproject videostore
20
21Under linux, you may have to adjust the folder separator. This tutorial has
22been written under the Windows XP operating system. The pictures also reflect
23that operating system.
24
25`PTC` is the path to Camelot main directory. The folder :file:`videostore`
26should appear in your the directory you are working in. We will be working the
27Python modules created and put inside this directory.
28
29Main Window and Views
30=====================
31
32:option:`camelot_admin.py` created some modules for us. Let's focus on the
33one called :file:`main.py` which contains the entry point of your Camelot
34application. If you launch it::
35
36  python videostore\main.py
37
38your `PyQt <http://www.riverbankcomputing.co.uk/software/pyqt/intro>`_
39:abbr:`Graphical User Interface <GUI>` should look like the one we show in the
40picture below:
41
42.. image:: ../_static/picture1.png
43
44The application has menus, a toolbar, a left navigation pane, and a central
45area on which nothing is currently displayed.
46
47The navigation pane has its first button selected. Select any other button by
48clicking on it, and see the nagivation tree fill itself with new entries.
49These are `entities`, and we will talk about them later.  (Generally speaking,
50an `entity` represents a single table in a database.)
51
52.. image:: ../_static/picture2.png
53
54.. note::
55
56   Camelot uses `sections` to group `models`.  Each button in the navigation
57   pane represents a `section`, and each entry of the navigation tree is part
58   of this section.
59
60Notice that the application disables most of the menus and the toolbar
61buttons. When we click on an entity, more options become available.
62So let's click on the entity ``Persons`` of the section ``Relations``.
63
64A child window appears in the previously empty area and is maximized by
65default: this is the table view of the entity.
66
67.. image:: ../_static/picture3.png
68
69Each row is a record with some fields that we can edit (others might not be
70editable). Let's now add a new row by clicking on the new icon (next to the
71trash bin icon; which removes a row).
72
73.. image:: ../_static/picture4.png
74
75We now see a form view with additional fields. Forms are not maximized by
76default. Forms label **required** fields in bold.
77
78.. image:: ../_static/picture5.png
79
80Fill in a first and last name, and close the form. Camelot will automatically
81validate and echo the changes to the database. We can reopen the form by
82clicking on the blue icon in the first column of each row of the table. Notice
83also that there is now an entry in our table.
84
85.. image:: ../_static/picture6.png
86
87That's it for basic usages of the interface. Next we will write code for our
88database model.
89
90
91Creating the Movie Model
92========================
93
94Let's first take a look at the :file:`settings.py` in our project directory.
95There is an attribute, ``ENGINE``, an anonymous function, which returns a
96:abbr:`Uniform Resource Identifier URI`. That's the database your Camelot
97application will be connecting too. Camelot provides a default ``sqlite`` URI
98scheme. But you can set your own.
99
100If you set a database file that does not exist it will be created in the
101directory from which the application is *launched*.
102
103Now we can look at :file:`model.py`. Camelot has already imported some classes
104for us. They are used to create our entities. Let's say we want a movie entity
105with a ``title``, a short ``description``, a ``release date``, and a
106``genre``.
107
108The aforementioned specifications translate into the following Python code,
109that we add to our model.py module::
110
111  class Movie(Entity):
112    using_options(tablename='movie')
113
114    title = Field(Unicode(60), required=True)
115    short_description = Field(Unicode(512))
116    release_date = Field(Date)
117    genre = Field(Unicode(15))
118
119``Movie`` inherits ``Entity`` from the `Elixir <http://elixir.ematia.de/trac/wiki>`_
120library. We use ``using_options()`` to name the table ourselves. Elixir would
121have used the location of our module to generate a name in the form
122*package_model_entity*, as described `in Elixir documentation
123<http://elixir.ematia.de/apidocs/elixir.options.html>`_.
124
125Our entity holds four fields.
126
127::
128
129  title = Field(Unicode(60), required=True)
130
131``title`` holds up to 60 unicode characters, and is required:
132
133::
134
135  short_description = Field(Unicode(512))
136
137``short_description`` can hold up to 512 characters:
138
139::
140
141  release_date = Field(Date)
142  genre = Field(Unicode(15))
143
144``release_date`` holds a date, and ``genre`` up to 15 unicode characters:
145
146For more information about defining fields, refer to
147`this page <http://elixir.ematia.de/apidocs/elixir.fields.html>`_. The
148different `SQLAlchemy <http://www.sqlalchemy.org>`_ types used by Elixir
149are described `here <http://www.sqlalchemy.org/docs/04/types.html>`_.
150Finally, Camelot fields are documented in the API.
151
152Let's now create an ``EntityAdmin`` subclass
153
154
155The EntityAdmin Subclass
156========================
157
158We have to tell Camelot about our entities, so they show up in the :abbr:`GUI`.
159This is one of the purposes of ``EntityAdmin`` subclasses. After adding the
160``EntityAdmin`` subclass, our ``Movie`` class now looks like this::
161
162  class Movie(Entity):
163    using_options(tablename='movie')
164
165    title = Field(Unicode(60), required=True)
166    short_description = Field(Unicode(512))
167    release_date = Field(Date)
168    genre = Field(Unicode(15))
169
170    class Admin(EntityAdmin):
171      verbose_name = 'Movie'
172      list_display = ['title', 'short_description', 'release_date', 'genre']
173
174    def __unicode__(self):
175      return self.title or 'untitled movie'
176
177We made ``Admin`` an inner class to strengthen the link between it and the
178``Entity`` subclass. Camelot does not force us. ``Admin`` holds three
179attributes.
180
181``verbose_name`` will be the label used in navigation trees.
182
183The last attribute is interesting; it holds a list containing the fields we
184have defined above. As the name suggests, ``list_display`` tells Camelot to
185only show the fields specified in the list. ``list_display`` does not affect
186forms.
187
188In our case we want to display four fields: ``title``, ``short_description``,
189``release_date``, and ``genre`` (that is, all of them.)
190
191We also add a ``__unicode__()`` method that will return either the title of the
192movie entity or ``'untitled movie'`` if title is empty. This is a good
193programming practice.
194
195Let's move onto the last piece of the puzzle.
196
197Configuring the Application
198===========================
199
200We are now working with :file:`application_admin.py`.  One of
201the tasks of :file:`application_admin.py` is to specify the sections in
202the left pane of the main window.
203
204Camelot defined a class, ``MyApplicationAdmin``, for us. This class is a
205subclass of ``ApplicationAdmin``, which is used to control the overall look
206and feel of every Camelot application.
207
208To change sections in the left pane of the main window, simply overwrite the
209``get_sections`` method, to return a list of the desired sections.  By default
210this method contains::
211
212  def get_sections(self):
213    from camelot.model.memento import Memento
214    from camelot.model.authentication import Person, Organization
215    from camelot.model.i18n import Translation
216    return [Section('relation',
217                    Icon('tango/24x24/apps/system-users.png'),
218                    items = [Person, Organization]),
219            Section('configuration',
220                    Icon('tango/24x24/categories/preferences-system.png'),
221                    items = [Memento, Translation])
222            ]
223           
224which will display two buttons in the navigation pane, labelled ``'Relations'``
225and ``'Configurations'``, with the specified icon next to each label. And yes,
226the order matters.
227
228We need to add a new section for our ``Movie`` entity, this is done by
229extending the list of sections returned by the ``get_sections`` method with a
230Movie section::
231
232        Section('movies',
233            Icon('tango/24x24/mimetypes/x-office-presentation.png'),
234            items = [Movie])
235
236The constructor of a section object takes the name of the section, the icon to
237be used and the items in the section.  The items is a list of the entities for
238which a table view should shown.
239
240Camelot comes with the `Tango <http://tango.freedesktop.org/Tango_Icon_Library>`_
241icon collection; we use a suitable icon for our movie section.
242
243The resulting method now becomes::
244
245  def get_sections(self):
246    from camelot.model.memento import Memento
247    from camelot.model.authentication import Person, Organization
248    from camelot.model.i18n import Translation   
249    from example.model import Movie
250    return [Section('movies',
251                    Icon('tango/24x24/mimetypes/x-office-presentation.png'),
252                    items = [Movie]),
253            Section('relation',
254                    Icon('tango/24x24/apps/system-users.png'),
255                    items = [Person, Organization]),
256            Section('configuration',
257                    Icon('tango/24x24/categories/preferences-system.png'),
258                    items = [Memento, Translation])
259            ]
260   
261We can now try our application.
262
263We see a new button the navigation pane labelled `'Movies'`. Clicking on it
264fills the navigation tree with the only entity in the movies's section.
265Clicking on this tree entry opens the table view. And if we click on the blue
266folder of each record, a form view appears as shown below.
267
268.. image:: ../_static/picture7.png
269
270That's it for the basics of defining an entity and setting it for display in
271Camelot. Next we look at relationships between entities.
272
273Relationships
274=============
275
276We will be using Elixir's special fields ``ManyToOne`` and ``OneToMany`` to
277specify relationships between entities. But first we need a ``Director``
278entity. We define it as follows::
279
280  class Director(Entity):
281    using_options(tablename='director')
282
283    name = Field(Unicode(60))
284    movies = OneToMany('Movie')
285
286Once again, we name the table ourselves. What's new here is ``OneToMany``.
287
288In Elixir, ``OneToMany`` is a relationship; it takes as parameter the related
289class's name. Behind the scenes, Elixir creates a director id column in the
290table represented by the entity ``Movie`` and set a foreign key constraint on
291this column.
292
293Elixir requires that we add an inverse relationship ``ManyToOne`` to our
294``Movie`` entity. It ends up looking as follows::
295
296  class Movie(Entity):
297    using_options(tablename='movie')
298
299    title = Field(Unicode(60), required=True)
300    short_description = Field(Unicode(512))
301    release_date = Field(Date)
302    genre = Field(Unicode(15))
303    director = ManyToOne('Director')
304
305    class Admin(EntityAdmin):
306      verbose_name = 'Movie'
307      list_display = ['title',
308                      'short_description',
309                      'release_date',
310                      'genre',
311                      'director']
312
313    def __unicode__(self):
314      return self.title or 'untitled movie'
315
316We also inserted ``'director'`` in ``list_display``.
317
318Our ``Director`` entity needs an administration class, which will adds the
319entity to the section ``'movies'``. We will also add ``__unicode__()`` method
320as suggested above. The entity now looks as follows::
321
322  class Director(Entity):
323    using_options(tablename='director')
324
325    name = Field(Unicode(60))
326    movies = OneToMany('Movie')
327
328    class Admin(EntityAdmin):
329      verbose_name = 'Director'
330      list_display = ['name']
331
332    def __unicode__(self):
333      return self.name or 'unknown director'
334
335For completeness the two entities are once again listed below::
336
337  class Movie(Entity):
338    using_options(tablename='movie')
339
340    title = Field(Unicode(60), required=True)
341    short_description = Field(Unicode(512))
342    release_date = Field(Date)
343    genre = Field(Unicode(15))
344    director = ManyToOne('Director')
345
346    class Admin(EntityAdmin):
347      verbose_name = 'Movie'
348      list_display = ['title',
349                      'short_description',
350                      'release_date',
351                      'genre',
352                      'director']
353
354    def __unicode__(self):
355      return self.title or 'untitled movie'
356
357
358  class Director(Entity):
359    using_options(tablename='director')
360
361    name = Field(Unicode(60))
362    movies = OneToMany('Movie')
363
364    class Admin(EntityAdmin):
365      verbose_name = 'Director'
366      list_display = ['name']
367
368    def __unicode__(self):
369      return self.name or 'unknown director'
370
371The last step is to fix :file:`application_admin.py` by adding the following
372lines to the Director entity to the Movie section::
373
374        Section('movies',
375            Icon('tango/24x24/mimetypes/x-office-presentation.png'),
376            items = [Movie, Director])
377
378This takes care of the relationship between our two entities. Below is the new
379look of our video store application.
380
381.. image:: ../_static/picture8.png
382
383We have just learned the basics of Camelot, and have a nice movie database
384application we can play with. In another tutorial, we will learn more advanced
385features of Camelot.
Note: See TracBrowser for help on using the browser.