Commits

Raimon Esteve (Zikzakmedia) committed d93cae2

Comments (0)

Files changed (8)

+include INSTALL
+include README
+include TODO
+include COPYRIGHT
+include CHANGELOG
+include LICENSE
+include *.xml
+include *.odt
+include locale/*.po
+include doc/*
+include icons/*
+# -*- coding: UTF-8 -*-
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+"Electronic Mail"
+
+from electronic_mail import *
+
+# -*- coding: UTF-8 -*-
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+{
+    'name': 'Electronic Mail',
+    'description': '''Electronic mail storage''',
+    'version': '2.4.0.2',
+    'author': 'Openlabs Technologies & Consulting (P) LTD',
+    'email': 'info@openlabs.co.in',
+    'website': 'http://openlabs.co.in/',
+    'depends': [
+        'ir',
+        'res',
+    ],
+    'xml': [
+        'electronic_mail.xml',
+    ],
+    'translation': [
+    ],
+}
+#This file is part of Tryton.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+"Electronic Mail"
+from __future__ import with_statement
+
+import os
+import base64
+from sys import getsizeof
+try:
+    import hashlib
+except ImportError:
+    hashlib = None
+    import md5
+from datetime import datetime
+from time import mktime
+from email.utils import parsedate
+
+from trytond.model import ModelView, ModelSQL, fields
+from trytond.config import CONFIG
+from trytond.transaction import Transaction
+from trytond.pool import Pool
+
+
+class Mailbox(ModelSQL, ModelView):
+    "Mailbox"
+    _name = "electronic_mail.mailbox"
+    _description = __doc__
+
+    name = fields.Char('Name', required=True)
+    user = fields.Many2One('res.user', 'Owner')
+    parents = fields.Many2Many(
+             'electronic_mail.mailbox-mailbox',
+             'parent', 'child' ,'Parents')
+    subscribed = fields.Boolean('Subscribed')
+    read_users = fields.Many2Many('electronic_mail.mailbox-read-res.user',
+            'mailbox', 'user', 'Read Users')
+    write_users = fields.Many2Many('electronic_mail.mailbox-write-res.user',
+            'mailbox', 'user', 'Write Users')
+
+Mailbox()
+
+
+class MailboxParent(ModelSQL):
+    'Mailbox - parent - Mailbox'
+    _description = __doc__
+    _name = 'electronic_mail.mailbox-mailbox'
+
+    parent = fields.Many2One('electronic_mail.mailbox', 'Parent',
+            ondelete='CASCADE', required=True, select=1)
+    child = fields.Many2One('electronic_mail.mailbox', 'Child',
+            ondelete='CASCADE', required=True, select=1)
+
+MailboxParent()
+
+
+class ReadUser(ModelSQL):
+    'Electronic Mail - read - User'
+    _description = __doc__
+    _name = 'electronic_mail.mailbox-read-res.user'
+
+    mailbox = fields.Many2One('electronic_mail.mailbox', 'Mailbox',
+            ondelete='CASCADE', required=True, select=1)
+    user = fields.Many2One('res.user', 'User', ondelete='CASCADE',
+            required=True, select=1)
+
+ReadUser()
+
+
+class WriteUser(ModelSQL):
+    'Mailbox - write - User'
+    _description = __doc__
+    _name = 'electronic_mail.mailbox-write-res.user'
+
+    mailbox = fields.Many2One('electronic_mail.mailbox', 'mailbox',
+            ondelete='CASCADE', required=True, select=1)
+    user = fields.Many2One('res.user', 'User', ondelete='CASCADE',
+            required=True, select=1)
+
+WriteUser()
+
+
+class ElectronicMail(ModelSQL, ModelView):
+    "E-mail"
+    _name = 'electronic_mail'
+    _description = __doc__
+
+    mailbox = fields.Many2One(
+        'electronic_mail.mailbox', 'Mailbox', required=True)
+    from_ = fields.Char('From')
+    sender = fields.Char('Sender')
+    to = fields.Char('To')
+    cc = fields.Char('CC')
+    bcc = fields.Char('BCC')
+    subject = fields.Char('Subject')
+    date = fields.DateTime('Date')
+    message_id = fields.Char('Message-ID', help='Unique Message Identifier')
+    in_reply_to = fields.Char('In-Reply-To')
+    headers = fields.One2Many(
+        'electronic_mail.header', 'electronic_mail', 'Headers')
+    digest = fields.Char('MD5 Digest', size=32)
+    collision = fields.Integer('Collision')
+    email = fields.Function(fields.Binary('Email'), 'get_email', 'set_email')
+    flag_seen = fields.Boolean('Seen')
+    flag_answered = fields.Boolean('Answered')
+    flag_flagged = fields.Boolean('Flagged')
+    flag_draft = fields.Boolean('Draft')
+    flag_recent = fields.Boolean('Recent')
+    size = fields.Integer('Size')
+    mailbox_owner = fields.Function(
+        fields.Many2One('res.user', 'Owner'),
+        'get_mailbox_owner', searcher='search_mailbox_owner')
+    mailbox_read_users = fields.Function(
+        fields.One2Many('res.user', None, 'Read Users'),
+        'get_mailbox_users', searcher='search_mailbox_users')
+    mailbox_write_users = fields.Function(
+        fields.One2Many('res.user', None, 'Write Users'),
+        'get_mailbox_users', searcher='search_mailbox_users')
+
+    def default_collision(self):
+        return 0
+
+    def default_flag_seen(self):
+        return False
+
+    def default_flag_answered(self):
+        return False
+
+    def default_flag_flagged(self):
+        return False
+
+    def default_flag_recent(self):
+        return False
+
+    def get_mailbox_owner(self, ids, name):
+        "Returns owner of mailbox"
+        mails = self.browse(ids)
+        return dict([(mail.id, mail.mailbox.user.id) for mail in mails])
+
+    def get_mailbox_users(self, ids, name):
+        assert name in ('mailbox_read_users', 'mailbox_write_users')
+        res = {}
+        for mail in self.browse(ids):
+            if name == 'mailbox_read_users':
+                res[mail.id] = [x.id for x in mail.mailbox['read_users']]
+            else:
+                res[mail.id] = [x.id for x in mail.mailbox['write_users']]
+        return res
+
+    def search_mailbox_owner(self, name, clause):
+        return [('mailbox.user',) + clause[1:]]
+
+    def search_mailbox_users(self, name, clause):
+        return [('mailbox.' + name[8:],) + clause[1:]]
+
+    def _get_email(self, electronic_mail):
+        """
+        Returns the email object from reading the FS
+        :param electronic_mail: Browse Record of the mail
+        """
+        db_name = Transaction().cursor.dbname
+        value = u''
+        if electronic_mail.digest:
+            filename = electronic_mail.digest
+            if electronic_mail.collision:
+                filename = filename + '-' + str(electronic_mail.collision)
+            filename = os.path.join(
+                CONFIG['data_path'], db_name, 
+                'email', filename[0:2], filename)
+            try:
+                with open(filename, 'r') as file_p:
+                    value =  file_p.read()
+            except IOError:
+                pass
+        return value
+
+    def get_email(self, ids, name):
+        """Fetches email from the data_path as email object
+        """
+        result = { }
+        for electronic_mail in self.browse(ids):
+            result[electronic_mail.id] = base64.encodestring(
+                self._get_email(electronic_mail)
+                ) or False
+        return result
+
+    def set_email(self, ids, name, data):
+        """Saves an email to the data path
+
+        :param data: Email as string
+        """
+        if data is False or data is None:
+            return
+        db_name = Transaction().cursor.dbname
+        # Prepare Directory <DATA PATH>/<DB NAME>/email
+        directory = os.path.join(CONFIG['data_path'], db_name)
+        if not os.path.isdir(directory):
+            os.makedirs(directory, 0770)
+        digest = self.make_digest(data)
+        directory = os.path.join(directory, 'email', digest[0:2])
+        if not os.path.isdir(directory):
+            os.makedirs(directory, 0770)
+        # Filename <DIRECTORY>/<DIGEST>
+        filename = os.path.join(directory, digest)
+        collision = 0
+
+        if not os.path.isfile(filename):
+            # File doesnt exist already
+            with open(filename, 'w') as file_p:
+                file_p.write(data)
+        else:
+            # File already exists, may be its the same email data
+            # or maybe different. 
+
+            # Case 1: If different: we have to write file with updated
+            # Collission index
+
+            # Case 2: Same file: Leave it as such
+            with open(filename, 'r') as file_p:
+                data2 = file_p.read()
+            if data != data2:
+                cursor = Transaction().cursor
+                cursor.execute(
+                    'SELECT DISTINCT(collision) FROM electronic_mail '
+                    'WHERE digest = %s AND collision !=0 '
+                    'ORDER BY collision', (digest,))
+                collision2 = 0
+                for row in cursor.fetchall():
+                    collision2 = row[0]
+                    filename = os.path.join(
+                        directory, digest + '-' + str(collision2))
+                    if os.path.isfile(filename):
+                        with open(filename, 'r') as file_p:
+                            data2 = file_p.read()
+                        if data == data2:
+                            collision = collision2
+                            break
+                if collision == 0:
+                    collision = collision2 + 1
+                    filename = os.path.join(
+                        directory, digest + '-' + str(collision))
+                    with open(filename, 'w') as file_p:
+                        file_p.write(data)
+        self.write(ids, {'digest': digest, 'collision': collision})
+
+    def make_digest(self, data):
+        """
+        Returns a digest from the mail
+
+        :param data: Data String
+        :return: Digest
+        """
+        if hashlib:
+            digest = hashlib.md5(data).hexdigest()
+        else:
+            digest = md5.new(data).hexdigest()
+        return digest
+
+    def create_from_email(self, mail, mailbox):
+        """
+        Creates a mail record from a given mail
+        :param mail: email object
+        :param mailbox: ID of the mailbox
+        """
+        header_obj = Pool().get('electronic_mail.header')
+        email_date = mail.get('date') and datetime.fromtimestamp(
+                mktime(parsedate(mail.get('date'))))
+        values = {
+            'mailbox': mailbox,
+            'from_': mail.get('from'),
+            'sender': mail.get('sender'),
+            'to': mail.get('to'),
+            'cc': mail.get('cc'),
+            'bcc': mail.get('bcc'),
+            'subject': mail.get('subject'),
+            'date': email_date,
+            'message_id': mail.get('message-id'),
+            'in_reply_to': mail.get('in-reply-to'),
+            'email': mail.as_string(),
+            'size': getsizeof(mail.as_string()),
+            }
+        create_id = self.create(values)
+        header_obj.create_from_email(mail, create_id)
+        return create_id
+
+ElectronicMail()
+
+
+class Header(ModelSQL, ModelView):
+    "Header fields"
+    _name = 'electronic_mail.header'
+    _description = __doc__
+
+    name = fields.Char('Name', help='Name of Header Field')
+    value = fields.Char('Value', help='Value of Header Field')
+    electronic_mail = fields.Many2One('electronic_mail', 'e-mail')
+
+    def create_from_email(self, mail, mail_id):
+        """
+        :param mail: Email object
+        :param mail_id: ID of the email from electronic_mail
+        """
+        for name, value in mail.items():
+            values = {
+                'electronic_mail':mail_id,
+                'name':name,
+                'value':value,
+                }
+            self.create(values)
+        return True
+
+Header()
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tryton>
+  <data>
+    <menuitem name="Email Management" sequence="10" 
+      id="menu_email_management" parent="res.menu_res"/>
+
+    <record model="res.group" id="group_email_admin">
+      <field name="name">Electronic Mail Administrator</field>
+    </record>
+    <record model="res.group" id="group_email_user">
+      <field name="name">Electronic Mail User</field>
+    </record>
+    <record model="res.user-res.group" id="user_admin_group_email_admin">
+      <field name="user" ref="res.user_admin"/>
+      <field name="group" ref="group_email_admin"/>
+    </record>
+    <record model="res.user-res.group" id="user_admin_group_email_user">
+      <field name="user" ref="res.user_admin"/>
+      <field name="group" ref="group_email_user"/>
+    </record>
+
+    <record model="ir.ui.view" id="mailbox_view_tree">
+      <field name="model">electronic_mail.mailbox</field>
+      <field name="type">tree</field>
+      <field name="arch" type="xml">
+        <![CDATA[
+	        <tree string="Mailboxes">
+	        <field name="name"/>
+	        <field name="user"/>
+	        </tree>
+        ]]>
+      </field>
+    </record>
+    <record model="ir.ui.view" id="mailbox_view_form">
+      <field name="model">electronic_mail.mailbox</field>
+      <field name="type">form</field>
+      <field name="arch" type="xml">
+      <![CDATA[
+      <form string="Mailbox">
+        <group colspan="4" id="wrapper">
+          <label name="name"/>
+          <field name="name"/>
+          <label name="user"/>
+          <field name="user"/>
+          <label name="subscribed"/>
+          <field name="subscribed"/>
+        </group>
+        <notebook colspan="4">
+          <page string="Parents" id="parents">
+            <field name="parents"/>
+          </page>
+          <page string="Permissions" id="permissions">
+            <separator name="read_users" colspan="4"/>
+            <field name="read_users"/>
+            <separator name="write_users" colspan="4"/>
+            <field name="write_users"/>
+          </page>
+        </notebook>
+      </form>
+      ]]>
+      </field>
+    </record>
+    <record model="ir.action.act_window" id="act_mailbox_form">
+      <field name="name">Mailboxes</field>
+      <field name="res_model">electronic_mail.mailbox</field>
+    </record>
+    <record model="ir.action.act_window.view" id="act_mailbox_form_view1">
+      <field name="sequence" eval="10"/>
+      <field name="view" ref="mailbox_view_tree"/>
+      <field name="act_window" ref="act_mailbox_form"/>
+    </record>
+    <record model="ir.action.act_window.view" id="act_mailbox_form_view2">
+      <field name="sequence" eval="20"/>
+      <field name="view" ref="mailbox_view_form"/>
+      <field name="act_window" ref="act_mailbox_form"/>
+    </record>
+    <menuitem id="menu_mailbox" action="act_mailbox_form" 
+      parent="menu_email_management"/>
+
+    <record model="ir.ui.view" id="headers_view_tree">
+      <field name="model">electronic_mail.header</field>
+      <field name="type">tree</field>
+      <field name="arch" type="xml">
+      <![CDATA[
+        <tree string="Headers">
+          <field name="name"/>
+          <field name="value"/>
+        </tree>
+	    ]]>
+      </field>
+    </record>
+
+    <record model="ir.ui.view" id="mail_view_tree">
+      <field name="model">electronic_mail</field>
+      <field name="type">tree</field>
+      <field name="arch" type="xml">
+      <![CDATA[
+      <tree string="Emails">
+        <field name="flag_seen"/>
+        <field name="flag_flagged"/>
+        <field name="flag_answered"/>
+        <field name="from_"/>
+        <field name="subject"/>
+        <field name="date"/>
+      </tree>
+	    ]]>
+      </field>
+    </record>
+    <record model="ir.ui.view" id="mail_view_form">
+      <field name="model">electronic_mail</field>
+      <field name="type">form</field>
+      <field name="arch" type="xml">
+      <![CDATA[
+      <form string="Email">
+        <group colspan="4" id="master_fields">
+          <label name="from_"/>
+          <field name="from_"/>
+          <label name="date"/>
+          <field name="date"/>
+          <label name="to"/>
+          <field name="to"/>
+          <label name="in_reply_to"/>
+          <field name="in_reply_to"/>
+          <label name="cc"/>
+          <field name="cc"/>
+          <label name="bcc"/>
+          <field name="bcc"/>
+          <label name="subject"/>
+          <field name="subject"/>
+          <label name="mailbox"/>
+          <field name="mailbox"/>
+        </group>
+        <group colspan="4" col="10" id="flags_area">
+          <label name="flag_seen"/>
+          <field name="flag_seen"/>
+          <label name="flag_flagged"/>
+          <field name="flag_flagged"/>
+          <label name="flag_answered"/>
+          <field name="flag_answered"/>
+          <label name="flag_draft"/>
+          <field name="flag_draft"/>
+          <label name="flag_recent"/>
+          <field name="flag_recent"/>
+        </group>
+        <separator name="email" colspan="4"/>
+        <field name="email"/>
+      </form>
+      ]]>
+      </field>
+    </record>
+    <record model="ir.action.act_window" id="act_mail_form">
+      <field name="name">Emails</field>
+      <field name="res_model">electronic_mail</field>
+    </record>
+    <record model="ir.action.act_window.view" id="act_mail_form_view1">
+      <field name="sequence" eval="10"/>
+      <field name="view" ref="mail_view_tree"/>
+      <field name="act_window" ref="act_mail_form"/>
+    </record>
+    <record model="ir.action.act_window.view" id="act_mail_form_view2">
+      <field name="sequence" eval="20"/>
+      <field name="view" ref="mail_view_form"/>
+      <field name="act_window" ref="act_mail_form"/>
+    </record>
+    
+    <menuitem id="menu_mail" action="act_mail_form" parent="menu_email_management"/>
+  
+    <!-- Access Rule Mailbox -->
+    <record model="ir.model.access" id="access_mailbox_admin">
+      <field name="model" search="[('model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="group" ref="group_email_admin"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="True"/>
+      <field name="perm_create" eval="True"/>
+      <field name="perm_delete" eval="True"/>
+    </record>
+    <record model="ir.model.access" id="access_mailbox_user">
+      <field name="model" search="[('model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="group" ref="group_email_user"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="False"/>
+      <field name="perm_create" eval="False"/>
+      <field name="perm_delete" eval="False"/>
+    </record>
+    <!-- Access Rule Mail -->
+    <record model="ir.model.access" id="access_mail_admin">
+      <field name="model" search="[('model', '=', 'electronic_mail')]"/>
+      <field name="group" ref="group_email_admin"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="True"/>
+      <field name="perm_create" eval="True"/>
+      <field name="perm_delete" eval="True"/>
+    </record>
+    <record model="ir.model.access" id="access_mail_user">
+      <field name="model" search="[('model', '=', 'electronic_mail')]"/>
+      <field name="group" ref="group_email_user"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="True"/>
+      <field name="perm_create" eval="True"/>
+      <field name="perm_delete" eval="True"/>
+    </record>
+
+    <!-- Rule to read mailboxes -->
+    <record model="ir.rule.group" id="rule_group_read_mailbox">
+      <field name="model" search="[('model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="global_p" eval="True"/>
+      <field name="default_p" eval="False"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="False"/>
+      <field name="perm_create" eval="False"/>
+      <field name="perm_delete" eval="False"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mailbox_line1">
+      <field name="field" 
+             search="[('name', '=', 'user'), ('model.model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mailbox"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mailbox_line2">
+      <field name="field" 
+        search="[('name', '=', 'read_users'), ('model.model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mailbox"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mailbox_line3">
+      <field name="field" 
+        search="[('name', '=', 'write_users'), ('model.model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mailbox"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mailbox_line4">
+      <field name="field" 
+        search="[('name', '=', 'create_uid'), ('model.model', '=', 'electronic_mail.mailbox')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mailbox"/>
+    </record>
+
+    <!-- Rule to read emails -->
+    <record model="ir.rule.group" id="rule_group_read_mail">
+      <field name="model" search="[('model', '=', 'electronic_mail')]"/>
+      <field name="global_p" eval="True"/>
+      <field name="default_p" eval="False"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="False"/>
+      <field name="perm_create" eval="False"/>
+      <field name="perm_delete" eval="False"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mail_line1">
+      <field name="field" 
+        search="[('name', '=', 'mailbox_owner'), ('model.model', '=', 'electronic_mail')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mail"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mail_line2">
+      <field name="field" 
+        search="[('name', '=', 'mailbox_read_users'), ('model.model', '=', 'electronic_mail')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mail"/>
+    </record>
+    <record model="ir.rule" id="rule_group_read_mail_line3">
+      <field name="field" 
+        search="[('name', '=', 'mailbox_write_users'), ('model.model', '=', 'electronic_mail')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_read_mail"/>
+    </record>
+
+    <!-- Rule to write emails -->
+    <record model="ir.rule.group" id="rule_group_write_mail">
+      <field name="model" search="[('model', '=', 'electronic_mail')]"/>
+      <field name="global_p" eval="True"/>
+      <field name="default_p" eval="False"/>
+      <field name="perm_read" eval="True"/>
+      <field name="perm_write" eval="True"/>
+      <field name="perm_create" eval="True"/>
+      <field name="perm_delete" eval="True"/>
+    </record>
+    <record model="ir.rule" id="rule_group_write_mail_line1">
+      <field name="field" 
+        search="[('name', '=', 'mailbox_owner'), ('model.model', '=', 'electronic_mail')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_write_mail"/>
+    </record>
+    <record model="ir.rule" id="rule_group_write_mailbox_line2">
+      <field name="field" 
+        search="[('name', '=', 'mailbox_write_users'), ('model.model', '=', 'electronic_mail')]"/>
+      <field name="operator">=</field>
+      <field name="operand">User</field>
+      <field name="rule_group" ref="rule_group_write_mail"/>
+    </record>
+  </data>
+</tryton>
+#!/usr/bin/env python
+#This file is part of Tryton.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+
+from setuptools import setup
+import re
+
+info = eval(open('__tryton__.py').read())
+major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+
+requires = []
+for dep in info.get('depends', []):
+    if not re.match(r'(ir|res|workflow|webdav)(\W|$)', dep):
+        requires.append('trytond_%s >= %s.%s, < %s.%s' %
+                (dep, major_version, minor_version, major_version,
+                    minor_version + 1))
+requires.append('trytond >= %s.%s, < %s.%s' %
+        (major_version, minor_version, major_version, minor_version + 1))
+
+setup(name='trytond_electronic_mail',
+    version=info.get('version', '0.0.1'),
+    description=info.get('description', ''),
+    author=info.get('author', ''),
+    author_email=info.get('email', ''),
+    url=info.get('website', ''),
+    download_url="http://downloads.tryton.org/" + \
+            info.get('version', '0.0.1').rsplit('.', 1)[0] + '/',
+    package_dir={'trytond.modules.electronic_mail': '.'},
+    packages=[
+        'trytond.modules.electronic_mail',
+        'trytond.modules.electronic_mail.tests',
+    ],
+    package_data={
+        'trytond.modules.electronic_mail': info.get('xml', []) \
+                + info.get('translation', []),
+    },
+    classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Environment :: Plugins',
+        'Framework :: Tryton',
+        'Intended Audience :: Developers',
+        'Intended Audience :: Financial and Insurance Industry',
+        'Intended Audience :: Legal Industry',
+        'Intended Audience :: Manufacturing',
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Topic :: Office/Business',
+    ],
+    license='GPL-3',
+    install_requires=requires,
+    zip_safe=False,
+    entry_points="""
+    [trytond.modules]
+    electronic_mail = trytond.modules.electronic_mail
+    """,
+    test_suite='tests',
+    test_loader='trytond.test_loader:Loader',
+)
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+from test_electronic_mail import suite

tests/test_electronic_mail.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+"Electronic Mail test suite"
+
+import sys, os
+DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
+    '..', '..', '..', '..', '..', 'trytond')))
+if os.path.isdir(DIR):
+    sys.path.insert(0, os.path.dirname(DIR))
+import unittest
+
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from email.utils import formatdate
+
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
+    test_view, test_depends
+from trytond.transaction import Transaction
+from trytond.config import CONFIG
+
+# Set a data path since the module stores email attachment content in data dir
+CONFIG['data_path'] = '/tmp/'
+
+USER_TYPES = ('owner_user_%s', 'read_user_%s', 'write_user_%s')
+
+
+class ElectronicMailTestCase(unittest.TestCase):
+    """
+    Test Electronic Mail Module
+    """
+
+    def setUp(self):
+        trytond.tests.test_tryton.install_module('electronic_mail')
+
+        self.mailbox_obj = POOL.get('electronic_mail.mailbox')
+        self.mail_obj = POOL.get('electronic_mail')
+        self.header_obj = POOL.get('electronic_mail.header')
+        self.user_obj = POOL.get('res.user')
+        self.groups_obj = POOL.get('res.group')
+        self.ir_model_data_obj = POOL.get('ir.model.data')
+
+    def create_user(self, name):
+        """
+        Creates a new user and returns the ID
+        """
+        group_email_admin_id =  self.ir_model_data_obj.get_id(
+            'electronic_mail', 'group_email_admin')
+        group_email_user_id =  self.ir_model_data_obj.get_id(
+            'electronic_mail', 'group_email_user')
+
+        return self.user_obj.create(
+            {
+            'login': name,
+            'name': name,
+            'password': name,
+            'groups': [('set', [group_email_admin_id, group_email_user_id])]
+            })
+
+    def create_users(self, no_of_sets=1):
+        """
+        A set of user is:
+            1 Owner
+            1 Read user
+            1 Write User
+        :return: List of tuple of the ID of three users
+        """
+        created_users = [ ]
+        for iteration in xrange(1, no_of_sets + 1):
+            created_users.append(
+                tuple([
+                    self.create_user(user_type % iteration) \
+                        for user_type in USER_TYPES
+                ])
+            )
+        return created_users
+
+    def test0005views(self):
+        '''
+        Test views.
+        '''
+        test_view('electronic_mail')
+
+    def test0010depends(self):
+        test_depends()
+
+    def test0010mailbox_read_rights(self):
+        '''
+        Read access rights Test
+        '''
+        with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
+            # Create Users for testing access
+            user_set_1, user_set_2 = self.create_users(no_of_sets=2)
+            # Create a mailbox with a user set
+            self.mailbox_obj.create(
+                {
+                    'name': 'Parent Mailbox',
+                    'user': user_set_1[0],
+                    'read_users': [('set', [user_set_1[1]])],
+                    'write_users': [('set', [user_set_1[2]])],
+                    })
+
+            # Create a mailbox 2 with RW users of set 1 + set 2
+            self.mailbox_obj.create(
+                {
+                    'name': 'Child Mailbox',
+                    'user': user_set_2[0],
+                    'read_users': [('set', [user_set_1[1], user_set_2[1]])],
+                    'write_users': [('set', [user_set_1[2], user_set_2[2]])],
+                    })
+
+            # Directly test the mailboxes each user has access to
+            expected_results = {
+                user_set_1[0]: 1, user_set_2[0]: 1,
+                user_set_1[1]: 2, user_set_2[1]: 1,
+                user_set_1[2]: 2, user_set_2[2]: 1,
+                USER: 2
+                }
+            for user_id, mailbox_count in expected_results.items():
+                with Transaction().set_user(user_id):
+                    self.assertEqual(
+                        self.mailbox_obj.search([], count=True),
+                        mailbox_count
+                    )
+
+            transaction.cursor.rollback()
+
+    def test0020mail_create_access_rights(self):
+        """
+        Create access rights Test
+
+        Create Three Users
+            user_r, user_w, user_o
+
+        Create a mailbox with write access to user_w and read access
+        to user_r and owner as user_o
+
+        Try various security combinations for create
+        """
+        with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
+            user_o, user_r, user_w = self.create_users(no_of_sets=1)[0]
+            mailbox = self.mailbox_obj.create(
+                {
+                    'name': 'Mailbox',
+                    'user': user_o,
+                    'read_users': [('set', [user_r])],
+                    'write_users': [('set', [user_w])],
+                    })
+
+            # Raise exception when writing a mail with the read user
+            with Transaction().set_user(user_r):
+                self.assertRaises(
+                    Exception, self.mail_obj.create,
+                    ({
+                        'from_': 'Test',
+                        'mailbox': mailbox
+                        },))
+
+            # Creating mail with the write user
+            with Transaction().set_user(user_w):
+                self.assert_(
+                    self.mail_obj.create({'from_': 'Test', 'mailbox': mailbox})
+                )
+
+            # Create an email as mailbox owner
+            with Transaction().set_user(user_o):
+                self.assert_(
+                    self.mail_obj.create({'from_': 'Test', 'mailbox': mailbox})
+                )
+
+            transaction.cursor.rollback()
+
+    def test0030_email_message_extraction(self):
+        """
+        Create from email functionality extracts info from
+        an email.message. Test the extraction for multiple types
+        """
+        # 1: multipart/alternative
+        message = MIMEMultipart('alternative')
+
+        message['date'] = formatdate()
+        message['Subject'] = "Link"
+        message['From'] = "pythonistas@example.com"
+        message['To'] = "trytonistas@example.com"
+        text = """Hi!\nHow are you?\nHere is the link you wanted:
+        http://www.python.org/
+        http://www.tryton.org/
+        """
+        html = """\
+        <html>
+          <head></head>
+          <body>
+            <p>Hi!<br>
+               How are you?<br>
+               Here is the <a href="http://www.python.org">link</a> you wanted.
+            </p>
+          </body>
+        </html>
+        """
+        part1 = MIMEText(text, 'plain')
+        part2 = MIMEText(html, 'html')
+        message.attach(part1)
+        message.attach(part2)
+
+        with Transaction().start(DB_NAME, USER, CONTEXT):
+            mailbox = self.mailbox_obj.create(
+                {
+                    'name': 'Mailbox',
+                    'user': USER,
+                    'read_users': [('set', [USER])],
+                    'write_users': [('set', [USER])],
+                    })
+            mail_id = self.mail_obj.create_from_email(message, mailbox)
+            mail = self.mail_obj.browse(mail_id)
+
+            self.assertEqual(mail.subject, message['Subject'])
+            self.assertEqual(mail.from_, message['From'])
+            self.assertEqual(mail.to, message['To'])
+
+
+def suite():
+    "Electronic mail test suite"
+    suite = trytond.tests.test_tryton.suite()
+    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+        ElectronicMailTestCase
+        )
+    )
+    return suite
+
+if __name__ == '__main__':
+    unittest.TextTestRunner(verbosity=2).run(suite())