.. _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 ` 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 :class:`camelot.view.action_steps.orm.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.