summaryrefslogtreecommitdiff
path: root/doc/reference
diff options
context:
space:
mode:
authorDamien Bouvy <dbo@odoo.com>2016-09-21 18:42:22 +0200
committerDamien Bouvy <dbo@odoo.com>2016-10-05 23:06:23 +0200
commit9b55bf5b3df44f645383e4d4062bf7b70f00c6f5 (patch)
tree1a3bd585afbed31050536cec587892d4f2411771 /doc/reference
parent49033e3ec6f8e47a503209a925c31bd5fc244424 (diff)
[DOC] doc: add documentation for mixins and useful classes
Documentation to help developers use the following classes: - utm.mixin - rating.mixin - website.published.mixin - website.seo.metadata - mail.thread - mail.alias.mixin After careful consideration, the ir.needaction.mixin was not included in this doc page since it is no longer displayed anywhere and will be dropped quite soon.
Diffstat (limited to 'doc/reference')
-rw-r--r--doc/reference/mixins.rst1037
1 files changed, 1037 insertions, 0 deletions
diff --git a/doc/reference/mixins.rst b/doc/reference/mixins.rst
new file mode 100644
index 00000000000..4e001aae583
--- /dev/null
+++ b/doc/reference/mixins.rst
@@ -0,0 +1,1037 @@
+:banner: banners/mixins.jpg
+
+.. _reference/mixins:
+
+=========================
+Mixins and Useful Classes
+=========================
+
+Odoo implements some useful classes and mixins that make it easy for you to add
+often-used behaviours on your objects. This guide will details most of them, with
+examples and use cases.
+
+.. _reference/mixins/mail:
+
+Messaging features
+==================
+
+.. _reference/mixins/mail/chatter:
+
+Messaging integration
+---------------------
+
+Basic messaging system
+''''''''''''''''''''''
+
+Integrating messaging features to your model is extremely easy. Simply inheriting
+the ``mail.thread`` model and adding the messaging fields (and their appropriate
+widgets) to your form view will get you up and running in no time.
+
+.. admonition:: Example
+
+ Let's create a simplistic model representing a business trip. Since organizing
+ this kind of trip usually involves a lot of people and a lot of discussion, let's
+ add support for message exchange on the model.
+
+ .. code-block:: python
+
+ class BusinessTrip(models.Model):
+ _name = 'business.trip'
+ _inherit = ['mail.thread']
+ _description = 'Business Trip'
+
+ name = fields.Char()
+ partner_id = fields.Many2one('res.partner', 'Responsible')
+ guest_ids = fields.Many2many('res.partner', 'Participants')
+
+ In the form view:
+
+ .. code-block:: xml
+
+ <record id="businness_trip_form" model="ir.ui.view">
+ <field name="name">business.trip.form</field>
+ <field name="model">business.trip</field>
+ <field name="arch" type="xml">
+ <form string="Business Trip">
+ <!-- Your usual form view goes here
+ ...
+ Then comes chatter integration -->
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+
+Once you've added chatter support on your model, users can easily add messages
+or internal notes on any record of your model; every one of those will send a
+notification (to all followers for messages, to employee (*base.group_user*)
+users for internal notes). If your mail gateway and catchall address are correctly
+configured, these notifications will be sent by e-mail and can be replied-to directly
+from your mail client; the automatic routing system will route the answer to the
+correct thread.
+
+Server-side, some helper functions are there to help you easily send messages and
+to manage followers on your record:
+
+.. rubric:: Posting messages
+
+.. method:: message_post(self, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, content_subtype='html', **kwargs)
+
+ Post a new message in an existing thread, returning the new
+ mail.message ID.
+
+ :param str body: body of the message, usually raw HTML that will
+ be sanitized
+ :param str message_type: see mail_message.type field
+ :param str content_subtype: if plaintext: convert body into html
+ :param int parent_id: handle reply to a previous message by adding the
+ parent partners to the message in case of private discussion
+ :param list(tuple(str,str)) attachments: list of attachment tuples in the form
+ ``(name,content)``, where content is NOT base64 encoded
+ :param `\**kwargs`: extra keyword arguments will be used as default column values for the
+ new mail.message record
+ :return: ID of newly created mail.message
+ :rtype: int
+
+.. method:: message_post_with_view(views_or_xmlid, **kwargs):
+
+ Helper method to send a mail / post a message using a view_id to
+ render using the ir.qweb engine. This method is stand alone, because
+ there is nothing in template and composer that allows to handle
+ views in batch. This method will probably disappear when templates
+ handle ir ui views.
+
+ :param str or ``ir.ui.view`` record: external id or record of the view that
+ should be sent
+
+.. method:: message_post_with_template(template_id, **kwargs)
+
+ Helper method to send a mail with a template
+
+ :param template_id: the id of the template to render to create the body of the message
+ :param `\**kwargs`: parameter to create a mail.compose.message wizzard (which inherit from mail.message)
+
+.. rubric:: Receiving messages
+
+These methods are called when a new e-mail is processed by the mail gateway. These
+e-mails can either be new thread (if they arrive via an :ref:`alias <reference/mixins/mail/alias>`)
+or simply replies from an existing thread. Overriding them allows you to set values
+on the thread's record depending on some values from the email itself (i.e. update
+a date or an e-mail address, add CC's addresses as followers, etc.).
+
+.. method:: message_new(msg_dict, custom_values=None)
+
+ Called by ``message_process`` when a new message is received
+ for a given thread model, if the message did not belong to
+ an existing thread.
+
+ The default behavior is to create a new record of the corresponding
+ model (based on some very basic info extracted from the message).
+ Additional behavior may be implemented by overriding this method.
+
+ :param dict msg_dict: a map containing the email details and
+ attachments. See ``message_process`` and ``mail.message.parse`` for details
+ :param dict custom_values: optional dictionary of additional
+ field values to pass to create() when creating the new thread record;
+ be careful, these values may override any other values coming from
+ the message
+ :rtype: int
+ :return: the id of the newly created thread object
+
+.. method:: message_update(msg_dict, update_vals=None)
+
+ Called by ``message_process`` when a new message is received
+ for an existing thread. The default behavior is to update the record
+ with ``update_vals`` taken from the incoming email.
+
+ Additional behavior may be implemented by overriding this
+ method.
+
+ :param dict msg_dict: a map containing the email details and attachments;
+ see ``message_process`` and ``mail.message.parse()`` for details.
+ :param dict update_vals: a dict containing values to update records given
+ their ids; if the dict is None or is void, no write operation is performed.
+ :return: True
+
+.. rubric:: Followers management
+
+.. method:: message_subscribe(partner_ids=None, channel_ids=None, subtype_ids=None, force=True)
+
+ Add partners to the records followers.
+
+ :param list(int) partner_ids: IDs of the partners that will be subscribed
+ to the record
+ :param list(int) channel_ids: IDs of the channels that will be subscribed
+ to the record
+ :param list(int) subtype_ids: IDs of the subtypes that the channels/partners
+ will be subscribed to (defaults to the default subtypes if ``None``)
+ :param force: if True, delete existing followers before creating new one
+ using the subtypes given in the parameters
+ :return: Success/Failure
+ :rtype: bool
+
+.. method:: message_subscribe_users(user_ids=None, subtype_ids=None)
+
+ Wrapper on message_subscribe, using users instead of partners.
+
+ :param list(int) user_ids: IDs of the users that will be subscribed
+ to the record; if ``None``, subscribe the current user instead.
+ :param list(int) subtype_ids: IDs of the subtypes that the channels/partners
+ will be subscribed to
+ :return: Success
+ :rtype: bool
+
+.. method:: message_unsubscribe(partner_ids=None, channel_ids=None)
+
+ Remove partners from the record's followers.
+
+ :param list(int) partner_ids: IDs of the partners that will be subscribed
+ to the record
+ :param list(int) channel_ids: IDs of the channels that will be subscribed
+ to the record
+ :return: True
+ :rtype: bool
+
+
+.. method:: message_unsubscribe_users(user_ids=None)
+
+ Wrapper on message_subscribe, using users.
+
+ :param list(int) user_ids: IDs of the users that will be unsubscribed
+ to the record; if None, unsubscribe the current user instead.
+ :return: True
+ :rtype: bool
+
+
+Logging changes
+'''''''''''''''
+
+The ``mail`` module adds a powerful tracking system on fields, allowing you
+to log changes to specific fields in the record's chatter.
+
+To add tracking to a field, simple add the track_visibility attribute with the
+value ``onchange`` (if it should be displayed in the notification only if the
+field changed) or ``always`` (if the value should always be displayed in change
+notifications even if this particular field did not change - useful to make
+notification more explanatory by always adding the name field, for example).
+
+.. admonition:: Example
+
+ Let's track changes on the name and responsible of our business trips:
+
+ .. code-block:: python
+
+ class BusinessTrip(models.Model):
+ _name = 'business.trip'
+ _inherit = ['mail.thread']
+ _description = 'Business Trip'
+
+ name = fields.Char(track_visibility='always')
+ partner_id = fields.Many2one('res.partner', 'Responsible',
+ track_visibility='onchange')
+ guest_ids = fields.Many2many('res.partner', 'Participants')
+
+ From now on, every change to a trip's name or responsible will log a note
+ on the record. The ``name`` field will be displayed in the notification as
+ well to give more context about the notification (even if the name did not
+ change).
+
+
+Subtypes
+''''''''
+
+Subtypes give you more granular control over messages. Subtypes act as a classification
+system for notifications, allowing subscribers to a document to customize the
+subtype of notifications they wish to receive.
+
+Subtypes are created as data in your module; the model has the following fields:
+
+``name`` (mandatory) - :class:`~openerp.fields.Char`
+ name of the subtype, will be displayed in the notification customization
+ popup
+``description`` - :class:`~openerp.fields.Char`
+ description that will be added in the message posted for this
+ subtype. If void, the name will be added instead
+``internal`` - :class:`~openerp.fields.Boolean`
+ messages with internal subtypes will be visible only by employees,
+ aka members of the ``base.group_user`` group
+``parent_id`` - :class:`~openerp.fields.Many2one`
+ link subtypes for automatic subscription; for example project subtypes are
+ linked to task subtypes through this link. When someone is subscribed to
+ a project, he will be subscribed to all tasks of this project with
+ subtypes found using the parent subtype
+``relation_field`` - :class:`~openerp.fields.Char`
+ as an example, when linking project and tasks subtypes, the relation
+ field is the project_id field of tasks
+``res_model`` - :class:`~openerp.fields.Char`
+ model the subtype applies to; if False, this subtype applies to all models
+``default`` - :class:`~openerp.fields.Boolean`
+ wether the subtype is activated by default when subscribing
+``sequence`` - :class:`~openerp.fields.Integer`
+ used to order subtypes in the notification customization popup
+``hidden`` - :class:`~openerp.fields.Boolean`
+ wether the subtype is hidden in the notification customization popup
+
+
+Interfacing subtypes with field tracking allows to subscribe to different kind
+of notifications depending on what might interest users. To do this, you
+can override the ``_track_subtype()`` function:
+
+.. method:: _track_subtype(init_values)
+
+ Give the subtype triggered by the changes on the record according
+ to values that have been updated.
+
+ :param dict init_values: the original values of the record; only modified fields
+ are present in the dict
+ :returns: a subtype's full external id or False if no subtype is triggered
+
+
+.. admonition:: Example
+
+ Let's add a ``state`` field on our example class and trigger a notification
+ with a specific subtype when this field change values.
+
+ First, let's define our subtype:
+
+ .. code-block:: xml
+
+ <record id="mt_state_change" model="mail.message.subtype">
+ <field name="name">Trip confirmed</field>
+ <field name="res_model">business.trip</field>
+ <field name="default" eval="True"/>
+ <field name="description">Business Trip confirmed!</field>
+ </record>
+
+
+ Then, we need to override the ``track_subtype()`` function. This function
+ is called by the tracking system to know which subtype should be used depending
+ on the change currently being applied. In our case, we want to use our shiny new
+ subtype when the ``state`` field changes from *draft* to *confirmed*:
+
+ .. code-block:: python
+
+ class BusinessTrip(models.Model):
+ _name = 'business.trip'
+ _inherit = ['mail.thread']
+ _description = 'Business Trip'
+
+ name = fields.Char(track_visibility='onchange')
+ partner_id = fields.Many2one('res.partner', 'Responsible',
+ track_visibility='onchange')
+ guest_ids = fields.Many2many('res.partner', 'Participants')
+ state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')],
+ track_visibility='onchange')
+
+ def _track_subtype(self, init_values):
+ # init_values contains the modified fields' values before the changes
+ #
+ # the applied values can be accessed on the record as they are already
+ # in cache
+ self.ensure_one()
+ if 'state' in init_values and self.state == 'confirmed':
+ return 'my_module.mt_state_change' # Full external id
+ return super(BusinessTrip, self)._track_subtype(init_values)
+
+
+Customizing notifications
+'''''''''''''''''''''''''
+
+When sending notifications to followers, it can be quite useful to add buttons in
+the template to allow quick actions directly from the e-mail. Even a simple button
+to link directly to the record's form view can be useful; however in most cases
+you don't want to display these buttons to portal users.
+
+The notification system allows customizing notification templates in the following
+ways:
+
+- Display *Access Buttons*: these buttons are visible at the top of the notification
+ e-mail and allow the recipient to directly access the form view of the record
+- Display *Follow Buttons*: these buttons allow the recipient to
+ directly quickly subscribe from the record
+- Display *Unfollow Buttons*: these buttons allow the recipient to
+ directly quickly unsubscribe from the record
+- Display *Custom Action Buttons*: these buttons are calls to specific routes
+ and allow you to make some useful actions directly available from the e-mail (i.e.
+ converting a lead to an opportunity, validating an expense sheet for an
+ Expense Manager, etc.)
+
+These buttons settings can be applied to different groups that you can define
+yourself by overriding the function ``_notification_recipients``.
+
+.. method:: _notification_recipients(message, groups)
+
+ Give the subtype triggered by the changes on the record according
+ to values that have been updated.
+
+ :param ``record`` message: ``mail.message`` record currently being sent
+ :param list(tuple) groups: list of tuple of the form (group_name, group_func,group_data) where:
+
+ group_name
+ is an identifier used only to be able to override and manipulate
+ groups. Default groups are ``user`` (recipients linked to an employee user),
+ ``portal`` (recipients linked to a portal user) and ``customer`` (recipients not
+ linked to any user). An example of override use would be to add a group
+ linked to a res.groups like Hr Officers to set specific action buttons to
+ them.
+ group_func
+ is a function pointer taking a partner record as parameter. This
+ method will be applied on recipients to know whether they belong to a given
+ group or not. Only first matching group is kept. Evaluation order is the
+ list order.
+ group_data
+ is a dict containing parameters for the notification email with the following
+ possible keys - values:
+
+ - has_button_access
+ whether to display Access <Document> in email. True by default for
+ new groups, False for portal / customer.
+ - button_access
+ dict with url and title of the button
+ - has_button_follow
+ whether to display Follow in email (if recipient is not currently
+ following the thread). True by default for new groups, False for
+ portal / customer.
+ - button_follow
+ dict with url adn title of the button
+ - has_button_unfollow
+ whether to display Unfollow in email (if recipient is currently following the thread).
+ True by default for new groups, False for portal / customer.
+ - button_unfollow
+ dict with url and title of the button
+ - actions
+ list of action buttons to display in the notification email.
+ Each action is a dict containing url and title of the button.
+
+ :returns: a subtype's full external id or False if no subtype is triggered
+
+
+The urls in the actions list can be generated automatically by calling the
+``_notification_link_helper()`` function:
+
+
+.. method:: _notification_link_helper(self, link_type, **kwargs)
+
+ Generate a link for the given type on the current record (or on a specific
+ record if the kwargs ``model`` and ``res_id`` are set).
+
+ :param str link_type: link type to be generated; can be any of these values:
+
+ ``view``
+ link to form view of the record
+ ``assign``
+ assign the logged user to the ``user_id`` field of
+ the record (if it exists)
+ ``follow``
+ self-explanatory
+ ``unfollow``
+ self-explanatory
+ ``workflow``
+ trigger a workflow signal; the signal's name should be
+ provided as the kwarg ``signal``
+ ``method``
+ call a method on the record; the method's name should be
+ provided as the kwarg ``method``
+ ``new``
+ open an empty form view for a new record; you can specify
+ a specific action by providing its id (database id or fully resolved
+ external id) in the kwarg ``action_id``
+
+ :returns: link of the type selected for the record
+ :rtype: str
+
+.. admonition:: Example
+
+ Let's add a custom button to the Business Trip state change notification;
+ this button will reset the state to Draft and will be only visible to a member
+ of the (imaginary) group Travel Manager (``business.group_trip_manager``)
+
+ .. code-block:: python
+
+ class BusinessTrip(models.Model):
+ _name = 'business.trip'
+ _inherit = ['mail.thread', 'mail.alias.mixin']
+ _description = 'Business Trip'
+
+ # Pevious code goes here
+
+ def action_cancel(self):
+ self.write({'state': 'draft'})
+
+ def _notification_recipients(self, message, groups):
+ """ Handle Trip Manager recipients that can cancel the trip at the last
+ minute and kill all the fun. """
+ groups = super(BusinessTrip, self)._notification_recipients(message, groups)
+
+ self.ensure_one()
+ if self.state == 'confirmed':
+ app_action = self._notification_link_helper('method',
+ method='action_cancel')
+ trip_actions = [{'url': app_action, 'title': _('Cancel')}]
+
+ new_group = (
+ 'group_trip_manager',
+ lambda partner: bool(partner.user_ids) and
+ any(user.has_group('business.group_trip_manager')
+ for user in partner.user_ids),
+ {
+ 'actions': trip_actions,
+ })
+
+ return [new_group] + groups
+
+
+ Note that that I could have defined my evaluation function outside of this
+ method and define a global function to do it instead of a lambda, but for
+ the sake of being more brief and less verbose in these documentation files
+ that can sometimes be boring, I choose the former instead of the latter.
+
+Overriding defaults
+'''''''''''''''''''
+
+There are several ways you can customize the behaviour of ``mail.thread`` models,
+including (but not limited to):
+
+``_mail_post_access`` - :class:`~openerp.models.Model` attribute
+ the required access rights to be able to post a message on the model; by
+ default a ``write`` access is needed, can be set to ``read`` as well
+
+Context keys:
+ These context keys can be used to somewhat control ``mail.thread`` features
+ like auto-subscription or field tracking during calls to ``create()`` or
+ ``write()`` (or any other method where it may be useful).
+
+ - ``mail_create_nosubscribe``: at create or message_post, do not subscribe
+ the current user to the record thread
+ - ``mail_create_nolog``: at create, do not log the automatic '<Document>
+ created' message
+ - ``mail_notrack``: at create and write, do not perform the value tracking
+ creating messages
+ - ``tracking_disable``: at create and write, perform no MailThread features
+ (auto subscription, tracking, post, ...)
+ - ``mail_auto_delete``: auto delete mail notifications; True by default
+ - ``mail_notify_force_send``: if less than 50 email notifications to send,
+ send them directly instead of using the queue; True by default
+ - ``mail_notify_user_signature``: add the current user signature in
+ email notifications; True by default
+
+
+.. _reference/mixins/mail/alias:
+
+Mail alias
+----------
+
+Aliases are configurable email addresses that are linked to a specific record
+(which usually inherits the ``mail.alias.mixin`` model) that will create new records when
+contacted via e-mail. They are an easy way to make your system accessible from
+the outside, allowing users or customers to quickly create records in your
+database without needing to connect to Odoo directly.
+
+Aliases vs. Incoming Mail Gateway
+'''''''''''''''''''''''''''''''''
+
+Some people use the Incoming Mail Gateway for this same purpose. You still need
+a correctly configured mail gateway to use aliases, however a single
+catchall domain will be sufficient since all routing will be done inside Odoo.
+Aliases have several advantages over Mail Gateways:
+
+* Easier to configure
+ * A single incoming gateway can be used by many aliases; this avoids having
+ to configure multiple emails on your domain name (all configuration is done
+ inside Odoo)
+ * No need for System access rights to configure aliases
+* More coherent
+ * Configurable on the related record, not in a Settings submenu
+* Easier to override server-side
+ * Mixin model is built to be extended from the start, allowing you to
+ extract useful data from incoming e-mails more easily than with a mail
+ gateway.
+
+
+Alias support integration
+'''''''''''''''''''''''''
+
+Aliases are usually configured on a parent model which will then create specific
+record when contacted by e-mail. For example, Project have aliases to create tasks
+or issues, Sales Team have aliases to generate Leads.
+
+.. note:: The model that will be created by the alias **must** inherit the
+ ``mail_thread`` model.
+
+Alias support is added by inheriting ``mail.alias.mixin``; this mixin will
+generate a new ``mail.alias`` record for each record of the parent class that
+gets created (for example, every ``project.project`` record having its ``mail.alias``
+record initialized on creation).
+
+.. note:: Aliases can also be created manually and supported by a simple
+ :class:`~openerp.fields.Many2one` field. This guide assumes you wish a
+ more complete integration with automatic creation of the alias, record-specific
+ default values, etc.
+
+Unlike ``mail.thread`` inheritance, the ``mail.alias.mixin`` **requires** some
+specific overrides to work correctly. These overrides will specify the values
+of the created alias, like the kind of record it must create and possibly
+some default values these records may have depending on the parent object:
+
+.. method:: get_alias_model_name(vals)
+
+ Return the model name for the alias. Incoming emails that are not
+ replies to existing records will cause the creation of a new record
+ of this alias model. The value may depend on ``vals``, the dict of
+ values passed to ``create`` when a record of this model is created.
+
+ :param vals dict: values of the newly created record that will holding
+ the alias
+ :return: model name
+ :rtype: str
+
+.. method:: get_alias_values()
+
+ Return values to create an alias, or to write on the alias after its
+ creation. While not completely mandatory, it is usually required to make
+ sure that newly created records will be linked to the alias' parent (i.e.
+ tasks getting created in the right project) by setting a dictionary of
+ default values in the alias' ``alias_defaults`` field.
+
+ :return: dictionnary of values that will be written to the new alias
+ :rtype: dict
+
+The ``get_alias_values()`` override is particularly interesting as it allows you
+to modify the behaviour of your aliases easily. Among the fields that can be set
+on the alias, the following are of particular interest:
+
+``alias_name`` - :class:`~openerp.fields.Char`
+ name of the email alias, e.g. 'jobs' if you want to catch emails for
+ <jobs@example.odoo.com>
+``alias_user_id`` - :class:`~openerp.fields.Many2one` (``res.users``)
+ owner of records created upon receiving emails on this alias;
+ if this field is not set the system will attempt to find the right owner
+ based on the sender (From) address, or will use the Administrator account
+ if no system user is found for that address
+``alias_defaults`` - :class:`~openerp.fields.Text`
+ Python dictionary that will be evaluated to provide
+ default values when creating new records for this alias
+``alias_force_thread_id`` - :class:`~openerp.fields.Integer`
+ optional ID of a thread (record) to which all incoming messages will be
+ attached, even if they did not reply to it; if set, this will disable the
+ creation of new records completely
+``alias_contact`` - :class:`~openerp.fields.Selection`
+ Policy to post a message on the document using the mailgateway
+
+ - *everyone*: everyone can post
+ - *partners*: only authenticated partners
+ - *followers*: only followers of the related document or members of following channels
+
+Note that aliases make use of :ref:`delegation inheritance <reference/orm/inheritance>`,
+which means that while the alias is stored in another table, you have
+access to all these fields directly from your parent object. This allows
+you to make your alias easily configurable from the record's form view.
+
+.. admonition:: Example
+
+ Let's add aliases on our business trip class to create expenses on the fly via
+ e-mail.
+
+ .. code-block:: python
+
+ class BusinessTrip(models.Model):
+ _name = 'business.trip'
+ _inherit = ['mail.thread', 'mail.alias.mixin']
+ _description = 'Business Trip'
+
+ name = fields.Char(track_visibility='onchange')
+ partner_id = fields.Many2one('res.partner', 'Responsible',
+ track_visibility='onchange')
+ guest_ids = fields.Many2many('res.partner', 'Participants')
+ state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')],
+ track_visibility='onchange')
+ expense_ids = fields.One2many('business.expense', 'trip_id', 'Expenses')
+ alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict",
+ required=True)
+
+ def get_alias_model_name(self, vals):
+ """ Specify the model that will get created when the alias receives a message """
+ return 'business.expense'
+
+ def get_alias_values(self):
+ """ Specify some default values that will be set in the alias at its creation """
+ values = super(BusinessTrip, self).get_alias_values()
+ # alias_defaults holds a dictionnary that will be written
+ # to all records created by this alias
+ #
+ # in this case, we want all expense records sent to a trip alias
+ # to be linked to the corresponding business trip
+ values['alias_defaults'] = {'trip_id': self.id}
+ # we only want followers of the trip to be able to post expenses
+ # by default
+ values['alias_contact'] = 'followers'
+ return values
+
+ class BusinessExpense(models.Model):
+ _name = 'business.expense'
+ _inherit = ['mail.thread']
+ _description = 'Business Expense'
+
+ name = fields.Char()
+ amount = fields.Float('Amount')
+ trip_id = fields.Many2one('business.trip', 'Business Trip')
+ partner_id = fields.Many2one('res.partner', 'Created by')
+
+ We would like our alias to be easily configurable from the form view of our
+ business trips, so let's add the following to our form view:
+
+ .. code-block:: xml
+
+ <page string="Emails">
+ <group name="group_alias">
+ <label for="alias_name" string="Email Alias"/>
+ <div name="alias_def">
+ <!-- display a link while in view mode and a configurable field
+ while in edit mode -->
+ <field name="alias_id" class="oe_read_only oe_inline"
+ string="Email Alias" required="0"/>
+ <div class="oe_edit_only oe_inline" name="edit_alias"
+ style="display: inline;" >
+ <field name="alias_name" class="oe_inline"/>
+ @
+ <field name="alias_domain" class="oe_inline" readonly="1"/>
+ </div>
+ </div>
+ <field name="alias_contact" class="oe_inline"
+ string="Accept Emails From"/>
+ </group>
+ </page>
+
+ Now we can change the alias address directly from the form view and change
+ who can send e-mails to the alias.
+
+ We can then override ``message_new()`` on our expense model to fetch the values
+ from our email when the expense will be created:
+
+ .. code-block:: python
+
+ class BusinessExpense(models.Model):
+ # Previous code goes here
+ # ...
+
+ def message_new(self, msg, custom_values=None):
+ """ Override to set values according to the email.
+
+ In this simple example, we simply use the email title as the name
+ of the expense, try to find a partner with this email address and
+ do a regex match to find the amount of the expense."""
+ name = msg_dict.get('subject', 'New Expense')
+ # Match the last occurence of a float in the string
+ # Example: '50.3 bar 34.5' becomes '34.5'. This is potentially the price
+ # to encode on the expense. If not, take 1.0 instead
+ amount_pattern = '(\d+(\.\d*)?|\.\d+)'
+ expense_price = re.findall(amount_pattern, name)
+ price = expense_price and float(expense_price[-1][0]) or 1.0
+ # find the partner by looking for it's email
+ partner = self.env['res.partner'].search([('email', 'ilike', email_address)],
+ limit=1)
+ defaults = {
+ 'name': name,
+ 'amount': price,
+ 'partner_id': partner.id
+ }
+ defaults.update(custom_values or {})
+ res = super(BusinessExpense, self).message_new(msg, custom_values=defaults)
+ return res
+
+
+.. _reference/mixins/website:
+
+Website features
+================
+
+.. _reference/mixins/website/utm:
+
+Visitor tracking
+----------------
+
+The ``utm.mixin`` class can be used to track online marketing/communication
+campaigns through arguments in links to specified resources. The mixin adds
+3 fields to your model:
+
+* ``campaign_id``: :class:`~openerp.fields.Many2one` field to a ``utm.campaign``
+ object (i.e. Christmas_Special, Fall_Collection, etc.)
+* ``source_id``: :class:`~openerp.fields.Many2one` field to a ``utm.source``
+ object (i.e. Search Engine, mailing list, etc.)
+* ``medium_id``: :class:`~openerp.fields.Many2one` field to a ``utm.medium``
+ object (i.e. Snail Mail, e-Mail, social network update, etc.)
+
+These models have a single field ``name`` (i.e. they are simply there to
+distinguish campaigns but don't have any specific behaviour).
+
+Once a customer visits your website with these parameters set in the url
+(i.e. http://www.odoo.com/?campaign_id=mixin_talk&source_id=www.odoo.com&medium_id=website),
+three cookies are set in the visitor's website for these parameters.
+Once a object that inherits the utm.mixin is created from the website (i.e. lead
+form, job application, etc.), the utm.mixin code kicks in and fetches the values
+from the cookies to set them in the new record. Once this is done, you can then
+use the campaign/source/medium fields as any other field when defining reports
+and views (group by, etc.).
+
+To extend this behaviour, simply add a relational field to a simple model (the
+model should support the *quick create* (i.e. call to ``create()`` with a single
+``name`` value) and extend the function ``tracking_fields()``:
+
+.. code-block:: python
+
+ class UtmMyTrack(models.Model):
+ _name = 'my_module.my_track'
+ _description = 'My Tracking Object'
+
+ name = fields.Char(string='Name', required=True)
+
+
+ class MyModel(models.Models):
+ _name = 'my_module.my_model'
+ _inherit = ['utm.mixin']
+ _description = 'My Tracked Object'
+
+ my_field = fields.Many2one('my_module.my_track', 'My Field')
+
+ @api.model
+ def tracking_fields(self):
+ result = super(MyModel, self).tracking_fields()
+ result.append([
+ # ("URL_PARAMETER", "FIELD_NAME_MIXIN", "NAME_IN_COOKIES")
+ ('my_field', 'my_field', 'odoo_utm_my_field')
+ ])
+ return result
+
+This will tell the system to create a cookie named *odoo_utm_my_field* with the
+value found in the url parameter ``my_field``; once a new record of this model is
+created by a call from a website form, the generic override of the ``create()``
+method of ``utm.mixin`` will fetch the default values for this field from the
+cookie (and the ``my_module.my_track`` record will be creatwed on the fly if it
+does not exist yet).
+
+You can find concrete examples of integration in the following models:
+
+* ``crm.lead`` in the CRM (*crm*) Application
+* ``hr.applicant`` in the Recruitment Process (*hr_recruitment*) Application
+* ``helpdesk.ticket`` in the Helpdesk (*helpdesk* - Odoo Enterprise only) Application
+
+.. _reference/mixins/website/published:
+
+Website visibility
+------------------
+
+You can quite easily add a website visibility toggle on any of your record. While
+this mixin is quite easy to implement manually, it is the most often-used after
+the ``mail.thread`` inheritance; a testament to its usefulness. The typical use
+case for this mixin is any object that has a frontend-page; being able to control
+the visibility of the page allows you to take your time while editing the page
+and only publish it when you're satisfied.
+
+To include the functionnality, you only need to inherit ``website.published.mixin``:
+
+.. code-block:: python
+
+ class BlogPost(models.Model):
+ _name = "blog.post"
+ _description = "Blog Post"
+ _inherit = ['website.published.mixin']
+
+This mixin adds 2 fields on your model:
+
+* ``website_published``: :class:`~openerp.fields.Boolean` field which represents
+ the status of the publication
+* ``website_url``: :class:`~openerp.fields.Char` field which represents
+ the URL through which the object is accessed
+
+Note that this last field is a computed field and must be implemented for your class:
+
+.. code-block:: python
+
+ def _compute_website_url(self):
+ for blog_post in self:
+ blog_post.website_url = "/blog/%s" % (log_post.blog_id)
+
+Once the mechanism is in place, you just have to adapt your frontend and backend
+views to make it accessible. In the backend, adding a button in the button box is
+usually the way to go:
+
+.. code-block:: xml
+
+ <button class="oe_stat_button" name="website_publish_button"
+ type="object" icon="fa-globe">
+ <field name="website_published" widget="website_button"/>
+ </button>
+
+In the frontend, some security checks are needed to avoid showing 'Edition'
+buttons to website visitors:
+
+.. code-block:: xml
+
+ <div id="website_published_button" class="pull-right"
+ groups="base.group_website_publisher"> <!-- or any other meaningful group -->
+ <t t-call="website.publish_management">
+ <t t-set="object" t-value="blog_post"/>
+ <t t-set="publish_edit" t-value="True"/>
+ <t t-set="action" t-value="'blog.blog_post_action'"/>
+ </t>
+ </div>
+
+Note that you must pass your object as the variable ``object`` to the template;
+in this example, the ``blog.post`` record was passed as the ``blog_post`` variable
+to the ``qweb`` rendering engine, it is necessary to specify this to the publish
+management template. The ``publish_edit`` variable allow the frontend
+button to link to the backend (allowing you to switch from frontend to backend
+and vice-versa easily); if set, you must specify the full external id of the action
+you want to call in the backend in the ``action`` variable (note that a Form View
+must exist for the model).
+
+The action ``website_publish_button`` is defined in the mixin and adapts its
+behaviour to your object: if the class has a valid ``website_url`` compute function,
+the user is redirected to the frontend when he clicks on the button; the user
+can then publish the page directly from the frontend. This ensures
+that no online publication can happen by accident. If there is not compute function,
+the boolean ``website_published`` is simply triggered.
+
+.. _reference/mixins/website/seo:
+
+Website metadata
+----------------
+
+This simple mixin simply allows you to easily inject metadata in your frontend
+pages.
+
+.. code-block:: python
+
+ class BlogPost(models.Model):
+ _name = "blog.post"
+ _description = "Blog Post"
+ _inherit = ['website.seo.metadata', 'website.published.mixin']
+
+This mixin adds 3 fields on your model:
+
+* ``website_meta_title``: :class:`~openerp.fields.Char` field that allow you to set
+ an additional title to your page
+* ``website_meta_description``: :class:`~openerp.fields.Char` field that contains a
+ short description of the page (sometimes used in search engines results)
+* ``website_meta_keywords``: :class:`~openerp.fields.Char` field that contains some
+ keywords to help your page to be classified more precisely by search engines; the
+ "Promote" tool will help you select lexically-related keywords easily
+
+These fields are editable in the frontend using the "Promote" tool from the Editor
+toolbar. Setting these fields can help search engines to better index your pages.
+Note that search engines do not base their results only on these metadata; the
+best SEO practice should still be to get referenced by reliable sources.
+
+.. _reference/mixins/misc:
+
+Others
+======
+
+.. _reference/mixins/misc/rating:
+
+Customer Rating
+---------------
+
+The rating mixin allows sending email to ask for customer rating, automatic
+transitioning in a kanban processes and aggregating statistics on your ratings.
+
+Adding rating on your model
+'''''''''''''''''''''''''''
+
+To add rating support, simply inherit the ``rating.mixin`` model:
+
+.. code-block:: python
+
+ class MyModel(models.Models):
+ _name = 'my_module.my_model'
+ _inherit = ['rating.mixin', 'mail.thread']
+
+ user_id = fields.Many2one('res.users', 'Responsible')
+ partner_id = fields.Many2one('res.partner', 'Customer')
+
+The behaviour of the mixin adapts to your model:
+
+* The ``rating.rating`` record will be linked to the ``partner_id`` field of your
+ model (if the field is present).
+
+ - this behaviour can be overriden with the function ``rating_get_partner_id()``
+ if you use another field than ``partner_id``
+
+* The ``rating.rating`` record will be linked to the partner of the ``user_id``
+ field of your model (if the field is present) (i.e. the partner who is rated)
+
+ - this behaviour can be overriden with the function ``rating_get_rated_partner_id()``
+ if you use another field than ``user_id`` (note that the function must return a
+ ``res.partner``, for ``user_id`` the system automatically fetches the partner
+ of the user)
+
+* The chatter history will display the rating event (if your model inherits from
+ ``mail.thread``)
+
+Send rating requests by e-mail
+''''''''''''''''''''''''''''''
+
+If you wish to send emails to request a rating, simply generate an e-mail with
+links to the rating object. A very basic email template could look like this:
+
+.. code-block:: xml
+
+ <record id="rating_my_model_email_template" model="mail.template">
+ <field name="name">My Model: Rating Request</field>
+ <field name="email_from">${object.rating_get_rated_partner_id().email or '' | safe}</field>
+ <field name="subject">Service Rating Request</field>
+ <field name="model_id" ref="my_module.model_my_model"/>
+ <field name="partner_to" >${object.rating_get_partner_id().id}</field>
+ <field name="auto_delete" eval="True"/>
+ <field name="body_html"><![CDATA[
+ % set access_token = object.rating_get_access_token()
+ <p>Hi,</p>
+ <p>How satsified are you?</p>
+ <ul>
+ <li><a href="/rating/${access_token}/10">Satisfied</a></li>
+ <li><a href="/rating/${access_token}/5">Not satisfied</a></li>
+ <li><a href="/rating/${access_token}/1">Very unsatisfied</a></li>
+ </ul>
+ ]]></field>
+ </record>
+
+Your customer will then receive an e-mail with links to a simple webpage allowing
+them to provide a feedback on their interaction with your users (including a free-text
+feedback message).
+
+You can then quite easily integrate your ratings with your form view by defining
+an action for the ratings:
+
+.. code-block:: xml
+
+ <record id="rating_rating_action_my_model" model="ir.actions.act_window">
+ <field name="name">Customer Ratings</field>
+ <field name="res_model">rating.rating</field>
+ <field name="view_mode">kanban,pivot,graph</field>
+ <field name="domain">[('res_model', '=', 'my_module.my_model'), ('res_id', '=', active_id), ('consumed', '=', True)]</field>
+ </record>
+
+ <record id="my_module_my_model_view_form_inherit_rating" model="ir.ui.view">
+ <field name="name">my_module.my_model.view.form.inherit.rating</field>
+ <field name="model">my_module.my_model</field>
+ <field name="inherit_id" ref="my_module.my_model_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@name='button_box']" position="inside">
+ <button name="%(rating_rating_action_my_model)d" type="action"
+ class="oe_stat_button" icon="fa-smile-o">
+ <field name="rating_count" string="Rating" widget="statinfo"/>
+ </button>
+ </xpath>
+ </field>
+ </record>
+
+Note that there are default views (kanban,pivot,graph) for ratings which allow
+you a quick bird's eye view of your customer ratings.
+
+You can find concrete examples of integration in the following models:
+
+* ``project.task`` in the Project (*rating_project*) Application
+* ``helpdesk.ticket`` in the Helpdesk (*helpdesk* - Odoo Enterprise only) Application
+