}
_order = 'sequence'
+
+def short_name(name):
+ """Keep first word(s) of name to make it small enough
+ but distinctive"""
+ if not name: return name
+ # keep 7 chars + end of the last word
+ keep_words = name[:7].strip().split()
+ return ' '.join(name.split()[:len(keep_words)])
+
class project(osv.osv):
_name = "project.project"
_description = "Project"
- _inherits = {'account.analytic.account': "analytic_account_id","mail.alias":"alias_id"}
+ _inherits = {'account.analytic.account': "analytic_account_id",
+ "mail.alias": "alias_id"}
_inherit = ['ir.needaction_mixin', 'mail.thread']
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
mail_alias = self.pool.get('mail.alias')
for proj in self.browse(cr, uid, ids):
if proj.tasks:
- raise osv.except_osv(_('Operation Not Permitted !'),
- _('You cannot delete a project containing tasks. You may disable it instead by unticking the Active checkbox.'))
+ raise osv.except_osv(_('Invalid Action!'),
+ _('You cannot delete a project containing tasks. You can either delete all the project\'s tasks and then delete the project or simply deactivate the project.'))
elif proj.alias_id:
alias_ids.append(proj.alias_id.id)
res = super(project, self).unlink(cr, uid, ids, *args, **kwargs)
project_ids = project_obj.search(cr, uid, [('message_ids.user_id.id', 'in', args[0][2])], context=context)
return [('id', 'in', project_ids)]
- # Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
+ # Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
_alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs)
_columns = {
'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'task_count': fields.function(_task_count, type='integer', string="Open Tasks"),
'color': fields.integer('Color Index'),
- 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
- help="The email address associated with this project. New emails received will automatically "
- "create project tasks (optionally project issues if the Project Issue module is installed)."),
- 'alias_model': fields.selection(_alias_models, "Alias Model", select=True, required=True,
- help="The kind of document created when an email is received by this project's email alias"),
+ 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
+ help="Internal email associated with this project. Incoming emails are automatically synchronized"
+ "with Tasks (or optionally Issues if the Issue Tracker module is installed)."),
+ 'alias_model': fields.selection(_alias_models, "Alias Model", select=True, required=True,
+ help="The kind of document created when an email is received on this project's email alias"),
'privacy_visibility': fields.selection([('public','Public'), ('followers','Followers Only')], 'Privacy / Visibility', required=True),
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','Pending'),('close','Closed')], 'Status', required=True,),
'followers': fields.function(_get_followers, method=True, fnct_search=_search_followers,
'type_ids': _get_type_common,
'alias_model': 'project.task',
'privacy_visibility': 'public',
+ 'alias_domain': False, # always hide alias during creation
}
# TODO: Why not using a SQL contraints ?
context['active_test'] = False
default['state'] = 'open'
default['tasks'] = []
- default['alias_id'] = False
+ default.pop('alias_name', None)
+ default.pop('alias_id', None)
proj = self.browse(cr, uid, id, context=context)
if not default.get('name', False):
default['name'] = proj.name + _(' (copy)')
- default['alias_name'] = default['name']
res = super(project, self).copy(cr, uid, id, default, context)
self.map_tasks(cr,uid,id,res,context)
return res
for project in projects:
if (not project.members) and force_members:
- raise osv.except_osv(_('Warning !'),_("You must assign members on the project '%s' !") % (project.name,))
+ raise osv.except_osv(_('Warning!'),_("You must assign members on the project '%s' !") % (project.name,))
resource_pool = self.pool.get('resource.resource')
return user_ids
def create(self, cr, uid, vals, context=None):
- alias_pool = self.pool.get('mail.alias')
+ if context is None: context = {}
+ # Prevent double project creation when 'use_tasks' is checked!
+ context = dict(context, project_creation_in_progress=True)
+ mail_alias = self.pool.get('mail.alias')
if not vals.get('alias_id'):
- name = vals.get('alias_name') or vals['name']
- alias_id = alias_pool.create_unique_alias(cr, uid,
- {'alias_name': "project_"+name,
- 'alias_model_id': vals.get('alias_model', 'project.task')}, context=context)
- alias = alias_pool.read(cr, uid, alias_id, ['alias_name'],context)
- vals.update({'alias_id': alias_id, 'alias_name': alias['alias_name']})
- res = super( project, self).create(cr, uid, vals, context)
- alias_pool.write(cr, uid, [vals['alias_id']], {'alias_defaults':{'project_id': res}}, context)
- self.create_send_note(cr, uid, [res], context=context)
- return res
+ vals.pop('alias_name', None) # prevent errors during copy()
+ alias_id = mail_alias.create_unique_alias(cr, uid,
+ # Using '+' allows using subaddressing for those who don't
+ # have a catchall domain setup.
+ {'alias_name': "project+"+short_name(vals['name'])},
+ model_name=vals.get('alias_model', 'project.task'),
+ context=context)
+ vals['alias_id'] = alias_id
+ project_id = super(project, self).create(cr, uid, vals, context)
+ mail_alias.write(cr, uid, [vals['alias_id']], {'alias_defaults': {'project_id': project_id} }, context)
+ self.create_send_note(cr, uid, [project_id], context=context)
+ return project_id
def create_send_note(self, cr, uid, ids, context=None):
return self.message_append_note(cr, uid, ids, body=_("Project has been <b>created</b>."), context=context)
return self.message_append_note(cr, uid, ids, body=message, context=context)
def write(self, cr, uid, ids, vals, context=None):
- model_pool = self.pool.get('ir.model')
- # if alias_model has been changed, update alias_model_id of alias accordingly
+ # if alias_model has been changed, update alias_model_id accordingly
if vals.get('alias_model'):
- model_ids = model_pool.search(cr, uid, [('model', '=', vals.get('alias_model','project.task'))])
+ model_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', vals.get('alias_model', 'project.task'))])
vals.update(alias_model_id=model_ids[0])
return super(project, self).write(cr, uid, ids, vals, context=context)
}),
'user_id': fields.many2one('res.users', 'Assigned to'),
'delegated_user_id': fields.related('child_ids', 'user_id', type='many2one', relation='res.users', string='Delegated To'),
- 'partner_id': fields.many2one('res.partner', 'Partner'),
+ 'partner_id': fields.many2one('res.partner', 'Contact'),
'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'),
'manager_id': fields.related('project_id', 'analytic_account_id', 'user_id', type='many2one', relation='res.users', string='Project Manager'),
'company_id': fields.many2one('res.company', 'Company'),
#TO FIX:Kanban view doesn't raise warning
#stages = [stage.id for stage in t.project_id.type_ids]
#if new_stage not in stages:
- #raise osv.except_osv(_('Warning !'), _('Stage is not defined in the project.'))
+ #raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.'))
write_vals = vals_reset_kstate if t.stage_id != new_stage else vals
super(task,self).write(cr, uid, [t.id], write_vals, context=context)
self.stage_set_send_note(cr, uid, [t.id], new_stage, context=context)
'use_tasks': fields.boolean('Tasks Mgmt.',help="If check,this contract will be available in the project menu and you will be able to manage tasks or track issues"),
'company_uom_id': fields.related('company_id', 'project_time_mode_id', type='many2one', relation='product.uom'),
}
-
+
def on_change_template(self, cr, uid, ids, template_id, context=None):
res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
if template_id and 'value' in res:
'''
This function is used to decide if a project needs to be automatically created or not when an analytic account is created. It returns True if it needs to be so, False otherwise.
'''
- return vals.get('use_tasks')
+ if context is None: context = {}
+ return vals.get('use_tasks') and not 'project_creation_in_progress' in context
def project_create(self, cr, uid, analytic_account_id, vals, context=None):
'''
project_obj = self.pool.get('project.project')
analytic_ids = project_obj.search(cr, uid, [('analytic_account_id','in',ids)])
if analytic_ids:
- raise osv.except_osv(_('Warning !'), _('Please delete the project linked with this account first.'))
+ raise osv.except_osv(_('Warning!'), _('Please delete the project linked with this account first.'))
return super(account_analytic_account, self).unlink(cr, uid, ids, *args, **kwargs)
+class project_project(osv.osv):
+ _inherit = 'project.project'
+ _defaults = {
+ 'use_tasks': True
+ }
+
#
# Tasks History, used for cumulative flow charts (Lean/Agile)