.. _tutorial-importer:

########################################
 Add an import wizard to an application
########################################

In this tutorial we will add an import wizard to the movie database 
application created in the :ref:`tutorial-videostore` tutorial.

We assume Camelot is properly :ref:`installed <doc-install>` and the movie
database application is working.

.. image:: /_static/controls/main_window.png

Introduction
============

Most applications need a way to import data.  This data is often delivered
in files generated by another application or company.  To demonstrate this
process we will build a wizard that allows the user to import cover images
into the movie database.  For each image the user selects, a new Movie will
be created with the selected image as a cover image.

Create an action
================

All user interaction in Camelot is handled through :ref:`doc-actions`.  For 
actions that run in the context of the application, we use the 
:ref:`doc-application-action`.  We first create a file :file:`importer.py` in 
the same directory as :file:`application_admin.py`.

In this file we create subclass of :class:`camelot.admin.action.Action` which 
will be the entry point of the import wizard::

    from camelot.admin.action import Action
    from camelot.core.utils import ugettext_lazy as _
    
    class ImportCovers( Action ):
        verbose_name = _('Import cover images')
        
        def model_run( self, model_context ):
            yield

So now we haven an ``ImportCovers`` action.  Such an action has a 
``verbose_name`` class attribute with the name of the action as shown to the
user.

The most important method of the action is the ``model_run`` method, which
will be triggered when the user clicks the action.  This method should be a
generator that yields an object whenever user interaction is required.  
Everything that happens inside the ``model_run`` method happens in a different
thread than the GUI thread, so it will not block the GUI.

Add the action to the GUI
=========================

Now the user needs to be able to trigger the action.  We edit the
:file:`application_admin.py` file and make sure the ``ImportCoversAction``
is imported.

.. literalinclude:: ../../../../camelot_example/application_admin.py
   :start-after: begin import action
   :end-before: end import action

Then we add an instance of the ``ImportCovers`` action to the sections 
defined in the ``get_sections`` method of the ``ApplicationAdmin``:

.. literalinclude:: ../../../../camelot_example/application_admin.py
   :start-after: begin section with action
   :end-before: end section with action

This will make sure the action pops up in the **Movies** section of the
application.

.. image:: /_static/controls/navigation_pane.png

Select the files
================

To make the action do something useful, we will implement its ``model_run``
method.  Inside the ``model_run`` method, we can :keyword:`yield` various 
:class:`camelot.admin.action.base.ActionStep` objects to the GUI.  An ``ActionStep`` 
is a part of the action that requires user interaction (the user answering 
a question).  The result of this interaction is returned by the 
:keyword:`yield` statement.

To ask the user for a number of image files to import, we will pop up a file
selection dialog inside the ``model_run`` method:

.. literalinclude:: ../../../../camelot_example/importer.py
   :start-after: begin select files
   :end-before: end select files

The :keyword:`yield` statement returns a list of file names selected by
the user.

.. image:: /_static/actionsteps/select_file.png

Create new movies
=================

First make sure the ``Movie`` class has an :class:`camelot.types.Image` field
named ``cover`` which will store the image files.

.. literalinclude:: ../../../../camelot_example/model.py
   :start-after: begin image definition
   :end-before: end image definition

Next we add to the ``model_run`` method the actual creation of new movies.

.. literalinclude:: ../../../../camelot_example/importer.py
   :start-after: begin create movies
   :end-before: end create movies

In this part of the code several things happen :

**Store the images**

In the first lines, we do some sqlalchemy magic to get access to the
``storage`` attribute of the ``cover`` field.  This ``storage`` attribute
is of type :class:`camelot.core.files.storage.Storage`.  The ``Storage``
represents the files managed by Camelot.
   
**Create Movie objects**

Then for each file, a new ``Movie`` object is created with as title the
name of the file.  For the ``cover`` attribute, the file is checked in into
the ``Storage``.  This actually means the file is copied from its original
directory to a directory managed by Camelot.
   
**Write to the database**

In the last line, the ``session`` is flushed and thus all changes are
written to the database.  The ``FlushSession`` action step flushes the session
and propagetes the changes to the GUI.

**Keep the user informed**

For each movie imported, a :class:`camelot.view.action_steps.update_progress.UpdateProgress`
object is :keyword:`yield` to the GUI to inform the user of the import progress.
Each time such an object is yielded, the progress bar is updated.
   
.. image:: /_static/controls/progress_dialog.png
   
Refresh the GUI
===============

The last step of the ``model_run`` method will be to refresh the GUI.  So if
the user has the ``Movies`` table open when importing, this table will show the
newly created movies.

.. literalinclude:: ../../../../camelot_example/importer.py
   :start-after: begin refresh
   :end-before: end refresh
   
Result
======

This is how the resulting :file:`importer.py` file looks like :

.. literalinclude:: ../../../../camelot_example/importer.py

Unit tests
==========

Once an action works, its important to keep it working as the development of
the application continues.  One of the advantages of working with generators
for the user interaction, is that its easy to simulate the user interaction
towards the :meth:`model_run` method of the action.  This is done by using
the :meth:`send` method of the generator that is returned when calling
:meth:`model_run` :

.. literalinclude:: ../../../../test/test_action.py
   :start-after: begin test application action
   :end-before: end test application action

Conclusion
==========

We went through the basics of the action framework Camelot :

  * Subclassing a :class:`camelot.admin.action.Action`
    class
    
  * Implementing the ``model_run`` method
  
  * :keyword:`yield` :class:`camelot.admin.action.base.ActionStep` objects to 
    interact with the user
    
  * Add the :class:`camelot.admin.action.base.Action` object to a 
    :class:`camelot.admin.section.Section` in the side pane
  
More :class:`camelot.admin.action.base.ActionStep` classes can be found in 
the :mod:`camelot.view.action_steps` module.
