class A(models.Model): _name = 'model.a' add_char = fields.Char() add_text = fields.Text() price_int = fields.Integer() cost_float = fields.Float() min_int = fields.Integer() max_int = fields.Integer() active = fields.Boolean(default=True) parent_id = fields.Many2one('model.a', 'Parent') meldb_ids = fields.Many2many(comodel_name='model.b', column1='meld_column2_id', column2='meld_column1_id', string='relation')
class CommonThreadWizard(models.TransientModel): _name = 'common.thread.wizard' model_id = fields.Selection( string='Model', selection=lambda self: get_model_selection(self), required=True) view = fields.Many2one('xopgi.selectable.view') views_count = fields.Integer(compute='_get_views_count') @api.onchange('model_id') @api.depends('model_id') def _get_views_count(self): selectable_view = self.env['xopgi.selectable.view'] for wizard in self: if wizard.model_id: conf_views = selectable_view.get_views(self.model_id) views_count = len(conf_views) self.views_count = views_count if views_count >= 1: self.view = conf_views[0] else: self.views_count = 0 self.view = False def get_thread_action(self, res_id=None): """ Returns the action that shows the form of this model """ return self.view.get_action(model=self.model_id, res_id=res_id)
class XopgiBoardWidgetRel(models.AbstractModel): _name = WIDGET_REL_MODEL_NAME _order = 'priority' widget = fields.Many2one(WIDGET_MODEL_NAME, delegate=True, required=True, ondelete='cascade') priority = fields.Integer(default=1000) def get_widgets(self): """ Get all widget dicts for uid and sorts them by priority. """ models = self.get_widget_capable_models() widgets = sorted(itertools.chain( *[list(model.get_user_widgets()) for model in models]), key=operator.attrgetter('priority')) result = [] # Adding missing widget for widget in widgets: widget.get_set_widgets(result) return result def get_user_widgets(self): """ It must be implemented on extended models. Should return a recordset of user's corresponding widgets. """ raise NotImplementedError() def get_widget_capable_models(self): """ Get a list of models instances that have `get_user_widgets` item """ result = [] for model in self.env.registry.values(): if hasattr(model, "get_user_widgets"): if model._name != WIDGET_REL_MODEL_NAME: result.append(self.env[model._name]) return result def get_set_widgets(self, result): """ Update in-place result adding missing widgets. """ for widget in self.read(fields=[ 'name', 'category', 'template_name', 'xml_template', 'python_code' ]): widget.pop('id', None) if widget not in result: result.append(widget)
class SelectableView(models.Model): _name = 'xopgi.selectable.view' _rec_name = 'label' _order = 'priority' label = fields.Char(translate=True, required=True) model_id = fields.Selection( string='Model', selection=lambda self: get_model_selection(self), required=True) view = fields.Many2one('ir.ui.view', required=True) priority = fields.Integer(default=16) def get_views(self, model): domain = [('model_id', '=', model)] return self.search(domain) def get_action(self, model=None, target='current', res_id='None'): """ Return an ir.actions.act_window """ res = dict(target=target) if self: view = self[0] model = view.model_id if res_id is not None: # If the recordset is empty in Odoo8 it returns an empty list.In # odoo10 gives error. values_action = self.env[model].browse(res_id).get_access_action() # If values_action contains a list it is because get_acess_action # was executed #in odoo8 and returns a [{}], in odoo10 returns a {}. if isinstance(values_action, list): values_action = values_action[0] res = dict(values_action, **res) else: values = { 'type': 'ir.actions.act_window', 'res_model': model, 'view_type': 'form', 'view_mode': 'form', 'views': [(self.view.id, 'form')], 'context': self._context } res = dict(values, **res) return res @api.multi def try_selected_view(self): return self.get_action(target='new')
class ir_model(models.Model): _inherit = 'ir.model' _sql_constraints = [ ('positive_merge_limit', 'check (merge_limit >= 0)', 'The limit quantity of objects to allow merge at one time must be ' 'positive number!'), ] object_merger_model = fields.Boolean( 'Object Merger', help=('If checked, by default the Object Merger configuration will ' 'get this module in the list')) merge_limit = fields.Integer( 'Merge Limit', default=0, help='Limit quantity of objects to allow merge at one time.') field_merge_way_ids = fields.One2many( 'field.merge.way.rel', 'model', string='Specific Merge Ways', help='Specify how to merge the fields') @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): if (view_type == 'form' and self._context.get('object_merger_settings', False)): _, view_id = self.env['ir.model.data'].get_object_reference( 'xopgi_object_merger', 'view_ir_model_merge_form') res = super(ir_model, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) return res @api.requires_singleton def _merge(self, sources, target): if self.field_merge_way_ids: values = self.field_merge_way_ids.meld(sources, target) else: values = {} if values: target.write(values)
class AliasMockerMixin(models.AbstractModel): '''A mixin that mocks mail.alias. True implementations will always read the data from the 'mail.alias' model. A virtual mail alias is simply made available so that other parts of the systems (views, most importantly) are easily made. A virtual alias is by all means a 'mail.alias' that gains other attributes via the 'alias_defaults' dictionary. Proposed usage:: >>> class project_valias(models.Model): ... _auto = False ... _name = 'project.valias' ... _inherit = ['xopgi.mail.alias.mocker'] ... ... project_id = fields.Many2one('project.project', string='Project') Then the 'project_id' attribute of 'project.valias' will be the key 'project_id' in the 'alias_default' attribute of any 'mail_alias'. ''' _name = 'xopgi.mail.alias.mocker' alias_name = fields.Char( 'Alias', required=True, help=("The name of the email alias, e.g. 'jobs' if " "you want to catch emails " "for <*****@*****.**>")) alias_domain = fields.Char( 'Domain', required=True, default=get_default_alias_domain, help=("The domain of the email alias, e.g. " "'example.my.openerp.com' if you want to catch emails " "for <*****@*****.**>")) alias_defaults = fields.Text( 'Default Values', default='{}', help=("A Python dictionary that will be evaluated to " "provide default values when creating new " "records for this alias.")) alias_force_thread_id = fields.Integer( 'Record Thread ID', help=("Optional ID of a thread (record) to which " "all incoming messages will be attached, " "even if they did not reply to it. If set, " "this will disable the creation of new " "records completely.")) @api.multi def read(self, fields=None, load='_classic_read'): """Read the ids from mail.alias if any of fields are not present on mail.alias model then search it on alias_defaults dict. """ Model = self.env['mail.alias'] extraneous = [] for field in fields: if field not in Model._fields.keys(): extraneous.append(field) if extraneous: fields = list(set(fields) - set(extraneous)) if extraneous and 'alias_defaults' not in fields: default_added = True fields.append('alias_defaults') else: default_added = False result = Model.browse(self.ids).read(fields) if not extraneous: return result else: for row in result: defaults = str2dict(row['alias_defaults']) for field in extraneous: row[field] = defaults.pop(field, False) # Restore the defaults but it will have only the keys not # in 'extraneous' (those upgraded to fields) if not default_added: row['alias_defaults'] = repr(defaults) else: row.pop('alias_defaults') return result @api.model def _parse_fields(self, alias_id, vals): '''The fields not present on mail.alias model are include on alias_defaults dictionary. ''' Aliases = self.env['mail.alias'] extraneous = [] fields = set(vals.keys()) for field in vals.keys(): if field not in Aliases._fields.keys(): extraneous.append(field) if extraneous: fields -= set(extraneous) defaults = {} if 'alias_defaults' in fields: defaults = str2dict(vals['alias_defaults'], 'Default Values') else: if alias_id: record = Aliases.browse(alias_id) row = record.read(['alias_defaults']) defaults = str2dict(row[0]['alias_defaults'], 'Default Values') for field in extraneous: value = vals.pop(field, False) if field in defaults: defaults.update({field: value}) else: defaults.setdefault(field, value) vals['alias_defaults'] = repr(defaults) return vals
class SystemEvent(models.Model): '''Generic CDR event definition. For specifics CDR events implementations just need to inherit by delegation of this model and override evaluate method. Recommendation: define _description for specific event implementations to allow correct user identification. ''' _name = 'cdr.system.event' _description = "Generic CDR event" _order = 'priority' name = fields.Char(translate=True) definition = fields.Char( required=True, help=("Boolean expression combining evidences and operators. " "For example: evidence1 or evidence2 and evidence3")) next_call = fields.Datetime( default=fields.Datetime.now(), help=("The date and time at which this event will be checked again. " "This is when the evidences and its variables will be computed " "again.")) priority = fields.Integer( default=10, help=("Priority in which events are to be evaluated in an evaluation " "cycle. When you run a search they will come sorted according " "to your priority.")) active = fields.Boolean(default=True) evidences = fields.Many2many('cdr.evidence', 'event_evidence_rel', 'event_id', 'evidence_id', compute='get_evidences', store=True) specific_event = fields.Reference( [], compute='_get_specific_event', help='Reference at specific event: basic or recurrent event') state = fields.Selection(EVENT_STATES, help='State of the event: Raising or Not raising') action = fields.Selection(EVENT_ACTIONS_SELECTION, string="Last action") def get_specific_event_models(self): '''Get models that look like specific event. ''' result = [] for model_name in self.env.registry.keys(): model = self.env[model_name] if 'cdr.system.event' in getattr(model, '_inherits', {}): result.append(model) return result def _get_specific_event(self): '''Get specific event reference from it's generic one. ''' for event in self: for model in self.get_specific_event_models(): field = model._inherits.get('cdr.system.event') result = model.search([(field, '=', event.id)], limit=1) if result: event.specific_event = "%s,%d" % (result._name, result.id) @api.depends('definition') @api.onchange('definition') def get_evidences(self): '''Get evidences referenced on event definition. ''' evidences = self.env['cdr.evidence'] for event in self: if event.active and event.definition: domain = [('name', 'in', tuple(get_free_names(event.definition)))] event.evidences = evidences.search(domain) else: event.evidences = evidences def _get_evidences_to_evaluate(self): return self.mapped('evidences') def _evaluate(self): return evaluate(self.definition, **self.evidences.get_bool_value()) @api.constrains('definition') def _check_definition(self): for record in self: try: record._evaluate() except Exception as e: raise exceptions.ValidationError( _("Wrong definition: %s") % e.message) def evaluate(self, cycle): if isinstance(cycle, int): cycle = self.env['cdr.evaluation.cycle'].browse(cycle) for event in self: # call specific event evaluate method to get each # corresponding behavior. event.specific_event.evaluate(cycle) for signalname, signal in EVENT_SIGNALS.items(): # Do not use groupby because a recordset is needed on signaling # send. sender = self.filtered(lambda event: event.action == signalname) if sender: logger.debug('Sending signal (%s) for Events: (%s)', signalname, ', '.join(e.name for e in sender)) signal.send(sender=sender)
class BasicEvent(models.Model): _name = 'cdr.basic.event' _description = 'Basic CDR event' _inherits = {'cdr.system.event': 'event_id'} event_id = fields.Many2one('cdr.system.event', required=True, ondelete='cascade') interval = fields.Float( required=True, help='Time (in hours:minutes format) between evaluations.') time_to_wait = fields.Float( required=True, help='Time (in hours:minutes format) getting ' 'consecutive positive evaluations before raise.') times_to_raise = fields.Integer( help='Waiting time to launch an event while an evidence is true in a ' 'time interval') @api.depends('interval') def get_next_call(self): '''Compute the next evaluation date. ''' for event in self: if event.active and event.interval: event.next_call = datetime.now() + timedelta( hours=event.interval) else: event.next_call = False def update_event(self, value, cycle): '''Update the fields next call, state and action for an event. When an event is evaluated is necessary to update its values. ''' next_call = str2dt(cycle.create_date) + timedelta(hours=self.interval) # If the interval is less or equal zero that means that the event does # not wait any time to launch. if self.interval <= 0: times_to_raise = -1 else: times_to_raise = ((self.time_to_wait / self.interval) if not value else self.times_to_raise - 1) state = 'raising' if value and times_to_raise < 1 else 'not_raising' if self.state == 'raising': action = 'continue_raising' if state == 'raising' else 'stop_raising' else: action = 'raise' if state == 'raising' else 'do_nothing' values = dict(next_call=next_call, state=state, action=action) self.write(values) def evaluate(self, cycle): '''Evaluate the basic event in a evaluation cycle. ''' if isinstance(cycle, int): cycle = self.env['cdr.evaluation.cycle'].browse(cycle) try: value = self.event_id._evaluate() except Exception: logger.exception('Error evaluating event %s defined as: ', self.name, self.definition) return None else: self.update_event(value, cycle)
class ResPartner(models.Model): _inherit = 'res.partner' id = fields.Integer('Id', readonly=True) create_date = fields.Datetime('Create Date', readonly=True)
class RecurrentRuleDefinition(models.AbstractModel): '''A recurrence definition mixin. The model behind this definition is that of the python module `dateutil.rrule`:mod:. ''' _name = RECURRENT_DESCRIPTION_MODEL _inherit = ['recurrence.abs.day_of_week'] date_from = fields.Date( 'Initial date', required=True, default=fields.Date.today, # XXX: When we return an occurrence this matches the date of the # occurrence. help='Date at which this recurrent event starts.') duration = fields.Integer('Duration', default=1, help='Duration (days) of each occurrence') allday = fields.Boolean( 'All Day', default=True, help='Is this a day-long occurrence?', ) freq = fields.Selection( FREQ, 'Frequency type', default=DEFAULT_FREQ, # XXX: Don't put this, because there's an error in the JS client that # messes up with the invisible toggling we have there. # help='Frecuency type (Daily/Weekly/Monthly/Yearly)' ) interval = fields.Integer( 'Repeat Every', default=DEFAULT_INTERVAL, help='Repeat every ...', ) # Recurrence by month data. days_option = fields.Selection( SELECT_WEEKDAY_MONTHDAY, 'Option', default=DEFAULT_WEEK_MONTH, help='Does this occur on the same day of the month, or the week', ) monthly_day = fields.Integer( 'Days of month', default=lambda *args: datetime.date.today().day) by_week_day = fields.Selection( BYWEEKDAY, 'Reference', default=DEFAULT_BYWEEKDAY, help=("Used in combination with each week day selection. If you " "check Monday, then this can mean: every Monday, the first " "Monday, the last Monday, etc... You may choose several week " "days.")) months = fields.Selection( MONTHS, 'Month', deafault=lambda *args: str(datetime.date.today().month), help="The month of the year at which this event reoccurs.") is_easterly = fields.Boolean( 'By easter', help="For events that reoccurs based on Western Easter Sunday.") byeaster = fields.Integer( 'By easter', default=0, help='Number of days from Western Easter Sunday.') # In this model, the end of recurrence is EITHER: a) does not have and # end, b) after many instances, c) after a given date. end_type = fields.Selection( END_TYPE, 'Recurrence Termination', default=DOES_NOT_END, help="How this recurrence stops from happening.") count = fields.Integer('Repeat', default=5, help='Repeat recurrent event by x times') until = fields.Date('Repeat Until', help='Date end for the recurrent termination') # TODO: Should we need to store this in the DB? rrule = fields.Char( 'RRULE', size=124, readonly=True, default='', compute='_compute_rrule_string', help=( 'This field is update after to creates or to update the recurrent' ' model by the function _update_rrule. By default it is taken' ' value but then it is calculated and takes a similar value.' ' E.g. rrule=FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,MO'), ) @api.depends('count', 'until', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'is_easterly', 'byeaster', 'months', 'monthly_day', 'by_week_day', 'freq', 'days_option', 'interval') def _compute_rrule_string(self): for record in self: record.rrule = str(record.get_rrule_from_description()) @api.requires_singleton def get_rrule_from_description(self): '''The recurrent rule that describes this recurrent event. :returns: `dateutil.rrule.rrule`:class:. ''' from xoeuf.tools import normalize_date kwargs = dict( dtstart=normalize_date(self.date_from), interval=max(1, self.interval or 0), ) if self.end_type == ENDS_AT: kwargs['until'] = max(normalize_date(self.date_from), normalize_date(self.until)) # or date_to? elif self.end_type == ENDS_AFTER_MANY: kwargs['count'] = min(1, self.count) else: assert self.end_type == DOES_NOT_END if self.days_option == USE_WEEK_DAY: kwargs['byweekday'] = self.byweekday elif self.days_option == USE_MONTH_DAY: kwargs['bymonthday'] = self.monthly_day else: assert False if self.is_easterly: kwargs['byeaster'] = self.byeaster else: kwargs['bymonth'] = int(self.months) return _rrule.rrule(FREQ_TO_RULE[self.freq], **kwargs) @fields.Property def byweekday(self): return self._weekno @api.constrains('monthly_day') def _check_monthly_day_no_longer_than_month(self): for record in self: if not (-31 <= record.monthly_day <= 31): raise ValidationError( 'You must provide a valid day of the month') @api.constrains('byeaster', 'is_easterly') def _check_byeaster_no_longer_than_year(self): for record in self: if record.is_easterly: if not (-365 <= record.byeaster <= 365): raise ValidationError( 'By Easter must not extend longer than a year') @api.requires_singleton def iter_from(self, start=None): '''Return an iterator that yields each occurrence after `start` date. If `start` is None, start at the first `date` (field ``date_from``). :returns: A generator of the dates of the occurrences. .. warning:: This can be an infinite iterator. ''' from xoeuf.tools import normalize_date if start is None: start = normalize_date(self.date_from) return self.get_rrule_from_description().xafter(start)