Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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)
Beispiel #5
0
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
Beispiel #6
0
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> &nbsp; &nbsp; &bull; <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> &nbsp; &nbsp; &bull; <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
Beispiel #7
0
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> &nbsp; &nbsp; &bull;\
                     <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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
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)