class HrAppraisalInput(models.SecureModel): _inherit = 'hr.appraisal.input' extra_comments = fields.Secure( string="Extra Comments", security='_password_security', password=False) @api.multi def _password_security(self): """ Author can see and edit all secured fields of his appraisal input HR manager and Manager of the employee_of_current_user can only see all secured fields of all appraisal inputs. """ user_env = self.env['res.users'] employee_env = self.env['hr.employee'] employee_obj = employee_env.search([('user_id', '=', self._uid)]) is_allow = False for rec in self: manager_id = rec.appraisal_id and\ rec.appraisal_id.manager_id and\ rec.appraisal_id.manager_id.id or False if user_env.has_group('base.group_hr_manager') or \ employee_obj.id == manager_id or \ employee_obj.id == rec.author_id.id: # Current user is author or employee manager or HR manager # can access to all secured fields is_allow = True else: is_allow = False break return is_allow
class HrAppraisalInput(models.SecureModel): _inherit = 'hr.appraisal.input' expect_salary_raise = fields.Secure(string="Expected Salary Raise (%)", password=True, security="_security_https_password") @api.multi def _security_https_password(self): """ Only author of input and Admin profile can see expect_salary_raise """ user = self.env.user can_access = False for rec in self: if user.id == SUPERUSER_ID or rec.author_id.user_id.id == user.id: can_access = True if user.has_group('tms_modules.group_profile_tms_admin'): can_access = True return can_access @api.multi def _password_security(self): """ Author can see and edit all secured fields of his appraisal input HR manager and Manager of the employee_of_current_user and Evaluators of appraisal can only see all secured fields of all appraisal inputs. """ user_env = self.env['res.users'] employee_env = self.env['hr.employee'] employee_obj = employee_env.search([('user_id', '=', self._uid)]) is_allow = False for rec in self: manager_id = rec.appraisal_id and\ rec.appraisal_id.manager_id and\ rec.appraisal_id.manager_id.id or False if user_env.has_group('base.group_hr_manager') or \ employee_obj.id == manager_id or \ employee_obj.id == rec.author_id.id or \ employee_obj.id == rec.appraisal_id.employee_id.id or \ rec._uid in rec.appraisal_id.evaluators_user_ids.ids: # Current user is author or employee manager or HR manager # or evaluator of appraisal can access to all secured # fields is_allow = True else: is_allow = False break return is_allow
class HrAppraisalInputLine(models.SecureModel): _inherit = 'hr.appraisal.input.line' explanation = fields.Secure(string="Explanation", security='_password_security', password=False) @api.multi def _password_security(self): """ Author can see and edit all secured fields of his appraisal input HR manager and Manager of the employee_of_current_user can only see all secured fields of all appraisal inputs. """ is_allow = False for rec in self: if rec.input_id._password_security(): is_allow = True else: is_allow = False break return is_allow
class TmsAsset(models.SecureModel): _name = 'tms.asset' _description = 'IT Assets that allow to manage asset for each employee.' _inherit = ['mail.thread'] name = fields.Char(string="Assets", required=False, store=True, compute="_compute_asset_name") request_id = fields.Many2one(string="IT Equipment Request", comodel_name="hr.equipment.request", track_visibility="onchange") trobz_contribution = fields.Float(string="Trobz Contribution", track_visibility="onchange") financial_agreement = fields.Secure(password=False, multiline=True, track_visibility='on_change', string='Financial Agreement') purchased_price = fields.Float(string="Purchase Price", track_visibility="onchange") purchased_date = fields.Date(string="Purchased Dated", default=datetime.now().today()) category_id = fields.Many2one(string="Equipment Category", comodel_name="hr.equipment.category") residual_value = fields.Float(string="Residual Value", compute='_compute_residual_value', store=True) salvage_value = fields.Float(string="Salvage Value", compute="_compute_salvage_value", store=True) depreciation_amount = fields.Float( string="Depreciation Amount (month)", digits_compute=dp.get_precision('Assets'), compute="_compute_depreciation_amount", store=True) depreciation_period = fields.Selection(DEPRECATION_PERIOD, string="Depreciation Period", default='0') owner_id = fields.Many2one(string="Owner", comodel_name="hr.employee", track_visibility="onchange") assignee_id = fields.Many2one(string="Assignee", comodel_name="hr.employee", track_visibility="onchange") assigning_date = fields.Date(string="Latest Assigned Date") item_condition_id = fields.Many2one(string="Current Equip. Status", comodel_name="item.condition") condition_details = fields.Text(string="Conditional Details") type = fields.Selection([('trobz', "Trobz's Assets"), ('personal', "Personal Asset")], string="Asset Type", default='personal') supplier_id = fields.Many2one(string="Supplier", comodel_name="res.partner", domain="[('supplier', '=', True)]") internal_code = fields.Char(string="Internal Code", help="Tracking asset by internal code is good" " for management in document.", track_visibility="onchange", default=lambda self: self.env['ir.sequence']. next_by_code('tms.asset.seq') or 'ASSET-') depreciation_line_ids = fields.One2many(string="Depriciation Lines", comodel_name="depreciation.lines", inverse_name="asset_id", track_visibility="onchange") assign_history_ids = fields.One2many( string=u'Asset Assign History', comodel_name='asset.assign.history', inverse_name='asset_id', ) is_depreciate_it_fund = fields.Boolean( string="Is Depreciated From IT Fund?", default=False) state = fields.Selection([ ('in_use', "In Use"), ('no_use', "No Use"), ('scrap', "Scrap"), ], string="Asset State", default='in_use') partial_type = fields.Selection([ ('none', "None"), ('it_fund', "IT Fund"), ('salary', "Salary"), ('cash', "Cash"), ], string="Partial Type", default='none') partial_per_month = fields.Float(string="Partial Per Month", track_visibility="onchange") @api.depends('owner_id', 'category_id', 'purchased_date') def _compute_asset_name(self): """" Generate Name for Asset {Category}-{Employee_Name}-{Purchased date YYYY-MM-DD} """ for asset in self: asset_name = "" if asset.category_id: asset_name += asset.category_id.name + "-" if asset.owner_id: asset_name += asset.owner_id.name + "-" if asset.purchased_date: asset_name += str(asset.purchased_date) if not asset_name: asset_name = "New Asset" asset.name = asset_name @api.onchange('type') def onchange_asset_type(self): if self.type != 'personal': self.owner_id = None self.partial_type = 'none' elif self.type == 'personal': self.assignee_id = self.owner_id self.residual_value = 0.0 self.salvage_value = 0.0 self.is_depreciate_it_fund = False @api.onchange('partial_type') def onchange_partial_type(self): if self.partial_type == 'none': self.trobz_contribution = self.purchased_price if self.partial_type != 'cash': self.partial_per_month = 0.0 @api.onchange('purchased_price') def onchange_purchased_price(self): if self.partial_type == 'none': self.trobz_contribution = self.purchased_price @api.depends("purchased_price", 'depreciation_period', "salvage_value") @api.multi def _compute_residual_value(self): for record in self: if record.depreciation_period == '0': record.residual_value = 0 else: record.residual_value = \ record.purchased_price - record.salvage_value @api.depends("purchased_price", 'depreciation_period', "depreciation_line_ids", "depreciation_line_ids.is_depreciated") @api.multi def _compute_salvage_value(self): for rec in self: amount = 0 for line in rec.depreciation_line_ids: if line.is_depreciated: amount += line.amount rec.salvage_value = amount @api.depends("purchased_price", 'depreciation_period', "purchased_date") @api.multi def _compute_depreciation_amount(self): for record in self: period = int(record.depreciation_period) if period != 0 and \ record.purchased_price > 0 and record.purchased_date: record.depreciation_amount = record.purchased_price / period else: record.depreciation_amount = None def get_asset_assignee(self, asset, start, end): """ Return assignee of asset in period from start to end. In a month, if has two owners, get first owner """ if not asset or not (start and end): return None if start > end: raise Warning('Can get owner if start < end!') assigns = self.env['asset.assign.history'].search([('asset_id', '=', asset.id)]) for assign in assigns: if not assign.start_date: continue start_date = datetime.strptime(assign.start_date, '%Y-%m-%d') start_date = start_date.date() if not assign.end_date: td = datetime.now() end_date = td else: end_date = datetime.strptime(assign.end_date, '%Y-%m-%d') end_date = end_date + relativedelta(months=1) end_date = end_date + relativedelta(day=1) end_date = end_date.date() if start_date <= start and end_date >= end: return assign.assignee_id return None @api.multi def generate_depreciation_lines(self): """ Generate depreciation lines for asset. """ depreciation_line_env = self.env["depreciation.lines"] for record in self: current_value = record.purchased_price period = int(record.depreciation_period) if period == 0: continue if not record.purchased_date or not record.purchased_price: raise Warning(_("Purchasing information are not correct.")) # split date start = datetime.strptime(record.purchased_date, '%Y-%m-%d').date() end = start + relativedelta(months=period) if record.depreciation_line_ids: line_ids = str(tuple(record.depreciation_line_ids.ids)) self._cr.execute(""" DELETE FROM depreciation_lines WHERE id in %s """ % line_ids) flag = start while flag <= end and current_value: # Reset amount every month amount = record.depreciation_amount or \ (record.purchased_price / period) amount = int(amount) # compute month_start & month_end first_date = flag + relativedelta(day=1) end_date = first_date + relativedelta(months=1) - \ relativedelta(days=1) month_start = max(start, first_date) month_end = min(end, end_date) delta_days = (month_end - month_start).days + 1 # Compute depreciation of month if first_date != month_start or end_date != month_end: days_of_month = (end_date - first_date).days depre_in_day = 1.0 * amount / days_of_month depre_in_month = depre_in_day * delta_days amount = int(depre_in_month) flag = end_date + relativedelta(days=1) if month_start <= month_end: assignee = self.get_asset_assignee(record, month_start, month_end) vals = { 'asset_id': record.id, 'employee_id': assignee and assignee.id or None, 'start_date': month_start, 'end_date': month_end, } if current_value >= amount: vals['amount'] = amount current_value = current_value - amount elif current_value > 0: vals['amount'] = current_value current_value = 0 depreciation_line_env.create(vals) @api.model def create(self, vals): """ - Create new asset_assignee with start_date and leave end_date empty """ assign_history_env = self.env['asset.assign.history'] res = super(TmsAsset, self).create(vals) self.generate_depreciation_lines() if 'assignee_id' not in vals: return res # Create new Asset Assign History for new asset if res.assignee_id and res.assignee_id.id: new_assign_history_vals = { 'asset_id': res.id, 'assignee_id': res.assignee_id.id, 'start_date': res.assigning_date or datetime.now().date(), 'end_date': None } assign_history_env.create(new_assign_history_vals) return res @api.multi def write(self, vals): """ Creating new Asset Assign History when reassign new Owner to Asset: - Checking if asset has previous owner: + If yes, search asset_assign_history and set end_date for it - Create new Asset Assign History with start_date, leave end_date empty """ for record in self: # Check if need re generate depreciation is_regenerate_depreciation = False depreciation_relation = [ 'assignee_id', 'depreciation_period', 'purchased_price' ] for i in depreciation_relation: if i in vals: is_regenerate_depreciation = True # Get old assignee if reassigne asset for new employee old_assignee = self.assignee_id or None super(TmsAsset, self).write(vals) if is_regenerate_depreciation: record.generate_depreciation_lines() # Asset is reassigned when set new assignee if 'assignee_id' in vals: assigning_date = datetime.strftime(datetime.now().date(), '%Y-%m-%d') if 'assigning_date' in vals and vals['assigning_date']: assigning_date = vals['assigning_date'] record.re_assigne_asset( old_assignee and old_assignee.id or None, record.assignee_id and record.assignee_id.id or None, assigning_date) return True @api.multi def assigne_back_trobz(self): """ Reassign asset back to trobz """ for asset in self: asset.assignee_id = None @api.multi def re_assigne_asset(self, old_assignee_id, assignee_id, assigning_date): """ (self, int, int, datetime) -> Reassign asset to new assignee on assigning_date. if no assignee_id, asset is assigned back to trobz, ignore create assign history """ assign_history_env = self.env['asset.assign.history'] for asset in self: # If asset has previous assignee, find that old assign history and # set end_date of assign history as previous date of assigning_date if old_assignee_id: old_assign_history = assign_history_env.search([ ('asset_id', '=', asset.id), ('assignee_id', '=', old_assignee_id), ('end_date', '=', None) ]) if old_assign_history: assigning_date = datetime.strptime(assigning_date, '%Y-%m-%d') previous_date = assigning_date - timedelta(days=1) old_assign_history[0].end_date = previous_date # Create new Asset Assign History if asset has new assignee if assignee_id: new_assign_history_vals = { 'asset_id': asset.id, 'assignee_id': asset.assignee_id.id, 'start_date': asset.assigning_date, 'end_date': None } assign_history_env.create(new_assign_history_vals)
class HrEquipmentRequest(models.SecureModel): _name = 'hr.equipment.request' _description = 'HR Equipment Request' _inherit = ['mail.thread'] name = fields.Char(string='Name', store=True, compute="_generate_name") category_id = fields.Many2one('hr.equipment.category', string='Category', readonly=True, track_visibility='onchange', states={'draft': [('readonly', False)]}) state = fields.Selection(string='State', selection=[('draft', 'Draft'), ('confirmed', 'Confirmed'), ('request_apprvd', 'Request Approved'), ('purchase_apprvd', 'Purchase Approved'), ('purchased', 'Purchased'), ('cancel', 'Canceled')], default='draft', track_visibility='onchange') request_date = fields.Date(string='Request Date', required=True, default=fields.Date.today(), readonly=True, track_visibility='onchange', states={'draft': [('readonly', False)]}) employee_id = fields.Many2one('hr.employee', string='Employee', required=True, readonly=True, track_visibility='onchange', states={'draft': [('readonly', False)]}) job_id = fields.Many2one('hr.job', string='Position', readonly=True, related='employee_id.job_id') reason = fields.Text(string='Reason', required=True, readonly=True, track_visibility='onchange', states={'draft': [('readonly', False)]}) model_req = fields.Text(string='Model', required=True, readonly=True, track_visibility='onchange', states={'draft': [('readonly', False)]}) est_price = fields.Float(string='Estimated Price', help='This price is the price that get from\ several website or given by employee. It is not official price from our \ supplier.', track_visibility='onchange', readonly=True, states={'draft': [('readonly', False)]}) financial_aggr = fields.Secure(multiline=True, track_visibility='onchange', string='Financial Agreement', security="_security_https_password") partial_apprv = fields.Boolean(string='Partial Approval', default=False, readonly=True, track_visibility='onchange', states={ 'draft': [('readonly', False)], 'confirmed': [('readonly', False)], 'request_apprvd': [('readonly', False)] }) trobz_contr_amt = fields.Float(string='Trobz Contribution Amount') schd_pur_date = fields.Date( string='Scheduled Purchase Date', readonly=True, track_visibility='onchange', states={ 'draft': [('readonly', False)], 'confirmed': [('readonly', False)] }, help='Date at which it is approved to buy\ equipment.', ) # purchase_price is Marked after Purchased Approved purchase_price = fields.Float(string='Purchase Price', readonly=True, track_visibility='onchange', states={ 'draft': [('readonly', False)], 'confirmed': [('readonly', False)], 'request_apprvd': [('readonly', False)] }) supp_invoice = fields.Char(string='Supplier Invoice Ref TFA', readonly=True, track_visibility='onchange', states={ 'draft': [('readonly', False)], 'confirmed': [('readonly', False)], 'request_apprvd': [('readonly', False)], 'purchase_apprvd': [('readonly', False)] }) delivery_date = fields.Date(string='Delivery Date', readonly=True, track_visibility='onchange', states={ 'draft': [('readonly', False)], 'confirmed': [('readonly', False)], 'request_apprvd': [('readonly', False)], 'purchase_apprvd': [('readonly', False)] }) invoicing_date = fields.Date( string='Invoicing Date', track_visibility='on_change', help="Date at which the vendor invoice is received at Trobz") extra_info = fields.Text(string='Extra Information', track_visibility='onchange') schd_pur_month = fields.Date(string="Scheduled Purchase Month", compute="_get_month", store=True, track_visibility='onchange') supplier_code = fields.Char(string="Supplier Code") def _get_month(self): """ Get Months for Filter """ f = '%Y-%m-%d' for x in self: x.schd_pur_month = str(datetime.strptime(x.schd_pur_date, f).month) @api.model @api.onchange('purchase_price') def _update_when_full_approval(self): """ Upgrade Contributed Amount whenever the Request is fully Approved """ if not self.partial_apprv: self.trobz_contr_amt = self.purchase_price @api.depends('request_date', 'employee_id') def _generate_name(self): """" Generate Name for Request {Employee_Name}-{Request_date YYYY-MM-DD} """ for x in self: if x.employee_id: x.name = x.employee_id.name + "-Request Date " + x.request_date else: x.name = "New Request" @api.multi def write(self, vals): res = super(HrEquipmentRequest, self).write(vals) if not self.partial_apprv and 'purchase_price' in vals: self.trobz_contr_amt = vals['purchase_price'] or 0 return res
class hr_applicant(models.SecureModel): _inherit = "hr.applicant" salary_expected_secure = fields.Secure(string='Expected Salary', security="_password_security", help="Salary Expected by Applicant") salary_proposed_secure = fields.Secure( string='Proposed Salary', security="_password_security", help="Salary Proposed by the Organization") partner_name = fields.Char("Applicant's Name", track_visibility="onchange") email_from = fields.Char('Email', size=128, help="These people will receive email.", track_visibility="onchange") partner_mobile = fields.Char('Mobile', size=32, track_visibility="onchange") type_id = fields.Many2one('hr.recruitment.degree', 'Degree', track_visibility="onchange") date_action = fields.Date('Next Action Date', track_visibility="onchange") title_action = fields.Char('Next Action', size=64, track_visibility="onchange") priority = fields.Selection(AVAILABLE_PRIORITIES, 'Appreciation', track_visibility="onchange") source_id = fields.Many2one('hr.recruitment.source', 'Source', track_visibility="onchange") reference = fields.Char('Referred By', track_visibility="onchange") job_id = fields.Many2one('hr.job', 'Applied Job', track_visibility="onchange") availability = fields.Integer( 'Availability', help="The number of days in which the applicant will " "be available to start working", track_visibility="onchange") categ_ids = fields.Many2many('hr.applicant_category', string='Tags', track_visibility="onchange") description = fields.Text('Description', track_visibility="onchange") @api.model def create(self, vals): """ Override function Calculate Subject = {Applied Job} - {Applicants name} """ job_obj = self.env['hr.job'] job_name = '' if vals.get('job_id'): job_name = job_obj.browse(vals['job_id']).name vals['name'] = job_name and job_name + ' - ' + vals['partner_name'] \ or vals['partner_name'] return super(hr_applicant, self).create(vals) @api.multi def write(self, vals): """ Override function Calculate Subject = {Applied Job} - {Applicants name} """ if 'job_id' not in vals and 'partner_name' not in vals: # Nothing change return super(hr_applicant, self).write(vals) job_obj = self.env['hr.job'] if 'job_id' in vals and 'partner_name' in vals: # change job and applicant name # update all applicant at the same time job_name = '' if vals.get('job_id'): job_name = job_obj.browse(vals['job_id']).name vals['name'] = job_name and job_name + ' - ' \ + vals['partner_name'] \ or vals['partner_name'] return super(hr_applicant, self).write(vals) else: # change job or partner_name if 'job_id' in vals: # Only change job name job_name = '' if vals['job_id']: job_name = job_obj.browse(vals['job_id']).name for app in self: pos = app.name.find('-') vals['name'] = job_name and job_name + ' - ' + \ (pos > -1 and app.name[pos + 2:] or app.name) \ or app.name[pos + 2:] super(hr_applicant, app).write(vals) elif 'partner_name' in vals: # On change the application name for app in self: pos = app.name.find('-') vals['name'] = (pos > -1 and app.name[: pos + 2] or '') + \ vals['partner_name'] super(hr_applicant, app).write(vals) return True @api.multi def _password_security(self): """ Only the followers of the application can read/update/delete the Propose Salary/Suggested Salary. """ is_allow = False for rec in self: if self.env.user.partner_id.id in rec.message_follower_ids.ids: is_allow = True else: is_allow = False break return is_allow @api.model def _get_applicants_of_followers(self, user_id): filter_ids = [] current_user = self.env["res.partner"].search([('user_id', '=', user_id)]) self._cr.execute(""" SELECT id FROM hr_applicant """) datas = [data[0] for data in self._cr.fetchall()] for rec in self.browse(datas): if current_user in rec.message_follower_ids: filter_ids.append(rec.id) if filter_ids: return [('id', 'in', filter_ids)] else: return [('id', 'in', [])] @api.model def search(self, args, offset=0, limit=None, order=None, count=False): uid = self._context.get('uid', self._uid) if self.message_follower_ids and \ uid in self.message_follower_ids.ids: args.extend(self._get_applicants_of_followers(uid)) return super(hr_applicant, self).search(args, offset=offset, limit=limit, order=order, count=count) @api.cr_uid_ids_context def message_track(self, cr, uid, ids, tracked_fields, initial_values, context=None): def convert_for_display(value, col_info): if not value and col_info['type'] == 'boolean': return 'False' if not value: return '' if col_info['type'] == 'many2one': return value.name_get()[0][1] if col_info['type'] == 'selection': return dict(col_info['selection'])[value] if col_info['type'] == 'many2many': str1 = ', '.join([v.name_get()[0][1] for v in value]) return str1 return value def format_message(message_description, tracked_values): message = '' if message_description: message = '<span>%s</span>' % message_description for name, change in tracked_values.items(): old_values = change.get('old_value') if isinstance(old_values, int): old_values = str(old_values) list_old_values = \ [item.strip().encode('utf-8') for item in old_values.strip('[]').split(',')] new_values = change.get('new_value') if isinstance(new_values, int): new_values = str(new_values) list_new_values = \ [item.strip().encode('utf-8') for item in new_values.strip('[]').split(',')] vals = [] for x in list_old_values: if x not in list_new_values: vals.append(x.decode('utf-8')) if vals: message +=\ '<div> • <b>Removed %s</b>: ' %\ change.get('col_info') message += '%s</div>' % ', '.join(vals) vals = [] for x in list_new_values: if x not in list_old_values: vals.append(x.decode('utf-8')) if vals: message +=\ '<div> • <b>Added %s</b>: ' %\ change.get('col_info') message += '%s</div>' % ', '.join(vals) return message if not tracked_fields: return True for browse_record in self.browse(cr, uid, ids, context=context): initial = initial_values[browse_record.id] changes = set() tracked_values = {} # generate tracked_values data structure: {'col_name': {col_info, # new_value, old_value}} for col_name, col_info in tracked_fields.items(): field = self._fields[col_name] initial_value = initial[col_name] record_value = getattr(browse_record, col_name) if record_value == initial_value and\ getattr(field, 'track_visibility', None) == 'always': tracked_values[col_name] = dict( col_info=col_info['string'], new_value=convert_for_display(record_value, col_info), ) # because browse null != False elif record_value != initial_value and\ (record_value or initial_value): if getattr(field, 'track_visibility', None) in\ ['always', 'onchange']: tracked_values[col_name] = dict( col_info=col_info['string'], old_value=convert_for_display( initial_value, col_info), new_value=convert_for_display( record_value, col_info), ) if col_name in tracked_fields: changes.add(col_name) if not changes: continue # find subtypes and post messages or log if no subtype found subtypes = [] # By passing this key, that allows to let the subtype empty and so # don't sent email because partners_to_notify from # mail_message._notify will be empty if not context.get('mail_track_log_only'): for field, track_info in self._track.items(): if field not in changes: continue for subtype, method in track_info.items(): if method(self, cr, uid, browse_record, context): subtypes.append(subtype) posted = False for subtype in subtypes: subtype_rec = self.pool.get('ir.model.data').xmlid_to_object( cr, uid, subtype, context=context) if not (subtype_rec and subtype_rec.exists()): _logger.debug('subtype %s not found' % subtype) continue message = format_message( subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values) self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype, context=context) posted = True if not posted: message = format_message('', tracked_values) self.message_post(cr, uid, browse_record.id, body=message, context=context) return True
class ResUsers(models.SecureModel): _inherit = ['mail.thread', 'res.users'] _name = 'res.users' _description = "TMS user" _order = "login" @api.multi def _compute_hide_calendar(self): """ The tab Calendar should be visible only when: - A user is opening his own User form - Admin user is connected (uid=1) - User with Admin Profile is connected """ logged_user = self.env.user for user in self: if logged_user.id == 1 or logged_user.is_admin_profile()\ or logged_user.id == user.id: user.hide_calendar = False else: user.hide_calendar = True @api.model def is_admin_profile(self): user_profile_name = self.group_profile_id and \ self.group_profile_id.name or '' if user_profile_name == "Admin Profile": return True return False @api.multi @api.depends('supporter_of_project_ids', 'supporter_of_project_ids.project_supporter_rel_ids') def _get_customer_visible_user_ids(self): """ Calculate field `customer_visible_user_ids` Get users who are the supporters of the projects supported by current user (User > tab Support > Supporter of Project) """ # TODO: check why we must all clear_cache to make security rule re-run # related to security rule on res.users or using function field. # Conclusion: Both Stored function field and clear_cache # can make the security rule worked self.env['ir.rule'].sudo().clear_cache() for user in self: if user.id == SUPERUSER_ID: # Ignore superuser continue if not isinstance(user.id, (int, long)): continue customer_visible_user_ids = [] for project in user.supporter_of_project_ids: for supporter in project.project_supporter_rel_ids: customer_visible_user_ids.append(supporter.id) user.customer_visible_user_ids = \ customer_visible_user_ids and \ [(6, 0, list(set(customer_visible_user_ids)))] or False @api.multi @api.depends('group_profile_id') def _get_is_trobz_member(self): for user in self: group_user = self.env.ref('base.group_user') profile_ids = group_user.profile_ids and \ group_user.profile_ids.ids or False user.is_trobz_member = True if ( profile_ids and user.group_profile_id and user.group_profile_id.id in profile_ids) else False @api.multi @api.depends('group_profile_id') def _get_is_external_dev(self): for user in self: if user.has_group('tms_modules.group_profile_external_developer'): user.is_external_dev = True else: user.is_external_dev = False @api.multi def _get_users_not_working_hour(self): """ Get list of users who did not input enough working hours within n days """ result = '' no_days = self.env.ref('tms_modules.no_days_check_working_hour', raise_if_not_found=True) days = no_days and no_days.value or 40 sql = """ SELECT rus.id, rp.name FROM res_users rus JOIN res_partner rp ON rus.partner_id = rp.id AND rp.active = True WHERE rus.active = True AND rus.must_input_working_hour = True AND rus.is_trobz_member = True ORDER BY rus.id; """ self._cr.execute(sql) if self._cr.rowcount > 0: data = self._cr.fetchall() for user in data: day_not_enough = self.check_wh_n_day_past(user[0], days) if not day_not_enough: continue if len(day_not_enough) >= 10: result += '<span style="font-weight: bold; ' +\ 'color: red;">@' elif len(day_not_enough) >= 3: result += '<span style="font-weight: bold;">@' else: result += '<span>@' result += user[1] + ': </span>' result += ', '.join(day_not_enough) result += '<br/>' for user in self: user.users_not_working_hour = result def _set_new_password(self, cr, uid, user_id, name, value, args, context=None): if value is False: # Do not update the password if no value is provided, # ignore silently. # For example web client submits False values for all empty fields. return if uid == user_id: # To change their own password users must use # the client-specific change password wizard, # so that the new password is immediately used for # further RPC requests, otherwise the user # will face unexpected 'Access Denied' exceptions. raise osv.except_osv( _('Operation Canceled'), _('Please use the change password wizard (in User Preferences' + ' or User menu) to change your own password.')) self.write(cr, uid, user_id, {'password': value}) def _get_password(self, cr, uid, ids, arg, karg, context=None): return dict.fromkeys(ids, '') _columns = { 'new_password': fields_v7.function( _get_password, type='char', size=64, fnct_inv=_set_new_password, string='Set Password', help="Specify a value only when creating a user or if you're " "changing the user's password, otherwise leave empty. After " "a change of password, the user has to login again.", track_visibility='onchange'), 'google_calendar_rtoken': fields_v7.char('Refresh Token', track_visibility='onchange'), 'google_calendar_token': fields_v7.char('User token', track_visibility='onchange'), 'google_calendar_token_validity': fields_v7.datetime('Token Validity', track_visibility='onchange'), 'google_calendar_last_sync_date': fields_v7.datetime('Last synchro date', track_visibility='onchange'), 'google_calendar_cal_id': fields_v7.char('Calendar ID', help='Last Calendar ID who has been synchronized.\ If it is changed, we remove all links between GoogleID and\ Odoo Google Internal ID', track_visibility='onchange') } # Computed fields customer_visible_user_ids = fields.Many2many( comodel_name='res.users', relation='res_user_project_default_supporter_rel', column1='user_id', column2='supporter_id', compute=_get_customer_visible_user_ids, store=True, string="Visible Users for Customer") is_trobz_member = fields.Boolean(compute="_get_is_trobz_member", string='Trobz Member', store=True) is_external_dev = fields.Boolean(compute="_get_is_external_dev", string='External Dev?', store=True) employee_id = fields.Many2one(comodel_name='hr.employee', string='Related Employee', readonly=True) users_not_working_hour = fields.Char( compute='_get_users_not_working_hour', string='Users not input working hours') # Tracking Fields name = fields.Char(related='partner_id.name', inherited=True, track_visibility='onchange') employer_id = fields.Many2one('res.partner', 'Employer', ondelete='restrict', required=True, domain="[('is_company', '=', True)]", track_visibility='onchange') email = fields.Char('Email', track_visibility='onchange') # TODO: Discuss with Tu about renaming as _notif_pref_id to encourage the # use of get_notif_pref notif_pref_id = fields.Many2one('notification.preferences', string='Notification Preferences', track_visibility='onchange') login = fields.Char('Login', size=64, required=True, help="Used to log into the system", track_visibility='onchange') group_profile_id = fields.Many2one('res.groups', string='Profile Group', domain=[('is_profile', '=', True)], help='The profile group of a user \ which defines all the required \ groups for a user.', track_visibility='onchange') action_id = fields.Many2one( 'ir.actions.actions', 'Home Action', help="If specified, this action will be opened at log on " + " for this user, in addition to the standard menu.", track_visibility='onchange') active = fields.Boolean('Active', track_visibility='onchange') default_project_id = fields.Many2one('tms.project', 'Default Project', ondelete='restrict', track_visibility='onchange') send_support_status_mail = fields.Boolean('Send Support Status Mail', default=False, track_visibility='onchange') daily_hour = fields.Integer('Number of working hours per day', default=8, track_visibility='onchange') must_input_working_hour = fields.Boolean('Must Input Working Hours', track_visibility='onchange') # mac address field # Normal fields set_default_related_partner = fields.Boolean( 'Use Employer as Related Partner', default=False) has_full_sysadmin_access = fields.Boolean('Full Sysadmin Access', default=False, track_visibility='onchange') is_sysadmin = fields.Boolean('Is Sysadmin', default=False) default_supporter_of_project_ids = fields.One2many( 'tms.project', 'default_supporter_id', 'Default Supporter of Projects', help="Used when clicking on the button assign to Trobz.", track_visibility='onchange') supporter_of_project_ids = fields.Many2many( comodel_name='tms.project', relation='tms_project_supporter_rel', column1='user_id', column2='project_id', string="Supporter of Projects", help="Only those people would be listed in the fields Assignee," + "Reporter and Subscriber of the support ticket for.", track_visibility='onchange') subscriber_of_project_ids = fields.One2many( "project.subscriber", 'name', string="Default Subscriber of Projects", track_visibility='onchange') host_user_ids = fields.Many2many( comodel_name='tms.host', relation='host_users_rel', column1='user_id', column2='host_id', string="User of Hosts", help="Hosts where access through ssh will be granted " "(with user openerp).") instance_user_ids = fields.Many2many( comodel_name='tms.instance', relation='tms_instance_user_rel', column1='user_id', column2='instance_id', string="User of Instances", help="Instances where access through http authentication will be " "granted.") https_password = fields.Secure(string="HTTP Password", security="_security_https_password") https_password_hashed = fields.Secure( string="HTTP Password Hashed", security="_security_https_password", help="Use this hash to prepare htpasswd files on instances.") https_password_bcrypt_hashed = fields.Secure( string="HTTP Password Bcrypt Hashed", security="_security_https_password", help="Use this hash for Docker Authentication Gateway.") hide_calendar = fields.Boolean(compute='_compute_hide_calendar') external_project_ids = fields.Many2many( "tms.project", "tms_project_external_dev_rel", "user_id", "project_id", string="External Project", ) default_job_type_id = fields.Many2one(comodel_name='tms.job.type', string="Default Job Type") slack_user_id = fields.Char( string='Slack user ID', help="In Slack: View user profile > Copy member ID") tpm_project_ids = fields.One2many('tms.project', 'technical_project_manager_id', 'TPM of Projects', help="Projects which user is TPM.") @api.multi @api.onchange('set_default_related_partner') def onchange_set_default_related_partner(self): for user in self: if user.set_default_related_partner: user.partner_id = user.employer_id.id else: user.partner_id = False @api.multi @api.constrains('partner_id', 'login') def _constrains_partner_id(self): for user in self: users = self.search([('partner_id', '=', user.partner_id.id), ('id', '!=', user.id)]) if users: raise Warning( _('Warning'), _('The Related Partner of this User ' 'is already related to another User "%s". ' 'For specific case, you can create ' 'an additional Contact of the Employer ' '(example "http-account-2").' % users[0].login)) @api.multi def _security_https_password(self): # Only by users with profile Admin or users with "Full Sysadmin Access" current_user = self.env.user is_allow = False for rec in self: if current_user.group_profile_id and\ current_user.group_profile_id.name == 'Admin Profile' or\ current_user.has_full_sysadmin_access or\ rec.id == current_user.id: is_allow = True else: is_allow = False break return is_allow @api.constrains('has_full_sysadmin_access', 'group_profile_id') def _check_sysadmin_access(self): """ Cannot uncheck `Full Sysadmin Access` for user with Admin Profile """ # Cannot uncheck `Full Sysadmin Access` for user with Admin Profile if self.group_profile_id.name == 'Admin Profile' \ and not self.has_full_sysadmin_access: raise Warning( _('Cannot uncheck `Full Sysadmin Access` for users with ' 'Admin Profile. An Admin user always has ' '`Full Sysadmin Access`!')) @api.model def create(self, vals): """ Only SUPERUSER/Admin user can create a user with + `Full Sysadmin Access` checked + Admin Profile """ context = self._context and self._context.copy() or {} context.update({'reset_password': False}) self.with_context(context) current_user = self.env.user group_obj = self.env['res.groups'] if current_user.id != SUPERUSER_ID \ and current_user.group_profile_id.name != 'Admin Profile': if 'group_profile_id' in vals and \ group_obj.browse(vals['group_profile_id']).name \ == 'Admin Profile': raise Warning( _('Only Admin user can create a new user with' ' Admin Profile')) if vals.get('has_full_sysadmin_access'): raise Warning( _('Only Admin user can create a new user with ' '`Full Sysadmin Access` checked')) # F#12640 : Update Related User and company for Partner.contact # Get customer/prospect/supplier like company context.update({ 'write_tracking_fields': True, 'no_create_partner': vals.get('employer_id', False) }) new_user = super(ResUsers, self.with_context(context)).create(vals) partner_vals = {'related_user_id': new_user.id} new_user.partner_id.write(partner_vals) new_user._get_is_external_dev() return new_user # TODO: It does not seem correct to use the api.model here. # How can this work? self.default_supporter_of_project_ids @api.model def remove_user_from_project_support_subscriber(self): tms_subscriber_env = self.env['tms.subscriber'] project_subscriber_env = self.env['project.subscriber'] tms_forge_subscriber_env = self.env['tms.forge.subscriber'] # Remove this user from Supporters of projects if self.supporter_of_project_ids: self.supporter_of_project_ids = False if self.default_supporter_of_project_ids: self.default_supporter_of_project_ids.write( {'default_supporter_id': False}) # Remove Subscribers of support ticket is this users remove_tms_subscribers = tms_subscriber_env.search([('name', '=', self.id)]) if remove_tms_subscribers: remove_tms_subscribers.unlink() remove_tms_forge_subscribers = tms_forge_subscriber_env.search([ ('name', '=', self.id) ]) if remove_tms_forge_subscribers: remove_tms_forge_subscribers.unlink() remove_project_subscribers = project_subscriber_env.search([ ('name', '=', self.id) ]) if remove_project_subscribers: remove_project_subscribers.unlink() @api.model def get_notif_pref(self): ''' Returns the notification preference of the user or if not set from the profile of the user. At least one of the two must be defined. ''' np = self.notif_pref_id or self.group_profile_id.notif_pref_id or False msg = "A notification preference should be set on the user or " + \ "on the profile" assert np, msg return np @api.multi def change_reporter_for_support_ticket(self): tms_support_ticket_env = self.env['tms.support.ticket'] ctx = self._context and self._context.copy() or {} # prevent sending email for this case because many tickets will be # change and they will sent a bulk emails that they are same as spam. if not ctx.get('test_support_ticket', False): ctx.update({'test_support_ticket': True}) # select all tickets that they are reported by inactive users support_tickets = tms_support_ticket_env.search([('reporter_id', 'in', self.ids)]) logging.info("Change %d reporters for %d tickets." % (len(self.ids), len(support_tickets))) for spt in support_tickets: if spt.project_id.is_blocked: continue pm_id = spt.project_id.owner_id and \ spt.project_id.owner_id.id or False if not pm_id or pm_id not in\ spt.project_id.project_supporter_rel_ids.ids: continue spt.with_context(ctx).write({'reporter_id': pm_id}) return True @api.multi def write(self, vals): """ Only SUPERUSER/Admin user can update: + Full Sysadmin Access + Profile to Admin Profile """ context = self._context and self._context.copy() or {} group_obj = self.env['res.groups'] project_obj = self.env['tms.project'] current_user = self.browse(self._uid) default_project_id = vals.get('default_project_id', False) supporter_of_project_ids = vals.get('supporter_of_project_ids', []) for user in self: if 'email' in vals and not vals['email']: raise Warning( _('Forbidden action!', 'You must set an email for the user!')) if current_user.id != SUPERUSER_ID \ and current_user.group_profile_id.name != 'Admin Profile': if 'group_profile_id' in vals and \ group_obj.browse(vals['group_profile_id']).name \ == 'Admin Profile': raise Warning( _('Only Admin user can update the profile of user to' ' Admin Profile')) if 'has_full_sysadmin_access' in vals: raise Warning( _('Only Admin user can update `Full Sysadmin Access`')) # User without Admin profiles # only be able to change his password if 'new_password' in vals and \ current_user.id != SUPERUSER_ID and \ current_user.group_profile_id.name not in ( 'Admin Profile', 'Sysadmin Profile', 'Sysadmin Manager Profile', 'FC and Admin Profile') and \ current_user.id != user.id: raise Warning(_('You are able to change your password only.')) if 'slack_user_id' in vals and \ current_user.id != SUPERUSER_ID and \ current_user.group_profile_id.name not in ( 'Admin Profile', 'Sysadmin Profile', 'Sysadmin Manager Profile'): raise Warning( _('Only Admin user and Sysadmin can update "Slack user ID"' )) context.update({'write_tracking_fields': True}) if default_project_id: project = project_obj.browse(default_project_id) project_supporter_rel_ids = supporter_of_project_ids and \ supporter_of_project_ids[0] and \ supporter_of_project_ids[0][2] or \ user.supporter_of_project_ids.ids if project and project.id not in project_supporter_rel_ids: raise Warning( _('This user is not the supporter of project "%s"' % project.name)) if 'supporter_of_project_ids' in vals: new_ids = vals.get('supporter_of_project_ids', False)[0][2] old_ids = [ support_project.id for support_project in user.supporter_of_project_ids ] removed_ids = list(set(old_ids) - set(new_ids)) projects = project_obj.browse(removed_ids) # If user is Defalut Supporter of project, not allow remove # project out of Supporter list for project in projects: if user.default_project_id.id == project.id: raise Warning( _('Warning'), _('User %s must be a supporter of project %s' ' because it is his/her default project.' % (user.name, project.name))) default_project_id = default_project_id or \ user.default_project_id.id if default_project_id and \ default_project_id in removed_ids: vals.update({'default_project_id': False}) for project in projects: if user.id in [ project.technical_project_manager_id.id, project.tester_id.id ]: raise Warning( _('This user is TPM or Tester of %s project, ' 'can not remove this user out of supporter ' 'of %s project' % (project.name, project.name))) # write password hash if vals.get("login", False) or vals.get("https_password", False): user_name = vals.get("login", user.login) or False https_password = vals.get( "https_password", user.read_secure( fields=['https_password'])[0].get('https_password')) \ or False ht = HtpasswdFile() ht.set_password(user_name or '', https_password or '') vals.update({'https_password_hashed': ht.to_string()}) # write password bcrypt hash https_password_for_bcrypt = vals.get("https_password", False) if https_password_for_bcrypt: password_bcrypt_hash = bcrypt.hashpw( https_password_for_bcrypt.encode("utf-8"), bcrypt.gensalt()) vals.update( {'https_password_bcrypt_hashed': password_bcrypt_hash}) # update email information to employer_id(related_partner_id), # they are the same partner_id. Do not create a new partner if 'set_default_related_partner' in vals: if vals.get('set_default_related_partner'): if vals.get('employer_id'): vals.update({'partner_id': vals.get('employer_id')}) else: vals.update({'partner_id': user.employer_id.id}) context.update({ 'is_employer': True, 'old_related_partner_obj': user.partner_id }) else: context.update({'is_employer': False, 'user_id': user.id}) elif not user.set_default_related_partner and 'partner_id' in vals: context.update({ 'is_employer': True, 'old_related_partner_obj': user.partner_id, 'user_id': user.id }) elif user.set_default_related_partner and 'employer_id' in vals: vals.update({'partner_id': vals['employer_id']}) context.update({ 'is_employer': True, 'old_employer_obj': user.employer_id }) res = super(ResUsers, user.with_context(context)).write(vals) if vals.get('new_password', False): user.message_post(body='<div> •\ <b>Password has been changed</b>') if 'active' in vals and not vals.get('active', False): profile_tms_admin = self.env.ref( 'tms_modules.group_profile_tms_admin') profile_sys_admin = self.env.ref( 'tms_modules.group_profile_tms_sysadmin_manager') if self.env.user.group_profile_id.id in \ (profile_tms_admin.id, profile_sys_admin.id): user.sudo().change_reporter_for_support_ticket() user.sudo().remove_user_from_project_support_subscriber() else: user.change_reporter_for_support_ticket() user.remove_user_from_project_support_subscriber() return res @api.onchange('group_profile_id') def onchange_group_profile_id(self): """ - Update `is_sysadmin` and `has_full_sysadmin_access` according to selected profile. - Auto check `has_full_sysadmin_access` when change to Admin Profile """ self.is_sysadmin = self.group_profile_id.is_sysadmin if self.group_profile_id.name == 'Admin Profile': self.has_full_sysadmin_access = True elif not self.group_profile_id.is_sysadmin: self.has_full_sysadmin_access = False @api.onchange('employer_id') def onchange_employer_id(self): """ - If Use Employer as Related Partner is ticked, change Related Partner according to the new Employer. """ if self.set_default_related_partner: self.partner_id = self.employer_id and self.employer_id.id or False @api.model def get_email_list(self): """ @return email-to of email `[dev] Check Working Hour` """ result = '' no_days = self.env.ref('tms_modules.no_days_check_working_hour', raise_if_not_found=True) days = no_days and no_days.value or 40 sql = """ SELECT rus.id, rp.email FROM res_users rus JOIN res_partner rp ON rus.partner_id = rp.id WHERE rus.active = 't' AND rus.must_input_working_hour = 't' AND rus.is_trobz_member = 't' ORDER BY rus.id """ self._cr.execute(sql) for user in self._cr.fetchall(): day_not_enough = self.check_wh_n_day_past(user[0], days) if day_not_enough: result += user[1] + ',' return result @api.multi def get_public_holidays_last_n_days(self, days): pulic_holiday = [] today = datetime.now() lastdmonth = today - timedelta(days + 1) pulic_holidays = self.env['hr.public.holiday'].search([ ('date', '>=', lastdmonth), ('date', '<=', today), ('is_template', '=', False) ]) for phol in pulic_holidays: pulic_holiday.append(phol.date) return pulic_holiday @api.model def check_wh_n_day_past(self, user_id, days): """ Check missing working hours for a user with n days ago from now. """ days = int(days) working_hour_obj = self.env['tms.working.hour'] hr_employee_obj = self.env['hr.employee'] contract_obj = self.env['hr.contract'] result = [] pulic_holidays = self.get_public_holidays_last_n_days(days) hr_employees = hr_employee_obj.search([('user_id', '=', user_id)]) if not hr_employees: return ["Could not find any employee with user id %d" % (user_id)] if len(hr_employees) > 1: return [ "There are %s employee found with user id %d" % (len(hr_employees), user_id) ] employee_id = hr_employees[0].id today = datetime.now() contracts = contract_obj.search( [('employee_id', '=', employee_id), '|', '&', ('date_start', '<=', today), ('date_end', '=', False), '&', ('date_start', '<=', today), ('date_end', '>=', today)], order='date_start desc') if not contracts: return [ 'Cannot check working hours for this employee,' ' because no contract is defined.' ] contract = contracts[0] latest_contract_date = contract.date_start for i in range(0, days + 1): date = (datetime.now() - timedelta(i)) if date.weekday() in [5, 6] or \ date.strftime("%Y-%m-%d") in pulic_holidays: # We do not check working hours on public holidays nor weekend continue if latest_contract_date \ and date.strftime("%Y-%m-%d") < latest_contract_date: # Check working hours only after the current contract has # started continue wh_obj = self.env(self._cr, user_id, self._context) working_hour_in_today = \ working_hour_obj.with_env( wh_obj).get_daily_total_working_hour(date) number_hours_working_schedule = 0 if not contract.working_hours: return [ 'No working schedule found for the contract %s' % contract.name ] else: for attendance in contract.working_hours.attendance_ids: if str(date.weekday()) == attendance.dayofweek: number_hours_working_schedule += attendance.hour_to - \ attendance.hour_from if working_hour_in_today < number_hours_working_schedule: result.append( date.strftime("%d/%m") + ' (' + str(working_hour_in_today) + ' hours)') return result @api.model def _check_missing_working_hour(self, days): """ Go through the list of users, if there is one user_id who has some missing working hour, return True. """ users = self.search([('must_input_working_hour', '=', True), ('is_trobz_member', '=', True)]) for user in users: day_not_enough = self.check_wh_n_day_past(user.id, days) if day_not_enough: return True return False @api.model def send_email_remain_working_hour(self): """ """ no_days = self.env.ref('tms_modules.no_days_check_working_hour', raise_if_not_found=True) days = no_days and no_days.value or 40 if self._check_missing_working_hour(days): template = self.env.ref('tms_modules.tms_remain_wh_email_template') template._send_mail_asynchronous(1) return True @api.model def user_export_permitted(self): """ Use on front-end Get current user profile, will be called by the client side to hide Odoo export feature """ if self._context is None: self._context = {} record = self.sudo().browse(self._uid) # If current user is admin => overpass if self._uid == 1: return True # Customer profile are not allowed to use Openerp Export feature on # support ticket native_export_profiles = self.env['ir.config_parameter'].get_param( "native_export_profiles") native_export_profiles = eval(native_export_profiles) if record.group_profile_id.name in native_export_profiles: return False return True @api.model def name_search(self, name='', args=None, operator='ilike', limit=100): if not args: args = [] context = dict(self._context) # Filter the support ids on project form if context.get('filter_supporter_ids', False): supporter_ids = context['filter_supporter_ids'] if not supporter_ids: args = [('id', 'in', [])] else: args = [('id', 'in', supporter_ids)] project_id = context.get('project_id', False) project = self.env['tms.project'].search([('id', '=', project_id)]) new_args = ['|'] if project and project.project_supporter_rel_ids: new_args.extend(args) new_args.append(['id', 'in', list(project.external_dev_ids.ids)]) args = new_args ids = [] if name and operator in ['=', 'ilike']: ids = self.search( ['|', ('name', operator, name), ('login', operator, name)] + args, limit=limit) if not ids: ids = self.search([('name', operator, name)] + args, limit=limit) return ids and ids.name_get() or self.name_get() @api.model def fields_get(self, allfields=None, write_access=True, attributes=None): """ Override function To make the open chatter work on users """ if self._context is None: self._context = {} if self._context.get('write_tracking_fields', False): """ FIXME: (the one who refactor source code should notice) Currently res.users has some issue with open chatter so if we call directly super(models.SecureModel, self).fields_get we will get a strange error related to the group. for example: ``` field = self._fields[col_name] KeyError: 'in_group_52' ``` so temporary fix is to call from Model class directly which is from BaseModel, it should fix the error """ return super(models.Model, self).fields_get(allfields=allfields, write_access=write_access, attributes=attributes) return super(ResUsers, self).fields_get(allfields=allfields, write_access=write_access, attributes=attributes) @api.cr_uid_ids_context def message_post(self, cr, uid, thread_id, context=None, **kwargs): """ Override function To make the open chatter work on users """ if isinstance(thread_id, (list, tuple)): thread_id = thread_id[0] return mail.mail_thread.mail_thread.message_post( self, cr, uid, thread_id, **kwargs) @api.cr_uid_ids_context def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None): """ Override function To make the open chatter work on users """ if context is None: context = {} # not necessary for computation, but saves an access right check if not partner_ids: return True mail_followers_obj = self.pool.get('mail.followers') subtype_obj = self.pool.get('mail.message.subtype') user_pid = self.pool.get('res.users').browse( cr, uid, uid, context=context).partner_id.id if set(partner_ids) == set([user_pid]): try: self.check_access_rights(cr, uid, 'read') self.check_access_rule(cr, uid, ids, 'read') except (osv.except_osv, orm.except_orm): return False else: self.check_access_rights(cr, uid, 'write') self.check_access_rule(cr, uid, ids, 'write') existing_pids_dict = {} fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [ '&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids) ]) for fol in mail_followers_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context): existing_pids_dict.setdefault(fol.res_id, set()).add(fol.partner_id.id) # subtype_ids specified: update already subscribed partners if subtype_ids and fol_ids: mail_followers_obj.write(cr, SUPERUSER_ID, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context) # subtype_ids not specified: do not update already subscribed partner, # fetch default subtypes for new partners if subtype_ids is None: subtype_ids = subtype_obj.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context) for record_id in ids: existing_pids = existing_pids_dict.get(record_id, set()) new_pids = set(partner_ids) - existing_pids # subscribe new followers for new_pid in new_pids: mail_followers_obj.create( cr, SUPERUSER_ID, { 'res_model': self._name, 'res_id': record_id, 'partner_id': new_pid, 'subtype_ids': [(6, 0, subtype_ids)], }, context=context) return True @api.multi def get_secure_user_info(self): """ Get hashed of http auth password and login name """ user_lst = [] for user in self: httpauthpasswd = user.read_secure(fields=['https_password_hashed']) user_lst.append({ 'id': user.id, 'login': user.login, 'https_password_hashed': httpauthpasswd[0].get('https_password_hashed'), 'is_sysadmin': user.is_sysadmin and user.has_full_sysadmin_access }) return user_lst
class TmsInstance(models.SecureModel): _name = "tms.instance" _inherit = ['mail.thread'] _order = "name" _sql_constraints = [ ('instance_unique', 'unique (name)', 'This instance already exists!'), ('host_xmlrpc_unique', 'unique (host_id,xmlrpc_port)', 'This xmlrpc port already exists on that host!') ] # ======================================================================== # FIELD DEFINITIONS # ======================================================================== https_password = fields.Secure( # @UndefinedVariable string="HTTP Authen Test Password", security="_security_https_password") name = fields.Char('Instance Name', size=256, required=True, track_visibility='onchange') mail_instance = fields.Char(compute='_get_mail', string='Mail') project_id = fields.Many2one('tms.project', 'Project name', required=True, track_visibility='onchange') milestone_id = fields.Many2one( 'tms.milestone', 'Milestone', track_visibility='onchange', required=True) host_id = fields.Many2one('tms.host', 'Host', required=True, track_visibility='onchange') physical_host_id = fields.Many2one('tms.host', related='host_id.physical_host_id', string="Node", store=True) parent_bzr_repository_suffix = fields.Char( 'Parent bzr Repository Suffix', size=64, help='A possible value is "-stable" ' 'if we want to use the repositories ' 'in {project_name}-stable instead of ' 'the one in {project_name}.') server_type = fields.Selection( server_type_array, 'Server Type', required=True, track_visibility='onchange') url = fields.Char('URL', size=256, required=True, track_visibility='onchange') xmlrpc_port = fields.Char('xmlrpc port', size=256, track_visibility='onchange') psql_host = fields.Char('PostgreSQL Host', size=120, required=True, track_visibility='onchange', default='localhost') psql_port = fields.Char('PostgreSQL Port', size=120, required=True, track_visibility='onchange', default='5432') psql_user = fields.Char('PostgreSQL User', size=120, required=True, track_visibility='onchange') psql_pass = fields.Char('PostgreSQL Pass', size=120, required=True, track_visibility='onchange') is_set_up_domain = fields.Boolean( 'Domain', help='Create the instance for: {projectname}.tms.com') is_set_up_http_authentication = fields.Boolean( 'Http Authentication', help='Set http authentication to ' '{projectname}.tms.com to (denis, jc, ' 'tam, TPM,{projectname})') is_set_up_https = fields.Boolean('https') is_set_up_xmlrpc = fields.Boolean('xmlrpc') is_set_up_ssh_access = fields.Boolean('ssh access', help='Set ssh access & ' 'passwordless to ' '{projectname}.tms.com to ' '(denis, jc, TPM,{projectname})') is_project_manager = fields.Boolean( compute='compute_is_project_manager', string='Is Project Manager') state = fields.Selection( instance_state_array, string='Status', required=True, default='active', help='Sleep: the instance is active but is facing temporary ' 'issues not under our control. Inactive: This instance will ' 'not be used anymore. Deleted: This instance has been removed ' 'from our servers.', track_visibility='onchange' ) datetime_test = fields.Datetime('Last Test', readonly=True) note = fields.Text('Note') last_error = fields.Text('Last Error') operating_system = fields.Char(compute='compute_operating_system', method=True, string="Operating System", store=True) ssh_port = fields.Char(compute='compute_ssh_port', string="SSH Port", store=True) custom_parameter = fields.Serialized('Custom parameter', track_visibility='onchange') instance_database_ids = fields.One2many('instance.database', 'tms_instance_id', string='Instance Database') active = fields.Boolean('Active', default=True, help="When unchecked, the instance will not " "be visible in the user interface unless the " "search filters specify that you want to " "display non-active records (this a native " "behavior of Odoo)") test_instance = fields.Selection( test_instance_array, default='access_login', required=True, string='Test instance', help="- Access only: Test if instance is up by connecting xmlrpc " "and test database names\n" "- Access and login: Test if instance is up by connecting xmlrpc " "and can be login into admin account and test database names\n" "- None: Don't test status of the instance, " "don't check the database names.", track_visibility='onchange' ) xmlrpc_url = fields.Char(compute='compute_xmlrpc_url', string="XML-RPC URL") https_login = fields.Char('HTTP Authen Test Login', size=64) proj_owner_id = fields.Many2one('res.users', related='project_id.owner_id', string="Project's Owner", store=True) team_id = fields.Many2one(string='Team', related='project_id.team_id', store=True) team_manager_id = fields.Many2one( string="Team Manager", related='project_id.team_id.team_manager', store=True) backend_ip = fields.Char('Backend IP') backend_port = fields.Char('Backend Port', default='8069') ssl = fields.Boolean( 'SSL', default=True, help="SSL termination is handled by nginx" ) http_auth = fields.Boolean('HTTP Auth', default=True) htpasswd_file = fields.Char('htpasswd file') instance_user_ids = fields.Many2many( comodel_name='res.users', relation='tms_instance_user_rel', column1='instance_id', column2='user_id', string="Users of Instance", help="list users who can access to this instance " "( to generate htpasswd file)") multi_host = fields.Boolean(string="is Multi-host?", track_visibility='onchange') haproxy_host_id = fields.Many2one('tms.host', string="HA Proxy Host", track_visibility='onchange') front_end_ids = fields.Many2many( comodel_name='tms.host', relation="tms_instance_host_front_end_rel", column1="instance_id", column2="host_id", string="Front End", track_visibility='onchange') back_end_ids = fields.Many2many( comodel_name='tms.host', relation="tms_instance_host_back_end_rel", column1="instance_id", column2="host_id", string="Back Office", track_visibility='onchange') database_ids = fields.One2many( 'multi.host.database', 'instance_id', string="Databases", track_visibility='onchange') nfs_host_id = fields.Many2one( 'tms.host', string="NFS Host", track_visibility='onchange') awx_job_history_ids = fields.One2many( comodel_name='tms.awx.job.history', inverse_name='instance_id', string='AWX Job History') @api.multi def write(self, vals): res = super(TmsInstance, self).write(vals) if vals.get('database_ids', False): for instance in self: databases = instance.database_ids master_no = len(databases.filtered('master')) if master_no > 1: raise Warning('ONLY ONE DATABASE CAN BE SET AS MASTER') return res # ======================================================================== # COMPUTE FUNCTION DEFINITIONS # ======================================================================== @api.multi def _get_mail(self): mail_content = '' numb = 1 # Only check on active instances with databases defined domain_build = [ ('state', 'in', ['active']), ('instance_database_ids', '!=', False), ('test_instance', '=', 'access_login') ] instances = self.search(domain_build) logging.info('{0} instance(s) have databases need ' 'to be compared'.format(len(instances))) for instance in instances: list_db_in_instance, list_db_in_tms_instances = \ self.get_lst_db_instance(instance) if not list_db_in_instance and not list_db_in_tms_instances: continue mail_content += '<div><b>' + str(numb) + '.' mail_content += instance.name + '</b>:</div>' if list_db_in_instance and list_db_in_tms_instances: mail_content += '<ul>' mail_content += '<li>Database(s) in instance:' + \ ','.join(list_db_in_instance) + '</li>' mail_content += '<li>Database(s) in TMS:' + \ ','.join(list_db_in_tms_instances) + '</li>' mail_content += '</ul>' else: mail_content += '<ul>' mail_content += "<li><font color=red>Cannot access" +\ " the instance!</font></li>" mail_content += '</ul>' numb += 1 for user in self: user.mail_instance = mail_content @api.model def get_lst_db_instance(self, instance): # Get instance address to test - including port # Get the https login and password https_login = instance.https_login or 'guest' https_password = instance.read_secure( fields=['https_password'])[0].get('https_password', 'n0-@pplY') inject_idx = instance.xmlrpc_url.find('://') uri_base = '%s://%s:%s@%s' % (instance.xmlrpc_url[:inject_idx], https_login, https_password, instance.xmlrpc_url[inject_idx + 3:]) # List to store database for each instances and in TMS instance_databases_list = [] # Databases configured for instance in TMS tms_instances_databases_list = [ db_info.name for db_info in instance.instance_database_ids] # log in uri message = '' error_stack = [] try: # Link to instance's db service, should be: # http://<user>:<pass>@<host-name>:<port>/xmlrpc/db # can be changed by later versions (odoo) conn = xmlrpclib.ServerProxy(uri_base + '/xmlrpc/db') instance_databases_list = conn.list() except Exception as e: logging.warning('Failed at sock common') message += 'Warning: Failed at sock common\n' error_stack.append(e) return [], [] # Compare 2 database list before calling check_database to improve the # performance tms_instances_databases_list = sorted(tms_instances_databases_list) instance_databases_list = sorted(instance_databases_list) compare_list = set(tms_instances_databases_list).symmetric_difference( instance_databases_list) if not compare_list: return [], [] list_db_in_tms_instances = self.check_database( tms_instances_databases_list, instance_databases_list ) list_db_in_instance = self.check_database( instance_databases_list, tms_instances_databases_list ) return list_db_in_instance, list_db_in_tms_instances @api.multi def compute_is_project_manager(self): user = self.env["res.users"].browse(self._uid) user_group_ids = [group.id for group in user.groups_id] pm_group_ids = self.env["res.groups"].search( [('name', '=', 'TMS Activity Viewer')]) if pm_group_ids and user_group_ids: for record in self: record.is_project_manager = pm_group_ids[0].id in \ user_group_ids @api.multi @api.depends( "host_id", "host_id.operating_system_id", "host_id.operating_system_id.name") def compute_operating_system(self): for instance in self: instance.operating_system = \ instance.host_id \ and instance.host_id.operating_system_id \ and instance.host_id.operating_system_id.name \ or False @api.multi @api.depends( "host_id", "host_id.port") def compute_ssh_port(self): for instance in self: ssh_port = instance.host_id and instance.host_id.port or False instance.ssh_port = ssh_port @api.multi def compute_xmlrpc_url(self): for instance in self: # Normal authentication, use custom xmlrpc port if instance.xmlrpc_port: uri_base = '%s:%s' % (instance.url, instance.xmlrpc_port) if uri_base[:4] != 'http': uri_base = 'http://%s' % uri_base # 8113: Special uri for the instances which require https # authentication else: uri_base = '%s:443' % instance.url if uri_base[:5] != 'https': uri_base = 'https://' + uri_base instance.xmlrpc_url = uri_base # ======================================================================== # ONCHANGE FUNCTION DEFINITIONS # ======================================================================== @api.onchange('server_type') def on_change_server_type(self): for instance in self: project = instance.project_id server_type = instance.server_type if not project or not server_type: return {} name = 'openerp-%s-%s' % (project.name, server_type) if server_type == 'production': url = '%s.trobz.com' % (project.name) else: url = '%s-%s.trobz.com' % (project.name, server_type) instance.name = name instance.url = url instance.psql_user = name instance.psql_pass = name # F#13799 - htpasswd_file field on instance should be editable if project and server_type: htpasswd_file = '/usr/local/var/auth/htpasswd_%s_%s' %\ (project.name, server_type) instance.htpasswd_file = htpasswd_file @api.onchange('state') def on_change_state(self): for instance in self: if instance.state in ('inactive', 'deleted'): instance.active = False else: instance.active = True @api.onchange('project_id') def on_change_project_id(self): for instance in self: project = instance.project_id server_type = instance.server_type # F#13799 - htpasswd_file field on instance should be editable if project and server_type: htpasswd_file = '/usr/local/var/auth/htpasswd_%s_%s' %\ (project.name, server_type) instance.htpasswd_file = htpasswd_file @api.onchange('host_id') def on_change_host(self): for instance in self: host = instance.host_id if host: instance.backend_ip = '10.26.%d.y' % host.container_id instance.ssh_port = host.port else: instance.backend_ip = False instance.ssh_port = False # ======================================================================== # FORM BUTTON FUNCTION DEFINITIONS # ======================================================================== @api.multi def button_request_in_ticket(self): # FIXME: should we consider to remove this function, this is not used ticket_pool = self.env['tms.ticket'] for instance in self: ticket_pool.create({ 'summary': u'Configure the instance {0}'.format(instance.name) }) # ======================================================================== # Daily Check List Instance Databases scheduler (email: Test List DB) # ======================================================================== @api.model def check_database(self, list_check, list_to_check): RENDER_COLOR = '<font color=red>%s</font>' list_new = [] for i in list_check: if i not in list_to_check: i = RENDER_COLOR % i list_new.append(i) return list_new @api.model def run_scheduler_compare_instance_in_tms_and_database(self): logging.info('[Scheduler] [Start] Compare list of databases in TMS ' 'and in instances') domain_build = [ ('state', 'in', ['active']), ('instance_database_ids', '!=', False), ('test_instance', '!=', 'none') ] instances = self.search(domain_build) for instance in instances: list_db_in_instance, list_db_in_tms_instances = \ self.get_lst_db_instance(instance) if not list_db_in_instance and not list_db_in_tms_instances: continue # send notification email template = self.env.ref( 'tms_modules.daily_instances_db_template' ) template._send_mail_asynchronous(instance.id) logging.info('[Scheduler] [End] Compare list of databases ' 'in TMS and in instances') return True # ======================================================================== # Test Instances Scheduler (email: Instance Down Mail) # ======================================================================== @api.model def run_test_instance_scheduler(self): logging.info("run_test_instance_scheduler: start") try: self.button_test_all_instances() except Exception as e: logging.error("Error in run_test_instance_scheduler: %s" % str(e)) logging.info("run_test_instance_scheduler: end") return True @api.multi def button_test(self): """ Test instance. """ logging.info('Entered button_test of tms_instance ' '(to test availability of instances)...') # Number of attempts and Sleep time between each attempt NB_ATTEMPS = 2 TIME_SLEEP = 3 error_stack = [] # message for test instance log message = '' for instance in self: message = '' vals = {} # get the db_login, db_password and db_name # from instance database field db_login = '******' if instance.test_instance == 'access_login': # check if a database is created for this instance if not (instance.instance_database_ids and instance.instance_database_ids.ids or False): logging.warning( 'No database defined for the instance %s.' % instance.name ) continue # only check the first database in the list instance_db = instance.instance_database_ids and \ instance.instance_database_ids[0] or False if instance_db: # extract db name and password from first line db_name = instance_db.name db_password = instance_db\ .read_secure(fields=['password'])[0]\ .get('password', 'n0-@pplY') else: db_name = 'unnamed' db_password = '******' # Get the https login and password (from instance) https_login = instance.https_login or 'guest' https_password = instance\ .read_secure(fields=['https_password'])[0]\ .get('https_password', 'n0-@pplY') inject_idx = instance.xmlrpc_url.find('://') uri_base = '%s://%s:%s@%s' % (instance.xmlrpc_url[:inject_idx], https_login, https_password, instance.xmlrpc_url[inject_idx + 3:]) # To remove the https_login_pass from the instance down message https_real_pass = '******' % https_password https_replace_pass = '******' logging.info('Checking ' + instance.xmlrpc_url) for i in range(NB_ATTEMPS): instance_exc = None state = 'active' # Try to reach the instance try: sock_common = xmlrpclib.ServerProxy( '%s/xmlrpc/common' % uri_base) except Exception as e: logging.warning('Failed at sock common') message += 'Warning: Failed at sock common\n' state = 'exception' instance_exc = str(e).replace( https_real_pass, https_replace_pass ) # Try to login at the instance try: if instance.test_instance == 'access_login': logging.info( 'Trying to login as Admin into instance %s...' % instance.name) message += 'Info: Trying to login as Admin into '\ 'instance %s...\n' % instance.name connection = sock_common.login( db_name, db_login, db_password) if not connection: raise Exception( 'Could not login to the instance %s' % instance.name) else: # reset state and message then break, no more try # after successful check state, message = 'active', '' break elif instance.test_instance == 'access': try: connection = sock_common.login( 'test_access_only', 'test', 'test') except Exception as e: if 'FATAL: database "test_access_only" ' +\ 'does not exist' in str(e): state, message = 'active', '' break else: raise Exception( 'Could not connect to the instance %s' % instance.name) except Exception as e: # TODO: to remove the password from the exception exc_str = str(e).replace(https_real_pass, https_replace_pass) logging.warning('ATTEMPT FAILED: Could not connect ' 'to the instance %s: %s' % (instance.name, exc_str)) message += 'Warning: ATTEMPT FAILED: Could not connect '\ 'to the instance %s: %s' % (instance.name, exc_str) state = 'exception' instance_exc = exc_str finally: sock_common = None if state == 'exception': logging.warning('ATTEMPT FAILED: Tried %s time(s), ' 'trying again ...' % (i + 1,)) if i < NB_ATTEMPS - 1: time.sleep(TIME_SLEEP) elif i == NB_ATTEMPS - 1: error_stack.append(instance_exc) # after N times to check on the instance ==> if instance down if state == 'exception': logging.error('The instance %s is down !!!' % instance.name) message += 'Error: The instance %s is down !!!\n'\ % instance.name logging.error('Could not connect to the instance %s after %s ' 'attempts!!' % (instance.name, NB_ATTEMPS)) message += 'Error: Could not connect to the instance %s '\ 'after %s attempts!!\n' % (instance.name, NB_ATTEMPS) logging.error('uri_base: %s, db_name: %s, db_login: %s, ' 'db_password: ****' % (instance.xmlrpc_url, db_name, db_login)) message += 'Error: uri_base: %s, db_name: %s, '\ 'db_login: %s, db_password: ****\n' % (instance.xmlrpc_url, db_name, db_login) logging.error(error_stack) vals['last_error'] = message state = 'exception' else: logging.info( 'Connect to the instance %s successful!' % instance.name) state = 'active' vals['last_error'] = False vals.update({ 'state': state, 'datetime_test': datetime.now() }) logging.info('Writing new data to current instance: %s' % vals) instance.write(vals) # write test data to instance @api.model def button_test_all_instances(self): """ Test instance. """ logging.info('Entered button_test of tms_instance ' '(to test availability of instances)...') instances = self.search( [('state', 'in', ('active', 'exception')), ('test_instance', '!=', 'none')] ) for instance in instances: instance.button_test() # commit the write transaction (before sending email) self.env.cr.commit() down_instances = self.search([ ('state', '=', 'exception'), ('test_instance', '!=', 'none') ]) if down_instances and down_instances.ids: # context should be passed in email process context = {'instances_down_ids': down_instances.ids} # send email to inform number of down instances email_template = self.env.ref( 'tms_modules.tms_instance_down_mail_template' ) email_template.with_context(context).send_mail( down_instances[0].id) logging.info('Leaving button_test of tms_instance ' '(to test availability of instances)...') @api.multi def get_mail_down_instances_subject(self): """ this function will be called by email template to prepare the subject of the email """ context = self._context and self._context.copy() or {} # build domain to get only down instances domain = [ ('state', '=', 'exception'), ('test_instance', '!=', 'none') ] if context.get('instances_down_ids'): domain.append(('id', 'in', context.get('instances_down_ids'))) # get down instances down_instances = self.search(domain) if not down_instances.ids: return 'No instance down' nb_production_instance_down = 0 nb_instance_down = 0 for down_instance in down_instances: nb_instance_down += 1 if down_instance.name[-11:] == '-production': nb_production_instance_down += 1 if nb_production_instance_down == 0: return '%s Instance(s) down, none from production' %\ str(nb_instance_down) return '%s Instance(s) down, including %s from production' %\ (str(nb_instance_down), str(nb_production_instance_down)) @api.multi def get_mail_down_instances(self): """ # Ticket #1075 Send list of down instances. """ context = self._context and self._context.copy() or {} domain = [('state', '=', 'exception'), ('test_instance', '!=', 'none')] if context.get('instances_down_ids'): domain.append(('id', 'in', context.get('instances_down_ids'))) down_instances = self.search(domain) if not down_instances: return 'No instance down' config_pool = self.env['ir.config_parameter'] base_url = config_pool.get_param( 'web.base.url', default='https://tms.trobz.com') base_url = u'{0}#model=tms.instance&id='.format(base_url) # 3051 list_instances_production, list_instances_down = [], [] # Get name of down instances - classified through instance type for instance in down_instances: if "production" in instance.name: list_instances_production.append(instance.name) else: list_instances_down.append(instance.name) # predefined mail templates and mail contents mail_content = u'' list_template = u"<ol>{0}</ol>" details_template = u"<li>{0}: {1}</li>" instance_down_contents, instance_down_details = u"", u"" # compose list of down production instance names for instance in list_instances_production: instance_down_contents += u'<li>{0} down</li>'.format(instance) # compose list of down staging/integration instances name for instance in list_instances_down: instance_down_contents += u'<li>{0} down</li>'.format(instance) # compose list of down instance names mail_content += list_template.format(instance_down_contents) # detailed information section for down instances mail_content += u'More details:<br />' # information to be displayed in details, read from database for instance in down_instances: # name of the down instance first - followed by the details instance_down_details += u'<li style="margin-bottom: 15px;">' instance_down_details += u'<span>{0} down</span>'.format( instance.name) # begin detailed information instance_down_details += u'<ul style="padding-left: 20px;">' instance_down_details += details_template.format( "URL", instance.url) instance_db_list = [ db_info.name for db_info in instance.instance_database_ids] instance_down_details += details_template.format( "Database info", str(instance_db_list)) instance_down_details += details_template.format( "XML-RPC port", instance.xmlrpc_port) instance_down_details += details_template.format( "Last error", escape(instance.last_error)) instance_down_details += details_template.format( "Link to TMS", base_url + str(instance.id)) instance_down_details += u'</ul>' # end detailed information instance_down_details += u'</li>' # compose list of down instance details mail_content += list_template.format(instance_down_details) return mail_content @api.multi def get_sysadmin_tpm_email_list(self): """ Get list of users who are active Trobz members and have jobs in (Manager, Technical Project Manager) and have departments in (Management, OpenErp). """ departments = self.env['hr.department'].search( [('name', 'in', ('Management', 'OpenErp', 'Sysadmin'))]) if not departments: return '[email protected],[email protected]' jobs = self.env['hr.job'].search([ ('name', 'in', ('Manager', 'Technical Project Manager', 'System Admin'))]) if not jobs: return '[email protected],[email protected]' employee_obj = self.env['hr.employee'] employees = employee_obj.search( [('job_id', 'in', jobs.ids), ('department_id', 'in', departments.ids)]) if not employees: return '[email protected],[email protected]' mail_list = ['*****@*****.**'] for employee in employees: if employee and employee.user_id\ and employee.user_id.active\ and employee.user_id.is_trobz_member\ and employee.user_id.email: mail_list.append(employee.user_id.email) if mail_list: all_mail = ",".join(mail_list) + ",[email protected]" else: all_mail = '[email protected],[email protected]' return all_mail @api.multi def get_admin_tpm_sysadmin_email(self): result = "" res_users_obj = self.env['res.users'] res_groups_obj = self.env['res.groups'] admin_tpm_profs = res_groups_obj.search( [('name', 'in', ('Admin Profile', 'Sysadmin Profile', 'Technical Project Manager Profile')), ('is_profile', '=', True)]) res_users = res_users_obj.search( [('group_profile_id', 'in', admin_tpm_profs.ids)]) mail_list = [] for user in res_users: if user.email: mail_list.append(user.email) if mail_list: result = ",".join(mail_list) else: result = '*****@*****.**' return result # ======================================================================== # OTHER FUNCTIONS # ======================================================================== @api.model def _check_password_security(self, instance): """ @param instance: recordset tms.instance @return: - Admin profile, return True - TPM/FC profiles and in the supporters, return True - The rest, return False """ if self._uid == SUPERUSER_ID: return True config_pool = self.env['ir.config_parameter'] user_profile = self.env.user.group_profile_id # If this user is a Sysadmin and has full access if user_profile.is_sysadmin and self.env.user.has_full_sysadmin_access: return True # If this user is a Sysadmin but don't have full access # check if he is in the list of users of the host of this instance if user_profile.is_sysadmin\ and self.env.user.id in instance.host_id.user_ids.ids: return True # If this user is not a sysadmin, he must be in the list of supporters db_instance_profiles = config_pool.get_param( 'db_instance_profiles') db_instance_profiles = safe_eval(db_instance_profiles) if user_profile.name not in db_instance_profiles: return False project_supporters = instance and\ instance.project_id.project_supporter_rel_ids.ids\ or [] if self.env.user.id not in project_supporters: return False return True @api.multi def _security_https_password(self): """ Only allow Admin/TPM or FC update read/update password field. """ is_allow = False for rec in self: if self._check_password_security(rec): is_allow = True else: is_allow = False break return is_allow @api.model def run_get_module_quality_check_scheduler(self): instance_ids = self.search([ ('state', '=', 'active')] ) self.button_module_quality_check(instance_ids) self.env['email.template']._send_mail_asynchronous( self._uid, 'Module Quality Check Result') return True def _build_result_detail(self, result, result_details): detail_dict = {} for detail in result_details: module_name = result[detail['quality_check_id'][0]]['name'] module_score = result[detail['quality_check_id'][0]]['final_score'] if module_name not in detail_dict: detail_dict[module_name] = [ u'<li> Module {0}: {1}</li>'.format( module_name, module_score) ] result_detail = u'Last update: {0}<br/>'.format(datetime.now())\ + u'<li>Modules: </li><ul>' for detail in detail_dict.values(): detail_str = u'<br /> '.join(tuple(detail)) result_detail = str(result_detail) + detail_str return result_detail @api.model def migrate_database_instance(self): logging.info('==== START migrate database instance ====') tms_instance_datas = self.search([]) instance_db_env = self.env['instance.database'] for data in tms_instance_datas: if data.databases: databases = data.databases.split(",") for database in databases: dict_data = database.split(":") vals = {'name': dict_data[0], 'tms_instance_id': data.id} new_data = instance_db_env.create(vals) if len(dict_data) > 1: vals.update({'password': dict_data[1]}) new_data.secure_write(vals) logging.info('==== END migrate database instance ====') return True @api.multi def get_databases_list(self): """ Get a list of databases for each given instance. """ databases_lst = {} # {instance: list of databases} for instance in self: db_names = [db_info.name for db_info in instance.instance_database_ids] databases_lst.update({ instance.name: db_names }) return databases_lst @api.multi def get_instance_info(self, context=None): """ Get cleartext password of a database. """ databases_lst = {} for instance in self: instance_db = instance.instance_database_ids and \ instance.instance_database_ids[0] or False if instance_db: db_name = instance_db.name db_password = instance_db.read_secure(fields=['password']) databases_lst.update({ instance.name: [ instance.state, instance.server_type, db_name, db_password[0]['password']] }) return databases_lst
class instance_database(models.SecureModel): _name = "instance.database" _description = "Instance Database" name = fields.Char("Database Name", size=256, required=1) password = fields.Secure(string="Password", security="_password_security") login = fields.Char("Login", default="admin") tms_instance_id = fields.Many2one('tms.instance', string='TMS Instance', select=1, required=1) @api.model def create(self, vals): """ Override function Raise warning in case the current user is not Admin or TPM/FC profiles and in the supporters """ instance_env = self.env['tms.instance'] instance_id = vals.get('tms_instance_id') instance = instance_env.browse(instance_id) read_update_password = instance_env._check_password_security(instance) if not read_update_password: raise Warning( _('Only user with Admin profile or user with TPM/FC profiles' ' who exists in the supporters' ' of the project linked to this instance' ' can add/remove/update database recored')) return super(instance_database, self).create(vals) @api.multi def unlink(self): """ Override function Raise warning in case the current user is not Admin or TPM/FC profiles and in the supporters """ instance_env = self.env['tms.instance'] for line in self: read_update_password = instance_env._check_password_security( line.tms_instance_id) if not read_update_password: raise Warning( _('Only user with Admin profile or user with TPM/FC profiles' ' who exists in the supporters' ' of the project linked to this instance' ' can add/remove/update database recored')) return super(instance_database, self).unlink() @api.multi def _password_security(self): """ Only allow Admin/TPM or FC update read/update password field. """ instance_env = self.env['tms.instance'] is_allow = False for rec in self: if instance_env._check_password_security(rec.tms_instance_id): is_allow = True else: is_allow = False break return is_allow
class HrAppraisal(models.SecureModel): _inherit = "hr.appraisal" interview_result = fields.Secure( string="Interview result", security='_password_security_interview_result', password=False) salary_information = fields.Secure( string="Salary information", security='_password_security_salary', help="For the Manager to record employees expectations") @api.multi def _password_security_salary(self): """ HR manager and the Manager of the employee can see and edit Salary Information """ employee_env = self.env['hr.employee'] user_env = self.env['res.users'] employee_obj = employee_env.search([('user_id', '=', self._uid)]) is_allow = False for rec in self: if user_env.has_group('base.group_hr_manager') or \ employee_obj.id == rec.manager_id.id: is_allow = True else: is_allow = False break return is_allow @api.multi def _password_security_interview_result(self): """ Interview Result - HR manager and the Manager of the employee can see and edit - Employee can see """ employee_env = self.env['hr.employee'] user_env = self.env['res.users'] employee_obj = employee_env.search([('user_id', '=', self._uid)]) is_allow = False for rec in self: if user_env.has_group('base.group_hr_manager') or \ employee_obj.id == rec.manager_id.id or \ employee_obj.id == rec.employee_id.id: is_allow = True else: is_allow = False break return is_allow @api.multi def write(self, vals): """ Security: Only HR manager and Employee Manager can edit appraisal Chatter: Change message (NOT show encrypted string) """ if vals and not self._password_security_salary(): # interview_result without password confirmation # So Check here to avoid employee edit this field. raise Warning( _("Only HR manager and manager of employee " "can update this appraisal")) if 'interview_result' in vals: self.message_post(body=_('The interview result has been update')) if 'salary_information' in vals: self.message_post(body=_('The salary information has been update')) return super(HrAppraisal, self).write(vals)