[IMP] doc/howtos/backend: add section on wizards
authorRaphael Collet <rco@openerp.com>
Wed, 27 Aug 2014 14:06:30 +0000 (16:06 +0200)
committerRaphael Collet <rco@openerp.com>
Wed, 27 Aug 2014 14:07:43 +0000 (16:07 +0200)
doc/howtos/backend.rst
doc/howtos/backend/exercise-constraint-sql
doc/howtos/backend/exercise-copy-override
doc/howtos/backend/exercise-o2m-views [deleted file]
doc/howtos/backend/exercise-one2many
doc/howtos/backend/exercise-wizard [new file with mode: 0644]
doc/howtos/backend/exercise-wizard-action [new file with mode: 0644]
doc/howtos/backend/exercise-wizard-launch [new file with mode: 0644]
doc/howtos/backend/exercise-wizard-multi [new file with mode: 0644]
doc/howtos/backend/series

index 7af76db..9f5bc5f 100644 (file)
@@ -839,6 +839,19 @@ float, string), or a function taking a recordset and returning a value::
     name = fields.Char(default="Unknown")
     user_id = fields.Many2one('res.users', default=lambda self: self.env.user)
 
+.. note::
+
+    The object ``self.env`` gives access to request parameters and other useful
+    things:
+
+    - ``self.env.cr`` or ``self._cr`` is the database *cursor* object; it is
+      used for querying the database
+    - ``self.env.uid`` or ``self._uid`` is the current user's database id
+    - ``self.env.user`` is the current user's record
+    - ``self.env.context`` or ``self._context`` is the context dictionary
+    - ``self.env.ref(xml_id)`` returns the record corresponding to an XML id
+    - ``self.env[model_name]`` returns an instance of the given model
+
 .. exercise:: Active objects – Default values
 
     * Define the start_date default value as today (see
@@ -1289,7 +1302,7 @@ policy.
 Group-based access control mechanisms
 -------------------------------------
 
-Groups are created as normal records on the model “res.groups”, and granted
+Groups are created as normal records on the model ``res.groups``, and granted
 menu access via menu definitions. However even without a menu, objects may
 still be accessible indirectly, so actual object-level permissions (read,
 write, create, unlink) must be defined for groups. They are usually inserted
@@ -1299,7 +1312,7 @@ specific fields on a view or object using the field's groups attribute.
 Access rights
 -------------
 
-Access rights are defined as records of the model “ir.model.access”. Each
+Access rights are defined as records of the model ``ir.model.access``. Each
 access right is associated to a model, a group (or no group for global
 access), and a set of permissions: read, write, create, unlink. Such access
 rights are usually created by a CSV file named after its model:
@@ -1349,14 +1362,14 @@ Record rules
 ------------
 
 A record rule restricts the access rights to a subset of records of the given
-model. A rule is a record of the model “ir.rule”, and is associated to a
+model. A rule is a record of the model ``ir.rule``, and is associated to a
 model, a number of groups (many2many field), permissions to which the
 restriction applies, and a domain. The domain specifies to which records the
 access rights are limited.
 
 Here is an example of a rule that prevents the deletion of leads that are not
-in state “cancel”. Notice that the value of the field “groups” must follow
-the same convention as the method “write” of the ORM.
+in state ``cancel``. Notice that the value of the field ``groups`` must follow
+the same convention as the method ``write`` of the ORM.
 
 .. code-block:: xml
 
@@ -1384,6 +1397,94 @@ the same convention as the method “write” of the ORM.
 
         .. patch::
 
+Wizards
+=======
+
+Wizards describe interactive sessions with the user (or dialog boxes) through
+dynamic forms. A wizard is simply a model that extends the class
+:class:`~openerp.models.TransientModel` instead of
+:class:`~openerp.models.Model`. The class
+:class:`~openerp.models.TransientModel` extends :class:`~openerp.models.Model`
+and reuse all its existing mechanisms, with the following particularities:
+
+- Wizard records are not meant to be persistent; they are automatically deleted
+  from the database after a certain time. This is why they are called
+  *transient*.
+- Wizard models do not require explicit access rights: users have all
+  permissions on wizard records.
+- Wizard records may refer to regular records or wizard records through many2one
+  fields, but regular records *cannot* refer to wizard records through a
+  many2one field.
+
+We want to create a wizard that allow users to create attendees for a particular
+session, or for a list of sessions at once.
+
+.. exercise:: Define the wizard
+
+    Create a wizard model with a many2one relationship with the *Session*
+    model and a many2many relationship with the *Partner* model.
+
+    .. only:: solutions
+
+        Add a new file ``openacademy/wizard.py``:
+
+        .. patch::
+
+Launching wizards
+-----------------
+
+Wizards are launched by ``ir.actions.act_window`` records, with the field
+``target`` set to the value ``new``. The latter opens the wizard view into a
+popup window. The action may be triggered by a menu item.
+
+There is another way to launch the wizard: using an ``ir.actions.act_window``
+record like above, but with an extra field ``src_model`` that specifies in the
+context of which model the action is available. The wizard will appear in the
+contextual actions of the model, above the main view. Because of some internal
+hooks in the ORM, such an action is declared in XML with the tag ``act_window``.
+
+.. code:: xml
+
+    <act_window id="launch_the_wizard"
+                name="Launch the Wizard"
+                src_model="context_model_name"
+                res_model="wizard_model_name"
+                view_mode="form"
+                target="new"
+                key2="client_action_multi"/>
+
+Wizards use regular views and their buttons may use the attribute
+``special="cancel"`` to close the wizard window without saving.
+
+.. exercise:: Launch the wizard
+
+    #. Define a form view for the wizard.
+    #. Add the action to launch it in the context of the *Session* model.
+    #. Define a default value for the session field in the wizard; use the
+       context parameter ``self._context`` to retrieve the current session.
+
+    .. only:: solutions
+
+        .. patch::
+
+.. exercise:: Register attendees
+
+    Add buttons to the wizard, and implement the corresponding method for adding
+    the attendees to the given session.
+
+    .. only:: solutions
+
+        .. patch::
+
+.. exercise:: Register attendees to multiple sessions
+
+    Modify the wizard model so that attendees can be registered to multiple
+    sessions.
+
+    .. only:: solutions
+
+        .. patch::
+
 Internationalization
 ====================
 
index dad20c5..99ddeab 100644 (file)
@@ -3,11 +3,11 @@
 
 Index: addons/openacademy/models.py
 ===================================================================
---- addons.orig/openacademy/models.py  2014-08-26 17:26:07.479783261 +0200
-+++ addons/openacademy/models.py       2014-08-26 17:26:07.475783261 +0200
-@@ -14,6 +14,16 @@
-         'openacademy.session', 'course_id', string="Session")
+--- addons.orig/openacademy/models.py  2014-08-27 14:19:33.099111181 +0200
++++ addons/openacademy/models.py       2014-08-27 14:20:03.311110732 +0200
+@@ -13,6 +13,16 @@
+     session_ids = fields.One2many(
+         'openacademy.session', 'course_id', string="Sessions")
  
 +    _sql_constraints = [
 +        ('name_description_check',
@@ -19,6 +19,6 @@ Index: addons/openacademy/models.py
 +         "The course title must be unique"),
 +    ]
 +
  class Session(models.Model):
      _name = 'openacademy.session'
index c8340f2..41ed888 100644 (file)
@@ -3,11 +3,11 @@
 
 Index: addons/openacademy/models.py
 ===================================================================
---- addons.orig/openacademy/models.py  2014-08-26 17:26:08.359783248 +0200
-+++ addons/openacademy/models.py       2014-08-26 17:26:08.351783248 +0200
-@@ -14,6 +14,20 @@
-         'openacademy.session', 'course_id', string="Session")
+--- addons.orig/openacademy/models.py  2014-08-27 14:20:12.363110598 +0200
++++ addons/openacademy/models.py       2014-08-27 14:20:12.359110598 +0200
+@@ -13,6 +13,20 @@
+     session_ids = fields.One2many(
+         'openacademy.session', 'course_id', string="Sessions")
  
 +    @api.one
 +    def copy(self, default=None):
diff --git a/doc/howtos/backend/exercise-o2m-views b/doc/howtos/backend/exercise-o2m-views
deleted file mode 100644 (file)
index 9ba227d..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-# HG changeset patch
-# Parent 1299668a15a9359d4ef77d8f5231816c7de476fa
-
-Index: addons/openacademy/views/openacademy.xml
-===================================================================
---- addons.orig/openacademy/views/openacademy.xml      2014-08-26 17:26:00.403783366 +0200
-+++ addons/openacademy/views/openacademy.xml   2014-08-26 17:26:00.399783366 +0200
-@@ -9,13 +9,19 @@
-                     <sheet>
-                         <group>
-                             <field name="name"/>
-+                            <field name="responsible_id"/>
-                         </group>
-                         <notebook>
-                             <page string="Description">
-                                 <field name="description"/>
-                             </page>
--                            <page string="About">
--                                This is an example of notebooks
-+                            <page string="Sessions">
-+                                <field name="session_ids">
-+                                    <tree string="Registered sessions">
-+                                        <field name="name"/>
-+                                        <field name="instructor_id"/>
-+                                    </tree>
-+                                </field>
-                             </page>
-                         </notebook>
-                     </sheet>
-@@ -34,6 +40,18 @@
-             </field>
-         </record>
-+        <!-- override the automatically generated list view for courses -->
-+        <record model="ir.ui.view" id="course_tree_view">
-+            <field name="name">course.tree</field>
-+            <field name="model">openacademy.course</field>
-+            <field name="arch" type="xml">
-+                <tree string="Course Tree">
-+                    <field name="name"/>
-+                    <field name="responsible_id"/>
-+                </tree>
-+            </field>
-+        </record>
-+
-         <!-- window action -->
-         <!--
-             The following tag is an action definition for a "window action",
-@@ -65,6 +83,44 @@
-              action="openacademy.course_list_action"
-              It is not required when it is the same module -->
-+        <!-- session's form view -->
-+        <record model="ir.ui.view" id="session_form_view">
-+            <field name="name">session.form</field>
-+            <field name="model">openacademy.session</field>
-+            <field name="arch" type="xml">
-+                <form string="Session Form">
-+                    <sheet>
-+                        <group>
-+                            <group string="General">
-+                                <field name="course_id"/>
-+                                <field name="name"/>
-+                                <field name="instructor_id"/>
-+                            </group>
-+                            <group string="Schedule">
-+                                <field name="start_date"/>
-+                                <field name="duration"/>
-+                                <field name="seats"/>
-+                            </group>
-+                        </group>
-+                        <label for="attendee_ids"/>
-+                        <field name="attendee_ids"/>
-+                    </sheet>
-+                </form>
-+            </field>
-+        </record>
-+
-+        <!-- session's tree/list view -->
-+        <record model="ir.ui.view" id="session_tree_view">
-+            <field name="name">session.tree</field>
-+            <field name="model">openacademy.session</field>
-+            <field name="arch" type="xml">
-+                <tree string="Session Tree">
-+                    <field name="name"/>
-+                    <field name="course_id"/>
-+                </tree>
-+            </field>
-+        </record>
-+
-         <record model="ir.actions.act_window" id="session_list_action">
-             <field name="name">Sessions</field>
-             <field name="res_model">openacademy.session</field>
index b067332..45409b1 100644 (file)
@@ -3,21 +3,21 @@
 
 Index: addons/openacademy/models.py
 ===================================================================
---- addons.orig/openacademy/models.py  2014-08-27 10:37:24.591932036 +0200
-+++ addons/openacademy/models.py       2014-08-27 10:37:24.583932036 +0200
+--- addons.orig/openacademy/models.py  2014-08-27 14:18:59.091111685 +0200
++++ addons/openacademy/models.py       2014-08-27 14:19:03.259111624 +0200
 @@ -10,6 +10,8 @@
  
      responsible_id = fields.Many2one('res.users',
          ondelete='set null', string="Responsible", index=True)
 +    session_ids = fields.One2many(
-+        'openacademy.session', 'course_id', string="Session")
++        'openacademy.session', 'course_id', string="Sessions")
  
  
  class Session(models.Model):
 Index: addons/openacademy/views/openacademy.xml
 ===================================================================
---- addons.orig/openacademy/views/openacademy.xml      2014-08-27 10:37:24.591932036 +0200
-+++ addons/openacademy/views/openacademy.xml   2014-08-27 10:37:24.583932036 +0200
+--- addons.orig/openacademy/views/openacademy.xml      2014-08-27 14:18:59.091111685 +0200
++++ addons/openacademy/views/openacademy.xml   2014-08-27 14:18:59.083111686 +0200
 @@ -15,8 +15,13 @@
                              <page string="Description">
                                  <field name="description"/>
diff --git a/doc/howtos/backend/exercise-wizard b/doc/howtos/backend/exercise-wizard
new file mode 100644 (file)
index 0000000..1605185
--- /dev/null
@@ -0,0 +1,24 @@
+Index: addons/openacademy/__init__.py
+===================================================================
+--- addons.orig/openacademy/__init__.py        2014-08-27 14:19:23.023111330 +0200
++++ addons/openacademy/__init__.py     2014-08-27 14:22:25.043108628 +0200
+@@ -2,3 +2,4 @@
+ import controllers
+ import models
+ import partner
++import wizard
+Index: addons/openacademy/wizard.py
+===================================================================
+--- /dev/null  1970-01-01 00:00:00.000000000 +0000
++++ addons/openacademy/wizard.py       2014-08-27 14:24:39.195106637 +0200
+@@ -0,0 +1,10 @@
++# -*- coding: utf-8 -*-
++
++from openerp import models, fields, api
++
++class Wizard(models.TransientModel):
++    _name = 'openacademy.wizard'
++
++    session_id = fields.Many2one('openacademy.session',
++        string="Session", required=True)
++    attendee_ids = fields.Many2many('res.partner', string="Attendees")
diff --git a/doc/howtos/backend/exercise-wizard-action b/doc/howtos/backend/exercise-wizard-action
new file mode 100644 (file)
index 0000000..45a5f5c
--- /dev/null
@@ -0,0 +1,30 @@
+Index: addons/openacademy/views/openacademy.xml
+===================================================================
+--- addons.orig/openacademy/views/openacademy.xml      2014-08-27 15:01:00.355074258 +0200
++++ addons/openacademy/views/openacademy.xml   2014-08-27 15:04:45.891070910 +0200
+@@ -245,6 +245,12 @@
+                         <field name="session_id"/>
+                         <field name="attendee_ids"/>
+                     </group>
++                    <footer>
++                        <button name="subscribe" type="object"
++                                string="Subscribe" class="oe_highlight"/>
++                        or
++                        <button special="cancel" string="Cancel"/>
++                    </footer>
+                 </form>
+             </field>
+         </record>
+Index: addons/openacademy/wizard.py
+===================================================================
+--- addons.orig/openacademy/wizard.py  2014-08-27 14:43:02.323090261 +0200
++++ addons/openacademy/wizard.py       2014-08-27 15:05:28.407070278 +0200
+@@ -11,3 +11,8 @@
+     session_id = fields.Many2one('openacademy.session',
+         string="Session", required=True, default=_default_session)
+     attendee_ids = fields.Many2many('res.partner', string="Attendees")
++
++    @api.multi
++    def subscribe(self):
++        self.session_id.attendee_ids |= self.attendee_ids
++        return {}
diff --git a/doc/howtos/backend/exercise-wizard-launch b/doc/howtos/backend/exercise-wizard-launch
new file mode 100644 (file)
index 0000000..7689bfd
--- /dev/null
@@ -0,0 +1,46 @@
+Index: addons/openacademy/wizard.py
+===================================================================
+--- addons.orig/openacademy/wizard.py  2014-08-27 14:24:39.195106637 +0200
++++ addons/openacademy/wizard.py       2014-08-27 14:43:02.323090261 +0200
+@@ -5,6 +5,9 @@
+ class Wizard(models.TransientModel):
+     _name = 'openacademy.wizard'
++    def _default_session(self):
++        return self.env['openacademy.session'].browse(self._context.get('active_id'))
++
+     session_id = fields.Many2one('openacademy.session',
+-        string="Session", required=True)
++        string="Session", required=True, default=_default_session)
+     attendee_ids = fields.Many2many('res.partner', string="Attendees")
+Index: addons/openacademy/views/openacademy.xml
+===================================================================
+--- addons.orig/openacademy/views/openacademy.xml      2014-08-27 14:20:50.071110038 +0200
++++ addons/openacademy/views/openacademy.xml   2014-08-27 15:01:00.355074258 +0200
+@@ -235,5 +235,26 @@
+         <menuitem id="session_menu" name="Sessions"
+                   parent="openacademy_menu"
+                   action="session_list_action"/>
++
++        <record model="ir.ui.view" id="wizard_form_view">
++            <field name="name">wizard.form</field>
++            <field name="model">openacademy.wizard</field>
++            <field name="arch" type="xml">
++                <form string="Add Attendees">
++                    <group>
++                        <field name="session_id"/>
++                        <field name="attendee_ids"/>
++                    </group>
++                </form>
++            </field>
++        </record>
++
++        <act_window id="launch_session_wizard"
++                    name="Add Attendees"
++                    src_model="openacademy.session"
++                    res_model="openacademy.wizard"
++                    view_mode="form"
++                    target="new"
++                    key2="client_action_multi"/>
+     </data>
+ </openerp>
diff --git a/doc/howtos/backend/exercise-wizard-multi b/doc/howtos/backend/exercise-wizard-multi
new file mode 100644 (file)
index 0000000..19a21a4
--- /dev/null
@@ -0,0 +1,38 @@
+Index: addons/openacademy/views/openacademy.xml
+===================================================================
+--- addons.orig/openacademy/views/openacademy.xml      2014-08-27 15:04:45.891070910 +0200
++++ addons/openacademy/views/openacademy.xml   2014-08-27 15:27:13.919050898 +0200
+@@ -242,7 +242,7 @@
+             <field name="arch" type="xml">
+                 <form string="Add Attendees">
+                     <group>
+-                        <field name="session_id"/>
++                        <field name="session_ids"/>
+                         <field name="attendee_ids"/>
+                     </group>
+                     <footer>
+Index: addons/openacademy/wizard.py
+===================================================================
+--- addons.orig/openacademy/wizard.py  2014-08-27 15:05:28.407070278 +0200
++++ addons/openacademy/wizard.py       2014-08-27 15:27:07.119050999 +0200
+@@ -5,14 +5,15 @@
+ class Wizard(models.TransientModel):
+     _name = 'openacademy.wizard'
+-    def _default_session(self):
+-        return self.env['openacademy.session'].browse(self._context.get('active_id'))
++    def _default_sessions(self):
++        return self.env['openacademy.session'].browse(self._context.get('active_ids'))
+-    session_id = fields.Many2one('openacademy.session',
+-        string="Session", required=True, default=_default_session)
++    session_ids = fields.Many2many('openacademy.session',
++        string="Sessions", required=True, default=_default_sessions)
+     attendee_ids = fields.Many2many('res.partner', string="Attendees")
+     @api.multi
+     def subscribe(self):
+-        self.session_id.attendee_ids |= self.attendee_ids
++        for session in self.session_ids:
++            session.attendee_ids |= self.attendee_ids
+         return {}
index c21ba15..2057150 100644 (file)
@@ -30,6 +30,10 @@ exercise-state-workflow-automatic
 exercise-state-workflow-actions
 exercise-access-rights
 exercise-access-rules
+exercise-wizard
+exercise-wizard-launch
+exercise-wizard-action
+exercise-wizard-multi
 exercise-translations
 exercise-report
 exercise-dashboard