@@ -226,22 +226,35 @@
|
||||
<field name="context_tz"/>
|
||||
|
||||
<separator string="Group" colspan="4"/>
|
||||
<label align="0.0" colspan="4" string="
|
||||
Groups are used to define access rights on objects and
|
||||
the visibility of screens and menus"/>
|
||||
<label align="0.0" colspan="4" string="Groups are used to define access rights on objects and the visibility of screens and menus"/>
|
||||
<field colspan="4" nolabel="1" name="groups_id"/>
|
||||
</group>
|
||||
|
||||
<xpath expr='//button[@name="action_skip"]'
|
||||
position='attributes'>
|
||||
<attribute name='string'>Next</attribute>
|
||||
<attribute name='icon'>gtk-go-forward</attribute>
|
||||
</xpath>
|
||||
<xpath expr='//button[@name="action_next"]'
|
||||
position='attributes'>
|
||||
<attribute name="name">action_add</attribute>
|
||||
<attribute name='string'>Add User</attribute>
|
||||
<attribute name='icon'>gtk-add</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr='//button[@name="action_skip"]'
|
||||
position='attributes'>
|
||||
<!-- if this one is performed first, it transforms
|
||||
the action_skip into action_next, and the
|
||||
transformation of action_next to action_add,
|
||||
since it uses first-match, transforms the same
|
||||
button. And we end up with [add] [next] instead
|
||||
of [next] [add]
|
||||
|
||||
Would probably be simpler to just replace both
|
||||
by nothing and create a pair of brand new
|
||||
buttons... but we'd have to handle the groups
|
||||
around the buttons... oh well...
|
||||
-->
|
||||
<attribute name="name">action_next</attribute>
|
||||
<attribute name='string'>Done</attribute>
|
||||
<attribute name='icon'>gtk-go-forward</attribute>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
@@ -268,18 +281,11 @@
|
||||
</form>
|
||||
|
||||
<group string="res_config_contents" position="replace">
|
||||
<label colspan="4" align="0.0" string='
|
||||
Choose between the simplified interface and the extended
|
||||
one.'/>
|
||||
<label colspan="4" align="0.0" string="Choose between the simplified interface and the extended one."/>
|
||||
<newline/>
|
||||
<label colspan="4" align="0.0" string='
|
||||
If you are testing OpenERP or using it for the first time,
|
||||
we suggest you use the simplified interface. It has less
|
||||
options and fields but is easier to understand.'/>
|
||||
<label colspan="4" align="0.0" string="If you are testing OpenERP or using it for the first time, we suggest you use the simplified interface. It has less options and fields but is easier to understand."/>
|
||||
<newline/>
|
||||
<label colspan="4" align="0.0" string='
|
||||
You will be able to switch to the extended interface later.
|
||||
'/>
|
||||
<label colspan="4" align="0.0" string="You will be able to switch to the extended interface later."/>
|
||||
<separator string="Choose Your Interface" colspan="4"/>
|
||||
<field colspan="2" name="view" nolabel="1"/>
|
||||
</group>
|
||||
@@ -314,7 +320,7 @@
|
||||
|
||||
<record id="config_wizard_simple_view" model="ir.actions.todo">
|
||||
<field name="action_id" ref="action_config_simple_view_form"/>
|
||||
<field name="sequence">5</field>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -1342,7 +1342,7 @@
|
||||
<field name="sequence"/>
|
||||
<field name="action_id" select="1"/>
|
||||
<field name="state"/>
|
||||
<field name="active" select="2"/>
|
||||
<field name="active" select="2" invisible="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
@@ -15,11 +15,14 @@
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
import netsvc
|
||||
import pooler
|
||||
|
||||
@@ -57,11 +60,31 @@ class res_config_configurable(osv.osv_memory):
|
||||
'getting next %s' % todos)
|
||||
active_todos = todos.search(cr, uid, [('state','=','open'),
|
||||
('active','=',True)],
|
||||
limit=1, context=None)
|
||||
limit=1)
|
||||
if active_todos:
|
||||
return todos.browse(cr, uid, active_todos[0], context=None)
|
||||
return None
|
||||
|
||||
def _set_previous_todo(self, cr, uid, state):
|
||||
""" lookup the previous (which is still the next at this point)
|
||||
ir.actions.todo, set it to whatever state was provided.
|
||||
|
||||
Raises
|
||||
`LookupError`: if we couldn't find *any* previous todo
|
||||
`ValueError`: if no state is provided
|
||||
anything ir_actions_todo.write can throw
|
||||
"""
|
||||
# this is ultra brittle, but apart from storing the todo id
|
||||
# into the res.config view, I'm not sure how to get the
|
||||
# "previous" todo
|
||||
previous_todo = self._next_action(cr, uid)
|
||||
if not previous_todo:
|
||||
raise LookupError(_("Couldn't find previous ir.actions.todo"))
|
||||
if not state:
|
||||
raise ValueError(_("Can't set an ir.actions.todo's state to "
|
||||
"nothingness"))
|
||||
previous_todo.write({'state':state})
|
||||
|
||||
def _next(self, cr, uid):
|
||||
self.logger.notifyChannel('actions', netsvc.LOG_INFO,
|
||||
'getting next operation')
|
||||
@@ -69,9 +92,6 @@ class res_config_configurable(osv.osv_memory):
|
||||
self.logger.notifyChannel('actions', netsvc.LOG_INFO,
|
||||
'next action is %s' % next)
|
||||
if next:
|
||||
self.pool.get('ir.actions.todo').write(cr, uid, next.id, {
|
||||
'state':'done',
|
||||
}, context=None)
|
||||
action = next.action_id
|
||||
return {
|
||||
'view_mode': action.view_mode,
|
||||
@@ -90,42 +110,221 @@ class res_config_configurable(osv.osv_memory):
|
||||
# return the action associated with the menu
|
||||
return self.pool.get(current_user_menu.type)\
|
||||
.read(cr, uid, current_user_menu.id)
|
||||
|
||||
def next(self, cr, uid, ids, context=None):
|
||||
""" Returns the next todo action to execute (using the default
|
||||
sort order)
|
||||
"""
|
||||
return self._next(cr, uid)
|
||||
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
""" Method called when the user clicks on the ``Next`` button.
|
||||
|
||||
Execute *must* be overloaded unless ``action_next`` is overloaded
|
||||
(which is something you generally don't need to do).
|
||||
|
||||
If ``execute`` returns an action dictionary, that action is executed
|
||||
rather than just going to the next configuration item.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
'Configuration items need to implement execute')
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
""" Method called when the user click on the ``Skip`` button.
|
||||
|
||||
``cancel`` should be overloaded instead of ``action_skip``. As with
|
||||
``execute``, if it returns an action dictionary that action is
|
||||
executed in stead of the default (going to the next configuration item)
|
||||
|
||||
The default implementation is a NOOP.
|
||||
|
||||
``cancel`` is also called by the default implementation of
|
||||
``action_cancel``.
|
||||
"""
|
||||
pass
|
||||
|
||||
def action_next(self, cr, uid, ids, context=None):
|
||||
""" Action handler for the ``next`` event.
|
||||
|
||||
Sets the status of the todo the event was sent from to
|
||||
``done``, calls ``execute`` and -- unless ``execute`` returned
|
||||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
self._set_previous_todo(cr, uid, state='done')
|
||||
next = self.execute(cr, uid, ids, context=None)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
|
||||
def action_skip(self, cr, uid, ids, context=None):
|
||||
""" Action handler for the ``skip`` event.
|
||||
|
||||
Sets the status of the todo the event was sent from to
|
||||
``skip``, calls ``cancel`` and -- unless ``cancel`` returned
|
||||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
self._set_previous_todo(cr, uid, state='skip')
|
||||
next = self.cancel(cr, uid, ids, context=None)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
|
||||
def action_cancel(self, cr, uid, ids, context=None):
|
||||
""" Action handler for the ``cancel`` event. That event isn't
|
||||
generated by the res.config.view.base inheritable view, the
|
||||
inherited view has to overload one of the buttons (or add one
|
||||
more).
|
||||
|
||||
Sets the status of the todo the event was sent from to
|
||||
``cancel``, calls ``cancel`` and -- unless ``cancel`` returned
|
||||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
self._set_previous_todo(cr, uid, state='cancel')
|
||||
next = self.cancel(cr, uid, ids, context=None)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
res_config_configurable()
|
||||
|
||||
class res_config_installer(osv.osv_memory):
|
||||
''' New-style configuration base specialized for modules selection
|
||||
""" New-style configuration base specialized for addons selection
|
||||
and installation.
|
||||
'''
|
||||
|
||||
Basic usage
|
||||
-----------
|
||||
|
||||
Subclasses can simply define a number of _columns as
|
||||
fields.boolean objects. The keys (column names) should be the
|
||||
names of the addons to install (when selected). Upon action
|
||||
execution, selected boolean fields (and those only) will be
|
||||
interpreted as addons to install, and batch-installed.
|
||||
|
||||
Additional addons
|
||||
-----------------
|
||||
|
||||
It is also possible to require the installation of an additional
|
||||
addon set when a specific preset of addons has been marked for
|
||||
installation (in the basic usage only, additionals can't depend on
|
||||
one another).
|
||||
|
||||
These additionals are defined through the ``_install_if``
|
||||
property. This property is a mapping of a collection of addons (by
|
||||
name) to a collection of addons (by name) [#]_, and if all the *key*
|
||||
addons are selected for installation, then the *value* ones will
|
||||
be selected as well. For example::
|
||||
|
||||
_install_if = {
|
||||
('sale','crm'): ['sale_crm'],
|
||||
}
|
||||
|
||||
This will install the ``sale_crm`` addon if and only if both the
|
||||
``sale`` and ``crm`` addons are selected for installation.
|
||||
|
||||
You can define as many additionals as you wish, and additionals
|
||||
can overlap in key and value. For instance::
|
||||
|
||||
_install_if = {
|
||||
('sale','crm'): ['sale_crm'],
|
||||
('sale','project'): ['project_mrp'],
|
||||
}
|
||||
|
||||
will install both ``sale_crm`` and ``project_mrp`` if all of
|
||||
``sale``, ``crm`` and ``project`` are selected for installation.
|
||||
|
||||
Hook methods
|
||||
------------
|
||||
|
||||
Subclasses might also need to express dependencies more complex
|
||||
than that provided by additionals. In this case, it's possible to
|
||||
define methods of the form ``_if_%(name)s`` where ``name`` is the
|
||||
name of a boolean field. If the field is selected, then the
|
||||
corresponding module will be marked for installation *and* the
|
||||
hook method will be executed.
|
||||
|
||||
Hook methods take the usual set of parameters (cr, uid, ids,
|
||||
context) and can return a collection of additional addons to
|
||||
install (if they return anything, otherwise they should not return
|
||||
anything, though returning any "falsy" value such as None or an
|
||||
empty collection will have the same effect).
|
||||
|
||||
Complete control
|
||||
----------------
|
||||
|
||||
The last hook is to simply overload the ``modules_to_install``
|
||||
method, which implements all the mechanisms above. This method
|
||||
takes the usual set of parameters (cr, uid, ids, context) and
|
||||
returns a ``set`` of addons to install (addons selected by the
|
||||
above methods minus addons from the *basic* set which are already
|
||||
installed) [#]_ so an overloader can simply manipulate the ``set``
|
||||
returned by ``res_config_installer.modules_to_install`` to add or
|
||||
remove addons.
|
||||
|
||||
Skipping the installer
|
||||
----------------------
|
||||
|
||||
Unless it is removed from the view, installers have a *skip*
|
||||
button which invokes ``action_skip`` (and the ``cancel`` hook from
|
||||
``res.config``). Hooks and additionals *are not run* when skipping
|
||||
installation, even for already installed addons.
|
||||
|
||||
Again, setup your hooks accordinly.
|
||||
|
||||
.. [#] note that since a mapping key needs to be hashable, it's
|
||||
possible to use a tuple or a frozenset, but not a list or a
|
||||
regular set
|
||||
|
||||
.. [#] because the already-installed modules are only pruned at
|
||||
the very end of ``modules_to_install``, additionals and
|
||||
hooks depending on them *are guaranteed to execute*. Setup
|
||||
your hooks accordingly.
|
||||
"""
|
||||
_name = 'res.config.installer'
|
||||
_inherit = 'res.config'
|
||||
|
||||
_install_if = {}
|
||||
|
||||
def _modules_to_install(self, cr, uid, ids, context=None):
|
||||
def _already_installed(self, cr, uid, context=None):
|
||||
""" For each module (boolean fields in a res.config.installer),
|
||||
check if it's already installed (neither uninstallable nor uninstalled)
|
||||
and if it is, check it by default
|
||||
"""
|
||||
modules = self.pool.get('ir.module.module')
|
||||
|
||||
selectable = [field for field in self._columns
|
||||
if type(self._columns[field]) is fields.boolean]
|
||||
return modules.browse(
|
||||
cr, uid,
|
||||
modules.search(cr, uid,
|
||||
[('name','in',selectable),
|
||||
('state','not in',['uninstallable', 'uninstalled'])],
|
||||
context=context),
|
||||
context=context)
|
||||
|
||||
|
||||
def modules_to_install(self, cr, uid, ids, context=None):
|
||||
""" selects all modules to install:
|
||||
|
||||
* checked boolean fields
|
||||
* return values of hook methods. Hook methods are of the form
|
||||
``_if_%(addon_name)s``, and are called if the corresponding
|
||||
addon is marked for installation. They take the arguments
|
||||
cr, uid, ids and context, and return an iterable of addon
|
||||
names
|
||||
* additionals, additionals are setup through the ``_install_if``
|
||||
class variable. ``_install_if`` is a dict of {iterable:iterable}
|
||||
where key and value are iterables of addon names.
|
||||
|
||||
If all the addons in the key are selected for installation
|
||||
(warning: addons added through hooks don't count), then the
|
||||
addons in the value are added to the set of modules to install
|
||||
* not already installed
|
||||
"""
|
||||
base = set(module_name
|
||||
for installer in self.read(cr, uid, ids, context=context)
|
||||
for module_name, to_install in installer.iteritems()
|
||||
if module_name != 'id'
|
||||
if type(self._columns[module_name]) is fields.boolean
|
||||
if to_install)
|
||||
|
||||
hooks_results = set()
|
||||
for module in base:
|
||||
hook = getattr(self, '_if_%s'%(module), None)
|
||||
@@ -138,11 +337,40 @@ class res_config_installer(osv.osv_memory):
|
||||
if base.issuperset(requirements)
|
||||
for module in consequences)
|
||||
|
||||
return base | hooks_results | additionals
|
||||
return (base | hooks_results | additionals) - set(
|
||||
map(attrgetter('name'), self._already_installed(cr, uid, context)))
|
||||
|
||||
def default_get(self, cr, uid, fields_list, context=None):
|
||||
''' If an addon is already installed, check it by default
|
||||
'''
|
||||
defaults = super(res_config_installer, self).default_get(
|
||||
cr, uid, fields_list, context=context)
|
||||
|
||||
return dict(defaults,
|
||||
**dict.fromkeys(
|
||||
map(attrgetter('name'),
|
||||
self._already_installed(cr, uid, context=context)),
|
||||
True))
|
||||
|
||||
def fields_get(self, cr, uid, fields=None, context=None, read_access=True):
|
||||
""" If an addon is already installed, set it to readonly as
|
||||
res.config.installer doesn't handle uninstallations of already
|
||||
installed addons
|
||||
"""
|
||||
fields = super(res_config_installer, self).fields_get(
|
||||
cr, uid, fields, context, read_access)
|
||||
|
||||
for module in self._already_installed(cr, uid, context=context):
|
||||
fields[module.name].update(
|
||||
readonly=True,
|
||||
help=fields[module.name].get('help', '') +
|
||||
_('\n\nThis addon is already installed on your system'))
|
||||
|
||||
return fields
|
||||
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
modules = self.pool.get('ir.module.module')
|
||||
to_install = list(self._modules_to_install(
|
||||
to_install = list(self.modules_to_install(
|
||||
cr, uid, ids, context=context))
|
||||
self.logger.notifyChannel(
|
||||
'installer', netsvc.LOG_INFO,
|
||||
|
||||
@@ -47,5 +47,41 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_config_installer" model="ir.ui.view">
|
||||
<field name="name">Inheritable view for installer objects</field>
|
||||
<field name="model">res.config.installer</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<form>
|
||||
<group colspan="4">
|
||||
<separator string="title" colspan="4"/>
|
||||
<group colspan="4" col="10">
|
||||
<group colspan="2">
|
||||
<image name="gtk-dialog-info"/>
|
||||
<newline/>
|
||||
<label align="0.0" string="description" width="200"/>
|
||||
</group>
|
||||
<group colspan="8"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="" colspan="4"/>
|
||||
<group colspan="4" col="8">
|
||||
<field name="progress" widget="progressbar"/>
|
||||
<label string="" colspan="3"/>
|
||||
<group colspan="1" col="2">
|
||||
<button name="action_skip" icon="gtk-goto-last" special="cancel"
|
||||
type="object" string="Skip"/>
|
||||
</group>
|
||||
<group colspan="1" col="2">
|
||||
<button name="action_next" icon="gtk-go-forward"
|
||||
type="object" string="Install Modules"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -83,7 +83,6 @@ class groups(osv.osv):
|
||||
|
||||
groups()
|
||||
|
||||
|
||||
class roles(osv.osv):
|
||||
_name = "res.roles"
|
||||
_columns = {
|
||||
@@ -118,18 +117,66 @@ class users(osv.osv):
|
||||
_uid_cache = {}
|
||||
_name = "res.users"
|
||||
|
||||
WELCOME_MAIL_SUBJECT = u"Welcome to OpenERP"
|
||||
WELCOME_MAIL_BODY = u"An OpenERP account has been created for you, "\
|
||||
"\"%(name)s\".\n\nYour login is %(login)s, "\
|
||||
"you should ask your supervisor or system administrator if you "\
|
||||
"haven't been given your password yet.\n\n"\
|
||||
"If you aren't %(name)s, this email reached you errorneously, "\
|
||||
"please delete it."
|
||||
|
||||
def get_welcome_mail_subject(self, cr, uid, context=None):
|
||||
""" Returns the subject of the mail new users receive (when
|
||||
created via the res.config.users wizard), default implementation
|
||||
is to return config_users.WELCOME_MAIL_SUBJECT
|
||||
"""
|
||||
return self.WELCOME_MAIL_SUBJECT
|
||||
def get_welcome_mail_body(self, cr, uid, context=None):
|
||||
""" Returns the subject of the mail new users receive (when
|
||||
created via the res.config.users wizard), default implementation
|
||||
is to return config_users.WELCOME_MAIL_BODY
|
||||
"""
|
||||
return self.WELCOME_MAIL_BODY
|
||||
|
||||
def get_current_company(self, cr, uid):
|
||||
res=[]
|
||||
cr.execute('select company_id, res_company.name from res_users left join res_company on res_company.id = company_id where res_users.id=%s' %uid)
|
||||
res = cr.fetchall()
|
||||
return res
|
||||
|
||||
def send_welcome_email(self, cr, uid, id, context=None):
|
||||
logger= netsvc.Logger()
|
||||
user = self.pool.get('res.users').read(cr, uid, id, context=context)
|
||||
if not tools.config.get('smtp_server'):
|
||||
logger.notifyChannel('mails', netsvc.LOG_WARNING,
|
||||
_('"smtp_server" needs to be set to send mails to users'))
|
||||
return False
|
||||
if not tools.config.get('email_from'):
|
||||
logger.notifyChannel("mails", netsvc.LOG_WARNING,
|
||||
_('"email_from" needs to be set to send welcome mails '
|
||||
'to users'))
|
||||
return False
|
||||
if not user.get('email'):
|
||||
return False
|
||||
|
||||
return tools.email_send(email_from=None, email_to=[user['email']],
|
||||
subject=self.get_welcome_mail_subject(
|
||||
cr, uid, context=context),
|
||||
body=self.get_welcome_mail_body(
|
||||
cr, uid, context=context) % user)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True, select=True),
|
||||
'login': fields.char('Login', size=64, required=True),
|
||||
'name': fields.char('Name', size=64, required=True, select=True,
|
||||
help="The new user's real name, used for searching"
|
||||
" and most listings"),
|
||||
'login': fields.char('Login', size=64, required=True,
|
||||
help="Used to log into the system"),
|
||||
'password': fields.char('Password', size=64, invisible=True, help="Keep empty if you don't want the user to be able to connect on the system."),
|
||||
'email': fields.char('E-mail', size=64, help='If an email is provided'\
|
||||
', the user will be sent a message welcoming him'),
|
||||
'email': fields.char('E-mail', size=64,
|
||||
help='If an email is provided, the user will be sent a message '
|
||||
'welcoming him.\n\nWarning: if "email_from" and "smtp_server"'
|
||||
" aren't configured, it won't be possible to email new "
|
||||
"users."),
|
||||
'signature': fields.text('Signature', size=64),
|
||||
'address_id': fields.many2one('res.partner.address', 'Address'),
|
||||
'active': fields.boolean('Active'),
|
||||
@@ -138,10 +185,15 @@ class users(osv.osv):
|
||||
'groups_id': fields.many2many('res.groups', 'res_groups_users_rel', 'uid', 'gid', 'Groups'),
|
||||
'roles_id': fields.many2many('res.roles', 'res_roles_users_rel', 'uid', 'rid', 'Roles'),
|
||||
'rules_id': fields.many2many('ir.rule.group', 'user_rule_group_rel', 'user_id', 'rule_group_id', 'Rules'),
|
||||
'company_id': fields.many2one('res.company', 'Company', help="The company this user is currently working on.", required=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True,
|
||||
help="The company this user is currently working for."),
|
||||
'company_ids':fields.many2many('res.company','res_company_users_rel','user_id','cid','Accepted Companies'),
|
||||
'context_lang': fields.selection(_lang_get, 'Language', required=True),
|
||||
'context_tz': fields.selection(_tz_get, 'Timezone', size=64),
|
||||
'context_lang': fields.selection(_lang_get, 'Language', required=True,
|
||||
help="Sets the language for the user's user interface, when UI "
|
||||
"translations are available"),
|
||||
'context_tz': fields.selection(_tz_get, 'Timezone', size=64,
|
||||
help="The user's timezone, used to perform timezone conversions "
|
||||
"between the server and the client."),
|
||||
}
|
||||
def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||
def override_password(o):
|
||||
@@ -315,9 +367,14 @@ class config_users(osv.osv_memory):
|
||||
}
|
||||
|
||||
def create_user(self, cr, uid, new_id, context=None):
|
||||
''' create a new res.user instance from the data stored
|
||||
in the current res.config.users
|
||||
'''
|
||||
""" create a new res.user instance from the data stored
|
||||
in the current res.config.users.
|
||||
|
||||
If an email address was filled in for the user, sends a mail
|
||||
composed of the return values of ``get_welcome_mail_subject``
|
||||
and ``get_welcome_mail_body`` (which should be unicode values),
|
||||
with the user's data %-formatted into the mail body
|
||||
"""
|
||||
base_data = self.read(cr, uid, new_id, context=context)
|
||||
partner_id = self.pool.get('res.partner').main_partner(cr, uid)
|
||||
address = self.pool.get('res.partner.address').create(
|
||||
@@ -331,10 +388,14 @@ class config_users(osv.osv_memory):
|
||||
cr, base_data['name'], base_data['email'], context=context),
|
||||
address_id=address,
|
||||
)
|
||||
self.pool.get('res.users').create(
|
||||
new_user = self.pool.get('res.users').create(
|
||||
cr, uid, user_data, context)
|
||||
|
||||
self.send_welcome_email(cr, uid, new_user, context=context)
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
'Do nothing on execution, just launch the next action/todo'
|
||||
pass
|
||||
def action_add(self, cr, uid, ids, context=None):
|
||||
'Create a user, and re-display the view'
|
||||
self.create_user(cr, uid, ids[0], context=context)
|
||||
return {
|
||||
'view_type': 'form',
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
<rng:optional><rng:attribute name="position"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="fill"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="help"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="width"/></rng:optional>
|
||||
<rng:zeroOrMore>
|
||||
<rng:choice>
|
||||
<rng:ref name="notebook"/>
|
||||
|
||||
@@ -932,6 +932,17 @@ class orm_template(object):
|
||||
self._invalids.clear()
|
||||
|
||||
def default_get(self, cr, uid, fields_list, context=None):
|
||||
""" Set default values for the object's fields.
|
||||
|
||||
Returns a dict of {field_name:default_value}
|
||||
|
||||
Arguments:
|
||||
`fields_list`: the fields for which the object doesn't have
|
||||
any value yet, and default values need to be
|
||||
provided. If fields outside this list are
|
||||
returned, the user-provided values will be
|
||||
overwritten.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def perm_read(self, cr, user, ids, context=None, details=True):
|
||||
|
||||
+31
-27
@@ -395,13 +395,22 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
|
||||
for i, url in enumerate(url_index):
|
||||
if i == 0:
|
||||
html += '\n\n'
|
||||
html += '[%s] %s\n' % (i+1, url)
|
||||
html += '[%s] %s\n' % (i+1, url)
|
||||
return html
|
||||
|
||||
def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
|
||||
attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
|
||||
|
||||
"""Send an email."""
|
||||
"""Send an email.
|
||||
|
||||
Arguments:
|
||||
|
||||
`email_from`: A string used to fill the `From` header, if falsy,
|
||||
config['email_from'] is used instead. Also used for
|
||||
the `Reply-To` header if `reply_to` is not provided
|
||||
|
||||
`email_to`: a sequence of addresses to send the mail to.
|
||||
"""
|
||||
import smtplib
|
||||
from email.MIMEText import MIMEText
|
||||
from email.MIMEBase import MIMEBase
|
||||
@@ -410,32 +419,30 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
|
||||
from email.Utils import formatdate, COMMASPACE
|
||||
from email.Utils import formatdate, COMMASPACE
|
||||
from email import Encoders
|
||||
import netsvc
|
||||
import netsvc
|
||||
|
||||
if x_headers is None:
|
||||
x_headers = {}
|
||||
|
||||
if not ssl:
|
||||
ssl = config.get('smtp_ssl', False)
|
||||
if not ssl: ssl = config.get('smtp_ssl', False)
|
||||
|
||||
if not email_from and not config['email_from']:
|
||||
raise Exception("No Email sender by default, see config file")
|
||||
if not (email_from or config['email_from']):
|
||||
raise ValueError("Sending an email requires either providing a sender "
|
||||
"address or having configured one")
|
||||
|
||||
if not email_from:
|
||||
email_from = config.get('email_from', False)
|
||||
if not email_from: email_from = config.get('email_from', False)
|
||||
|
||||
if not email_cc:
|
||||
email_cc = []
|
||||
if not email_bcc:
|
||||
email_bcc = []
|
||||
if not email_cc: email_cc = []
|
||||
if not email_bcc: email_bcc = []
|
||||
if not body: body = u''
|
||||
try: email_body = body.encode('utf-8')
|
||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||
email_body = body
|
||||
|
||||
if not attach:
|
||||
try:
|
||||
msg = MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
|
||||
except:
|
||||
msg = MIMEText(body or '',_subtype=subtype,_charset='utf-8')
|
||||
else:
|
||||
msg = MIMEMultipart()
|
||||
email_text = MIMEText(email_body, _subtype=subtype, _charset='utf-8')
|
||||
|
||||
if attach: msg = MIMEMultipart()
|
||||
else: msg = email_text
|
||||
|
||||
msg['Subject'] = Header(ustr(subject), 'utf-8')
|
||||
msg['From'] = email_from
|
||||
@@ -455,21 +462,18 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
|
||||
msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
|
||||
msg['X-OpenERP-Server-Host'] = socket.gethostname()
|
||||
msg['X-OpenERP-Server-Version'] = release.version
|
||||
if priority:
|
||||
msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
|
||||
|
||||
msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
|
||||
|
||||
# Add dynamic X Header
|
||||
for key, value in x_headers.items():
|
||||
for key, value in x_headers.iteritems():
|
||||
msg['X-OpenERP-%s' % key] = str(value)
|
||||
|
||||
if openobject_id:
|
||||
msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
|
||||
|
||||
if attach:
|
||||
try:
|
||||
msg.attach(MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8'))
|
||||
except:
|
||||
msg.attach(MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
|
||||
msg.attach(email_text)
|
||||
for (fname,fcontent) in attach:
|
||||
part = MIMEBase('application', "octet-stream")
|
||||
part.set_payload( fcontent )
|
||||
|
||||
Reference in New Issue
Block a user