=================================== Howto: build a website with OpenERP =================================== .. queue:: howto_website/series .. warning:: This guide assumes `basic knowledge of python `_. This guide assumes :ref:`an OpenERP installed and ready for development `. For production deployment, see the dedicated guides :ref:`using-gunicorn` and :ref:`using-mod-wsgi`. Creating a basic module ======================= In OpenERP, doing things takes the form of creating modules, and these modules customize the behavior of the OpenERP installation. The first step is thus to create a module: at the command-line, go to your server's directory and enter .. code-block:: console $ ./oe scaffold Academy ../my-modules This will build a basic module for you in a directory called ``my-modules`` right next to your server's directory: .. code-block:: text academy ├── __init__.py ├── __openerp__.py ├── controllers │   ├── __init__.py │   └── academy.py ├── models │   ├── __init__.py │   └── academy.py └── security └── ir.model.access.csv * ``academy`` is the root directory of your module * ``__init__.py`` tells Python that it is a valid package, and imports sub-packages and sub-modules * ``__openerp__.py`` provides various meta-information about your module to OpenERP (a short description, the module's dependencies, its author, its version, ...) * ``controllers`` holds the object responding to web (browser) requests - ``academy.py`` is where a default controller has been created for you * ``models`` holds OpenERP stored objects, ignore it for now, we'll dive into it when `storing data in OpenERP` * ``ir.model.access.csv`` defines basic access rights to the models, you can also ignore it for now .. patch:: :hidden: Now we can create a database, start your OpenERP server and install your new module in it: .. code-block:: console $ createdb academy $ ./openerp-server --addons-path=../web/addons,../addons,../my-modules \ -d academy -i academy --db-filter=academy * ``--addons-path`` tells OpenERP where it can find its modules. By default it will only look into ``openerp/addons``, this adds the web client modules, the "standard" business modules (not needed yet) and the place where your own ``academy`` module lives. * ``-i`` installs the provided module name in the database specified via ``-d`` * ``--db-filter`` means the specified database will be selected by default in the web interface, and will be the only one selectable (makes starting things up simpler) Once the installation is finished you should see ``HTTP service (werkzeug) running on 0.0.0.0:8069`` and nothing more happening in the log. You can now open a web browser and navigate to http://localhost:8069. A page should appear with just the words "Hello, world!" on it: .. image:: howto_website/helloworld.png This page is generated by the ``index`` method in :file:`academy/controllers/academy.py`, which just returns some text. Let's make it prettier by returning HTML and using bootstrap_ to get a nicer default rendering: .. patch:: Restart the server, refresh the page .. image:: howto_website/hellobootstrap.png Although it is subtle for so little text and markup, the font has changed and margins have been added to the page. .. note:: this example requires internet access as we're accessing a :abbr:`CDN (Content Delivery Network, large distributed networks hosting static files and trying to provide high-performance and high-availability of these files)`-hosted file. .. note:: At this point, the OpenERP server has no autoreloader. Every time you Python code (and later templates or data files), you should restart the server using the original startup instruction (without the re-creation of the database) Controller Parameters ===================== For dynamic pages, query parameters are passed as string parameters to the controller method. For instance the index page can display a list of teaching assistants, and link to each assistant's page using an index (in a global array for now): .. patch:: No validation is performed on query input values, it could be missing altogether (if a user accesses ``/tas/`` directly) or it could be incorrectly formatted. For this reason, query parameters are generally used to provide "options" to a given page, and "required" data tends (when possible) to be inserted directly in the URL. This we can do by adding `converter patterns`_ to the URL in ``@http.route``: .. patch:: These patterns will generally do some validation (e.g. if the ``id`` is not a valid integer the converter will result in a ``404 Not Found`` page instead of a 500 server error when the conversion failed in our own code) and may perform some parsing or type conversion (in this case the conversion from a URL section — a string — to a Python integer). Basic templates =============== So far we've output HTML by munging strings. It works, but is not exactly fun to edit (and somewhat unsafe to boot) as even advanced text editors have a hard time understanding they're dealing with HTML embedded in Python code. The usual solution is to use templates_, documents with placeholders which can be "rendered" to produce final pages (or others). OpenERP lets you use any Python templating system you want, but bundles its own :doc:`QWeb ` templating system which we'll later see offers some useful features. Let's move our 2 pseudo-templates from inline strings to actual templates: .. patch:: This simplifies the controller code by moving data formatting out of it, and generally makes it simpler for designers to edit the markup. .. todo:: link to section about reusing/altering existing stuff, template overriding .. _howto-website-support: OpenERP's Website support ========================= OpenERP 8 is bundled with new modules dedicated specifically to building websites (whether it be simply sets of pages or more complex components such as blogs). First, we'll install the ``website`` module: restart your server with .. code-block:: console $ ./openerp-server --addons-path=../web/addons,../addons,../my-modules \ -d academy -i website --db-filter=academy If you navigate to `your openerp`_, your basic page may have been replaced by the generic index page of the ``website`` module. Don't panic! (if it has not been replaced, don't panic either). The problem here is that both ``website`` and ``academy`` try to handle the ``/`` (root) URL, and which one *gets* it depends on the order in which they're loaded (the last loaded module gets the last say), which itself depends on a bunch of irrelevant stuff and is essentially non-deterministic at this point. To make loading order deterministic, we can add ``website`` as a dependency to ``academy``: .. patch:: This tells OpenERP that ``academy`` needs ``website`` to work correctly, and that it must only be loaded after ``website`` has already been loaded. This ensures ``academy``'s index page overwrites ``website``'s. .. note:: because a change in dependencies is a metadata alteration, you'll need to force an update to your module: restart your server with .. code-block:: console $ ./openerp-server --addons-path=../web/addons,../addons,../my-modules \ -d academy -u academy --db-filter=academy instead of the previous command (note: ``-i`` was replaced by ``-u``) If you reload `your openerp`_, you can see that your old index page hasn't changed at all. Which is odd since we wanted to use the new ``website`` tools. That is because much of these tools are inserted and enabled by the "layout template" provided by ``website``. Let's use that layout instead of our own page structure: .. patch:: * ``website.layout`` is the main Website layout, it provides standard headers and footers as well as integration with various customization tools. * there's quite a bit of complex markup, used as hooks for various features (e.g. snippets). Although technically not mandatory, some things will not work if they're not there. Reload `your openerp`_, the page has changed and new content has appeared (footer, menus, …) but there's still no advanced edition tools in sight, as you are not yet logged-in. Click on the :guilabel:`Sign In` link, fill in your credentials (``admin``/``admin`` by default), click :guilabel:`Log in`. You're now in OpenERP "proper", the backend/administrative interface. We'll deal with it in :ref:`a latter section `. For now, click on the :menuselection:`Website` menu item in the top-left of the browser, between :menuselection:`Messaging` and :menuselection:`Settings`. You're back to your website, but are now an administrator and thus have access to the advanced edition features of an OpenERP-built website. * if you go in the HTML editor (:menuselection:`Customize --> HTML Editor`), you can see and edit your template * if you click the :menuselection:`Edit` button in the top left, you'll switch to "Edition Mode" where the blocks (snippets) and rich text edition are available. * there are a number of other features in the advanced editor, which we will not cover here .. todo:: link to document walking through editor features .. todo:: website template generator You can play around and add blocks or edit content on the home page, however if you go to a TA's page and edit it things seem to work at first (e.g. insert a :guilabel:`image-text` snippet to one of the TAs, as if adding a picture and a short bio), but if you go to a different TA's page after saving the first one… he has the exact same snippet inserted (and the same content, if you edited the snippet's content)! Because snippets are added in the template itself, they're content which is the same across all pages using that template, and all the teaching assistants share the same template (``academy.ta``). Thus snippets are mostly for generic content, when a given template is only used for a single page, or to add content in HTML fields. .. todo:: link HTML fields to HTML fields doc? .. note:: When creating a new page (e.g. via :menuselection:`Content --> New Page`), OpenERP will duplicate a "source" template, and create a new template for each page. As a result, it's safe to use dedicated-content snippets for "static" pages. Storing data in OpenERP ======================= The conceptual storage model of OpenERP is simple: there are storage tables, represented by OpenERP models, and inside these tables are records. The first step, then, is to define a model. We'll start by moving our teaching assistants in the database: .. patch:: We've also altered the index method slightly, to retrieve our teaching assistants from the database instead of storing them in a global list in the module\ [#taprofile]_. .. note:: :file:`ir.model.access.csv` is necessary to tell OpenERP that any user can *see* the teaching assistants: by default, only the administrator can see, edit, create or destroy objects. Here, we only change the ``read`` permission to allow any user to list and browse teaching assistants. .. todo:: command/shortcut Update the module, reload `your openerp`_… and the Teaching Assistants list is empty since we haven't put any TA in the database. Let's add them in data files: .. patch:: Update the module again, reload `your openerp`_ and the TAs are back. .. warning:: if you can't see your data, check that you have reloaded the server with ``-i academy``, not ``-u academy``, new data files are not installed with ``-u``. Click on a TA name, and you'll see an error message. Let's fix the TA view now: .. patch:: There are a few non-obvious things here, so let's go through them for clarity: * OpenERP provides a has a special `converter pattern`_, which knows how to retrieve OpenERP objects by identifier. Instead of an integer or other similar basic value, ``ta`` thus gets a full-blown ``academy.tas`` object, without having to retrieve it by hand (as is done in ``index``). * However because the ``model()`` `converter pattern`_ takes an identifier, we have to alter the creation of ``ta``'s URL to include such an identifier, rather than an index in an array * Finally, ``website.render()`` wants a dict as its rendering context, not an object, which is why we wrap our ``ta`` object into one. We're still where we started this section though: if we add snippets to or edit the text of a TA's page, these editions will be visible across all TA pages since they'll be stored in the shared ``academy.ta`` template. Not only that, but we can not even edit the TA's name, even though it's not shared content. Let's fix that first, instead of using the basic "display this content" template tag ``t-esc``, we'll use one aware of OpenERP objects and their fields: .. patch:: Update the module, go into a TA page and activate the edition mode. If you move your mouse over the TA's name, it is surrounded by a yellow border, and you can edit its content. If you change the name of a TA and save the page, the change is correctly stored in the TA's record, the name is fixed when you go to the index page but other TAs remain unaffected. For the issue of customizing our TA profiles, we can expand our model with a "freeform" HTML field: .. patch:: Then, insert the new biographical content in the template using the same object-aware template tag: .. patch:: Update the module, browse to a TA's page and open the edition mode (using the :guilabel:`Edit` button in the window's top-right). The empty HTML field now displays a big placeholder image, if you drop snippets in or write some content for one of the teaching assistants, you will see that other TA profiles are unaffected. A more complex model -------------------- Up to now, we've been working with displaying and manipulating objects representing teaching assistants. It's a basic and simple concept, but not one which allows for much further diving into interesting tools of OpenERP. We need an object fitting the theme yet allowing for richer interactions and more interesting extensions. Course lectures seem to fit: they can be displayed in various manners (e.g. as a list of lectures or as a calendar), they can be moved around as necessary (cancelled/rescheduled), they can have numerous pieces of data attached both intrinsic (lecture transcripts) and extrinsic (attendance records, student discussions, etc…). .. patch:: Note a new feature: ``t-field`` tags can take options through ``t-field-options``. The options must be a JSON_ object. Available options depend on the field's type and potentially the display widget (some types of fields can be displayed in multiple manners). In this case, the same ``date`` field is displayed using custom date formats, one being the generic ``long`` (which depends on the current user's locale) and the other being an explicit format for `the weekday in short form `_. .. note:: in edition mode, formatted date and datetime fields revert to a canonical representation in order to provide all of the field's information. .. warning:: if you edit the course's dates, you will notice that the two displays of the ``date`` field are not synchronized, if one is edited the second one will not change until the edition is saved. This is a limitation of the current ``website`` but may be improved in future releases. .. sending & storing comments (?) .. _howto-website-administration: Administration and ERP Integration ================================== In practice, the data we've created so far using XML data files is usually stored as "demo data", used for testing and demonstrations of modules, and the actual user data is input via the OpenERP "backend", which we're going to try out now. First let's move our data set to demo data: .. patch:: the difference is simply that new databases can be created either in "demo" mode or in "no demo" mode. In the former case, the database will be preloaded with any demo data configured in the installed module. A brief and incomplete introduction to the OpenERP administration ----------------------------------------------------------------- You've already seen it for a very short time in :ref:`howto-website-support`, you can go back to it using :menuselection:`Administrator --> Administration` if you're already logged-in (which you should be), or go through :menuselection:`Sign In` again if you are not. The conceptual structure of the OpenERP backend is simple: 1. first are menus, menus are a tree (they can have sub-menus). To menus without children is mapped… 2. an action. Actions have various types, they can be links, reports (PDF), code which the server should execute or window actions. Window actions tell the client to display the OpenERP object according to certain views… 3. a view has a type, the broad category to which it corresponds (tree, form, graph, calendar, …) and its architecture which represents the way the object is laid out inside the view. By default, when an OpenERP object is *defined* it is essentially invisible in the interface. To make it visible, it needs to be available through an action, which itself needs to be reachable somehow, usually a through a menu. Let us, then, create a menu and an action for our lectures: .. patch:: .. note:: if a requested view does not exist, OpenERP will automatically generate a very basic one on-the-fly. That is the case here as we have not yet created a list and a form view for the lectures. If you reload the backend, you should see a new menu :menuselection:`Academy` at the top-left corner, before :menuselection:`Messaging`. In it is the submenus we defined via ``menuitem``, and within (the first submenu is selected by default) opens a list view of the lectures. To the right is a series of 2 buttons, which lets you toggle between the "list" view (overview of all records in the object) and the "form" view (view an manipulation of a single record). The :guilabel:`Create` button above the list lets you create new record, you can select records to delete them. There's one big issue to fix right now, the labeling of the column in the list and the fields in the form view, which are all currently :guilabel:`unknown`. We can fix that by adding a ``string`` attribute to the model field: .. patch:: The second problem is that the list view only displays the ``name`` field. To fix this, we have to create an explicit list view for lectures: .. patch:: .. todo:: link to list view documentation Reusing and customizing existing work ------------------------------------- OpenERP and its standard modules provide a number of models which may already solve your problem or part of your problem. Part of being a good OpenERP developer is having an idea of existing models and how they can be retrofit to your purposes. For our courses, instead of developing teaching assistants and lectures from scratch we could reuse existing OpenERP *users* (for teaching assistants) and *events* (for lectures)\ [#bonus]_, as well as the built-in website support for events. Install ``website_event`` (which will also install ``events``) by restarting the server as: .. code-block:: console $ ./openerp-server --addons-path=../web/addons,../addons,../my-modules \ -d academy -i website_event --db-filter=academy We'll also add it as a dependency to our module: .. patch:: Reload `your openerp`_, click on the new :menuselection:`Events` item which was added to the menu. This will be our new lectures page, but there are a few adaptations to perform Fixing the menu ~~~~~~~~~~~~~~~ The menu title is currently a generic *Events*, we only want lectures so we will rename it to *Lectures*. Website menu items are defined through the ``website.menu`` model, *Events* is defined by ``website_event`` and has the external id ``website_event.menu_events``, renaming it is as simple as overwriting the ``name`` field for that record: .. patch:: Restart the server with .. code-block:: console $ ./openerp-server --addons-path=../web/addons,../addons,../my-modules \ -d academy -i academy --db-filter=academy and the menu item has been renamed to Lectures. Removing the sidebar ~~~~~~~~~~~~~~~~~~~~ The filters sidebar is not necessary for our lectures. It can be removed in the UI via :menuselection:`Customize --> Filters` (and new filters can be added to the current filtering by date). Template customization is done by adding and removing extension views, so much like the renaming of the menu, we simply need to find the right record (here the Filters template view extending the basic event page) and set its value correctly: .. todo:: documentation for view inheritance/in-place extension .. patch:: Note that the option is still available in :menuselection:`Customize`, we have merely flipped the default around. Simplifying templates ~~~~~~~~~~~~~~~~~~~~~ There are still two things to fix in the lectures list. First, remove the *Our Events* link in the top-left corner, simply replace the breadcrumb element by nothing: .. patch:: Second, remove the "organized by" and type rows in the event's description, keep only the datetime and location: .. patch:: Moving lectures and TAs ~~~~~~~~~~~~~~~~~~~~~~~ The gist of the operation is fairly simple, but there are lots of changes: * The custom models can be removed as we'll be using standard objects * The controller has to be altered to fetch from standard objects (``event.event`` and ``res.users``), we'll use groups to discriminate between our academy objects and other demo objects, so that has to be used as well * HTML templates have to be slightly edited to match the new objects (our lecture's ``date`` field is replaced by ``event.event``'s ``date_begin``) * Missing parts of the standard events have to be added (``res.partner``, which is where "personal" informations are stored for ``res.users``, does not have a biographical field. We have to add it) * Finally demo files must be converted, and existing demo data should be purged if we do not need it (e.g. existing non-lectures events and event types can be removed before adding our own) .. patch:: Our data is back in the fontend (site), and in the backend we get administrative views for free, e.g. a calendar view of our lectures. .. [#taprofile] the teaching assistants profile view ends up broken for now, but don't worry we'll get around to it .. [#bonus] as a bonus, we get access rights and TA access to the administrative backend "for free" .. _bootstrap: http://getbootstrap.com .. _converter pattern: .. _converter patterns: http://werkzeug.pocoo.org/docs/routing/#rule-format .. _templates: http://en.wikipedia.org/wiki/Web_template .. _your openerp: http://localhost:8069/ .. _JSON: http://www.json.org