class Portlet(models.Model): name = models.CharField(256, null=False, label=_('Portlet Type')) schema = models.CharField(128, db_index=True) active = models.BooleanField(default=True, label=_('Active')) tag = models.CharField(128, label=_('Tag Name'), null=False) type_name = models.CharField(label=_('Type Name')) description = models.TextField() info = models.TextField() class Meta: name = 'ui.portlet' def get_info(self): info = self.info if self.type_name: tp = import_string(self.type_name) info = tp.get_info() return { 'id': self.pk, 'name': gettext(self.name), 'tag': self.tag, 'description': self.description, 'info': info, } @api.classmethod def search_portlets(cls): res = [] for p in Portlet.objects.filter(active=True): res.append(p.get_info()) return {'portlets': res}
class ClientAction(Action): tag = models.CharField(512) target = models.SelectionField( ( ('current', 'Current Window'), ('new', 'New Window'), ('fullscreen', 'Full Screen'), ('main', 'Main Action of Current Window'), ), default='current', ) model_name = models.CharField(label=_('Model')) context = models.TextField() params = models.TextField() view = models.ForeignKey('ui.view') class Meta: name = 'ui.action.client' @api.method(request=True) def get_view(self, id, request): vw = self.objects.get(id) if vw.view: return { 'content': vw.view.render({}), }
class Stage(models.Model): """ Model for case stages. This models the main stages of a document management flow. Main CRM objects (leads, opportunities, project issues, ...) will now use only stages, instead of state and stages. Stages are for example used to display the kanban view of records. """ name = models.CharField( 128, verbose_name=_('Stage Name'), null=False, translate=True ) sequence = models.IntegerField( verbose_name=_('Sequence'), default=1, help_text="Used to order stages. Lower is better." ) probability = models.FloatField( verbose_name=_('Probability (%)'), null=False, default=10.0, help_text="This percentage depicts the default/average probability of the Case for this stage to be a success") on_change = models.BooleanField( verbose_name=_('Change Probability Automatically'), help_text="Setting this stage will change the probability automatically on the opportunity.") requirements = models.TextField( verbose_name=_('Requirements'), help_text="Enter here the internal requirements for this stage (ex: Offer sent to customer). It will appear as a tooltip over the stage's name." ) team_id = models.ForeignKey( 'crm.team', verbose_name='Sales Team', ondelete=models.CASCADE, help_text='Specific team that uses this stage. Other teams will not be able to see or use this stage.' ) legend_priority = models.TextField( verbose_name=_('Priority Management Explanation'), translate=True, help_text='Explanation text to help users using the star and priority mechanism on stages or issues that are in this stage.' ) fold = models.BooleanField( verbose_name=_('Folded in Pipeline'), help_text='This stage is folded in the kanban view when there are no records in that stage to display.' ) # This field for interface only team_count = models.IntegerField('team_count', compute='_compute_team_count') @api.record def default_get(self, fields): """ Hack : when going from the pipeline, creating a stage with a sales team in context should not create a stage for the current Sales Team only """ ctx = dict(self.env.context) if ctx.get('default_team_id') and not ctx.get('crm_team_mono'): ctx.pop('default_team_id') return super(Stage, self.with_context(ctx)).default_get(fields) @api.records def _compute_team_count(self): for stage in self: stage.team_count = self.env['crm.team'].search_count([]) class Meta: name = "crm.stage" verbose_name = "CRM Stages" ordering = ('sequence', 'name', 'id')
class Action(models.Model): name = models.CharField(128, _('Name'), null=False, translate=True) action_type = models.CharField(32, _('Action Type'), null=False) usage = models.TextField(label=_('Usage')) description = models.TextField(label=_('Description')) # external_id = models.CharField(label=_('External ID'), getter='get_external_id') groups = models.ManyToManyField('auth.group') binding_model = models.ForeignKey('content.type', on_delete=models.CASCADE) binding_type = models.SelectionField( ( ('action', _('Action')), ('print', _('Print')), ), default='action', ) multiple = models.BooleanField(default=False, label='Restrict to lists') qualname = models.CharField(help_text='System qualified name') class Meta: name = 'ui.action' field_groups = { 'list_fields': ['name', 'action_type', 'usage'] } def save(self, *args, **kwargs): if not self.action_type: self.action_type = self.__class__._meta.name super(Action, self).save(*args, **kwargs) def get_action(self): return apps[self.action_type].objects.get(pk=self.pk) @api.classmethod def load(cls, name_or_id, context=None): try: name_or_id = int(name_or_id) except ValueError: if isinstance(name_or_id, str): name_or_id = ref(name_or_id) info = cls.get(name_or_id).get_action()._get_info(context) info['type'] = info.pop('action_type') return info def execute(self): raise NotImplemented() @classmethod def get_bindings(cls, model): r = defaultdict(list) # TODO: optimize filter by name (plain query) obj = apps['content.type'].objects.get_by_natural_key(model) for action in cls.objects.filter(binding_model_id=obj.pk): r[action.binding_type].append(action) return r def _get_info(self, context): return self.to_dict(exclude=['groups'])
class Filter(models.Model): name = models.CharField(256, null=False, verbose_name=_('Name')) user = models.ForeignKey('auth.user', on_delete=models.CASCADE) domain = models.TextField() context = models.TextField() sort = models.TextField() params = models.TextField() is_default = models.BooleanField(default=False) is_shared = models.BooleanField(default=True) action = models.ForeignKey('ui.action', verbose_name=_('Action')) # query = models.ForeignKey('ir.query') active = models.BooleanField(default=True) class Meta: name = 'ui.filter'
class AutoReport(models.Model): name = models.CharField(128, null=False) model = models.ForeignKey('content.type', null=False) content = models.TextField() class Meta: name = 'ui.report.auto'
class Homepage(Action): content = models.TextField() class Meta: name = 'ui.action.homepage' @api.classmethod def get_portlets(cls, user_id): return [ {'name': 'Create Partner', 'type': 'client', 'tag': 'createNew', 'action_type': 'ui.action.window', 'action': '', 'model': 'res.partner'}, ] @api.method(request=True) def save_layout(self, layout, request: HttpRequest): home, _ = UserHomepage.objects.get_or_create(homepage_id=self.pk, user_id=int(request.user_id)) home.update(content=layout) return True def _get_info(self, context): res = super()._get_info(context) # check if exists a user defined homepage try: home = UserHomepage.objects.get(homepage_id=self.pk, user_id=context['user_id']) res['content'] = home.content except UserHomepage.DoesNotExist: pass return res
class CustomView(models.Model): user = models.ForeignKey('auth.user', null=False) view = models.ForeignKey(View, null=False) content = models.TextField() class Meta: name = 'ui.view.custom'
class UserHomepage(models.Model): homepage = models.ForeignKey(Homepage, null=False, on_delete=models.DB_CASCADE) user = models.ForeignKey('auth.user', null=False, on_delete=models.DB_CASCADE) content = models.TextField() class Meta: name = 'ui.user.homepage' unique_together = ('homepage', 'user')
class UserReport(models.Model): report = models.ForeignKey(ReportAction) company = models.ForeignKey('res.company') public = models.BooleanField(default=True) params = models.TextField() class Meta: name = 'usr.report'
class ServerAction(Action): sequence = models.IntegerField(default=5) model = models.ForeignKey('content.type', null=False) code = models.TextField(label=_('Python Code')) actions = models.ManyToManyField('self') target_model = models.ForeignKey('content.type') target_field = models.ForeignKey('content.field') lines = models.OneToManyField('ui.action.server.line') class Meta: name = 'ui.action.server'
class UrlAction(Action): url = models.TextField() target = models.SelectionField( ( ('new', 'New Window'), ('self', 'Current Window'), ), default='new', null=False, ) class Meta: name = 'ui.action.url'
class Rule(models.Model): name = models.CharField(128, verbose_name=_('object name'), null=False) active = models.BooleanField(default=True) content_type = models.ForeignKey('content.type', null=False, db_index=True, on_delete=models.DB_CASCADE) groups = models.ManyToManyField('auth.group') domain = models.TextField() class Meta: name = 'ir.rule'
class ServerActionLine(models.Model): server_action = models.ForeignKey(ServerAction, null=False, on_delete=models.CASCADE) field = models.ForeignKey('content.field') value = models.TextField() type = models.SelectionField( ( ('value', _('Value')), ('expr', _('Python Expression')), ), label=_('Evaluation Type') ) class Meta: name = 'ui.action.server.line'
class LogEntry(models.Model): """ Log entries on the internal database logging """ user = models.ForeignKey('auth_user', null=False, db_index=True) action = models.ForeignKey('ui.action') # optional ui action object_id = models.BigIntegerField() content_type = models.ForeignKey('content.type', on_delete=models.SET_NULL) content_object = models.CharField(200) change_message = models.TextField() class Meta: log_changes = False name = 'ui.admin.log'
class AccountBankStatementLine(models.Model): name = models.CharField(verbose_name='Label', null=False) date = models.DateField(null=False, default=lambda self: self._context.get('date', models.DateField.context_today(self))) amount = models.MonetaryField(currency_field='journal_currency') journal_currency = models.ForeignKey( 'res.currency', verbose_name="Journal's Currency", proxy='statement.currency', help_text='Utility field to express amount currency', readonly=True ) partner = models.ForeignKey('res.partner', verbose_name='Partner') account_number = models.CharField(verbose_name='Bank Account Number', help_text="Technical field used to store the bank account number before its creation, upon the line's processing") bank_account = models.ForeignKey('res.partner.bank', verbose_name='Bank Account', help_text="Bank account that was used in this transaction.") account = models.ForeignKey('account.account', verbose_name='Counterpart Account', domain=[('deprecated', '=', False)], help_text="This technical field can be used at the statement line creation/import time in order to avoid the reconciliation" " process on it later on. The statement line will simply create a counterpart on this account") statement = models.ForeignKey('account.bank.statement', verbose_name='Statement', index=True, null=False, on_delete=models.CASCADE) journal = models.ForeignKey('account.journal', proxy='statement.journal', verbose_name='Journal', store=True, readonly=True) partner_name = models.CharField(help_text="This field is used to record the third party name when importing bank statement in electronic format," " when the partner doesn't exist yet in the database (or cannot be found).") ref = models.CharField(verbose_name='Reference') note = models.TextField(verbose_name='Notes') transaction_type = models.CharField(verbose_name='Transaction Type') sequence = models.IntegerField(index=True, help_text="Gives the sequence order when displaying a list of bank statement lines.", default=1) company = models.ForeignKey('res.company', proxy='statement.company', verbose_name='Company', store=True, readonly=True) journal_entrys = models.OneToManyField('account.move.line', 'statement_line', 'Journal Items', copy=False, readonly=True) amount_currency = models.MonetaryField(help_text="The amount expressed in an optional other currency if it is a multi-currency entry.") currency = models.ForeignKey('res.currency', verbose_name='Currency', help_text="The optional other currency if it is a multi-currency entry.") state = models.ChoiceField(proxy='statement.state', verbose_name='Status', readonly=True) move_name = models.CharField( verbose_name='Journal Entry Name', readonly=True, default=False, copy=False, help_text="Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again." ) class Meta: name = 'account.bank.statement.line' description = _('Bank Statement Line') ordering = ('-statement_id', 'date', 'sequence', '-id')
class Event(mail.models.Comments): name = models.CharField(verbose_name=_('Meeting Subject'), null=True, widget_attrs={'done': [('readonly', True)]}) status = models.ChoiceField( (('draft', _('Unconfirmed')), ('open', _('Confirmed'))), readonly=True, default='draft' ) # is_attendee = models.BooleanField('Attendee', compute='_compute_attendee') # attendee_status = models.ChoiceField(Attendee.STATE_SELECTION, verbose_name='Attendee Status', compute='_compute_attendee') # display_time = models.CharField('Event Time', compute='_compute_display_time') # display_start = models.CharField('Date', compute='_compute_display_start', store=True) start = models.DateTimeField( verbose_name=_('Start'), null=False, help_text="Start date of an event, without time for full days events" ) stop = models.DateTimeField( verbose_name=_('Stop'), null=False, help_text="Stop date of an event, without time for full days events" ) all_day = models.BooleanField(verbose_name=_('All Day'), states={'done': [('readonly', True)]}, default=False) start_date = models.DateField('Start Date', compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange') start_datetime = models.DateTimeField( verbose_name=_('Start DateTime'), compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': {'readonly': True}}, track_visibility='onchange' ) end_date = models.DateField( verbose_name=_('End Date'), compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange' ) end_datetime = models.DateTimeField( verbose_name=_('End Datetime'), compute='_compute_dates', inverse='_inverse_dates', store=True, states={'done': [('readonly', True)]}, track_visibility='onchange' ) duration = models.FloatField(verbose_name=_('Duration'), states={'done': [('readonly', True)]}) description = models.TextField(verbose_name=_('Description'), states={'done': [('readonly', True)]}) privacy = models.ChoiceField( [('public', 'Everyone'), ('private', 'Only me'), ('confidential', 'Only internal users')], verbose_name=_('Privacy'), default='public', states={'done': [('readonly', True)]}, ) location = models.CharField( 'Location', states={'done': [('readonly', True)]}, track_visibility='onchange', help_text="Location of Event" ) show_as = models.ChoiceField( (('free', _('Free')), ('busy', _('Busy'))), verbose_name=_('Show Time as'), states={'done': [('readonly', True)]}, default='busy' ) # linked document object_id = models.BigIntegerField('Object ID') model = models.ForeignKey('ir.model', verbose_name=_('Document Model'), ondelete=models.CASCADE) model_name = models.CharField('Document Model Name', related='model.name', readonly=True, store=True) activities = models.OneToManyField('mail.activity', 'calendar_event_id', verbose_name='Activities') # redifine message_ids to remove autojoin to avoid search to crash in get_recurrent_ids # message_ids = models.OneToManyField() recurrent_rule = models.CharField( verbose_name=_('Recurrent Rule'), compute='_compute_recurrent_rule', inverse='_inverse_recurrent_rule', store=True ) recurrent_rule_type = models.ChoiceField( ( ('daily', _('Day(s)')), ('weekly', _('Week(s)')), ('monthly', _('Month(s)')), ('yearly', _('Year(s)')), ), verbose_name=_('Recurrence'), states={'done': [('readonly', True)]}, help_text="Let the event automatically repeat at that interval" ) recurrency = models.BooleanField('Recurrent', help_text="Recurrent Meeting") recurrent_id = models.BigIntegerField('Recurrent ID') recurrent_id_date = models.DateTimeField('Recurrent ID date') end_type = models.ChoiceField( ( ('count', 'Number of repetitions'), ('end_date', 'End date') ), verbose_name=_('Recurrence Termination'), default='count' ) interval = models.IntegerField( verbose_name='Repeat Every', default=1, help_text="Repeat every (Days/Week/Month/Year)" ) count = models.IntegerField(verbose_name='Repeat', help_text="Repeat x times", default=1) mo = models.BooleanField(verbose_name=_('Mon')) tu = models.BooleanField(verbose_name=_('Tue')) we = models.BooleanField(verbose_name=_('Wed')) th = models.BooleanField(verbose_name=_('Thu')) fr = models.BooleanField(verbose_name=_('Fri')) sa = models.BooleanField(verbose_name=_('Sat')) su = models.BooleanField(verbose_name=_('Sun')) month_by = models.ChoiceField([ ('date', 'Date of month'), ('day', 'Day of month') ], verbose_name=_('Option'), default='date') day = models.IntegerField('Date of month', default=1) week_list = models.ChoiceField( ( ('MO', 'Monday'), ('TU', 'Tuesday'), ('WE', 'Wednesday'), ('TH', 'Thursday'), ('FR', 'Friday'), ('SA', 'Saturday'), ('SU', 'Sunday') ), verbose_name=_('Weekday') ) by_day = models.ChoiceField( ( ('1', _('First')), ('2', _('Second')), ('3', _('Third')), ('4', _('Fourth')), ('5', _('Fifth')), ('-1', _('Last')), ), verbose_name=_('By day') ) final_date = models.DateField('Repeat Until') user_id = models.ForeignKey('res.users', 'Owner', states={'done': [('readonly', True)]}, default=lambda self: self.env.user) partner_id = models.ForeignKey( 'res.partner', verbose_name='Responsible', related='user_id.partner_id', readonly=True ) active = models.BooleanField( verbose_name=_('Active'), default=True, help_text="If the active field is set to false, it will allow you to hide the event alarm information without removing it." ) event_types = models.ManyToManyField('calendar.event.type', verbose_name=_('Tags')) attendee = models.OneToManyField( 'calendar.attendee', 'event', verbose_name=_('Participant'), ondelete=models.CASCADE ) partners = models.OneToManyField( 'res.partner', 'calendar_event_res_partner_rel', verbose_name='Attendees', states={'done': [('readonly', True)]}, default=_default_partners ) alarms = models.ManyToManyField( 'calendar.alarm', 'calendar_alarm_calendar_event_rel', verbose_name='Reminders', ondelete="restrict", copy=False ) is_highlighted = models.BooleanField(compute='_compute_is_highlighted', verbose_name='Is the Event Highlighted') class Meta: name = 'calendar.event' verbose_name = "Event"
class View(models.Model): name = models.CharField(max_length=100) active = models.BooleanField(label=_('Active'), default=True) parent = models.ForeignKey('self') view_type = models.ChoiceField(( ('list', 'List'), ('form', 'Form'), ('card', 'Card'), ('chart', 'Chart'), ('calendar', 'Calendar'), ('search', 'Search'), ('template', 'Template'), ('report', 'Report'), ('dashboard', 'Dashboard'), ('custom', 'Custom'), ('class', 'Class'), ), default='form', null=False) mode = models.ChoiceField( (('primary', _('Primary')), ('extension', _('Extension'))), default='extension', null=False) model = models.CharField(128, db_index=True) priority = models.IntegerField(_('Priority'), default=99, null=False) template_name = models.CharField(max_length=256) content = models.TextField(caption=_('Content')) # ref_id = models.CharField(caption=_('Reference ID'), getter='_get_xml_id') # children = models.OneToManyField('self', 'parent') class_name = models.CharField(max_length=256, verbose_name='Python Class Name') class Meta: name = 'ui.view' ordering = ('name', 'priority') def save(self, *args, **kwargs): if self.parent_id is None: self.mode = 'primary' if self.view_type is None: xml = etree.fromstring(self.render({})) self.view_type = xml.tag super(View, self).save(*args, **kwargs) def _get_xml_id(self): obj = apps['ir.object'].objects.get_by_object_id( self._meta.name, self.id) if obj: return obj.name def get_content(self, model=None): xml = etree.tostring(self.get_xml(model)) return xml def get_xml(self, model, context=None): if context is None: context = {} if model: context.update({'opts': model._meta if model else None}) context['env'] = apps return self.compile(context) def xpath(self, source, element): pos = element.attrib.get('position') expr = element.attrib.get('expr') target = source logger.critical('xpath %s %s' % (expr, self.template_name)) logger.critical(etree.tostring(element)) if expr: target = target.xpath(expr)[0] self._merge(target, pos, element) def _merge(self, target: etree.HtmlElement, pos: str, element: etree.HtmlElement): if pos == 'append': for child in element: target.append(child) elif pos == 'insert': for child in reversed(element): target.insert(0, etree.fromstring(etree.tostring(child))) elif pos == 'before': parent = target.getparent() idx = parent.index(target) for child in reversed(element): parent.insert(idx, etree.fromstring(etree.tostring(child))) elif pos == 'after': parent = target.getparent() idx = parent.index(target) + 1 for child in reversed(element): parent.insert(idx, etree.fromstring(etree.tostring(child))) elif pos == 'attributes': for child in element: target.attrib[child.attrib['name']] = child.text elif pos == 'replace': p = target.getparent() idx = p.index(target) p.remove(target) for child in element: p.insert(idx, etree.fromstring(etree.tostring(child))) def merge(self, target: etree.HtmlElement, element): for child in element: if child.tag == 'xpath': self.xpath(target, child) elif child.tag == 'insert' or child.tag == 'append': self._merge(target, child.tag, child) for k, v in element.attrib.items(): target.attrib[k] = v def compile(self, context, parent=None): view_cls = self.__class__ children = view_cls.objects.filter(parent_id=self.pk, mode='extension') context['ref'] = ref context['exec_scalar'] = exec_scalar context['exec_query'] = exec_query context['query'] = query context['models'] = apps xml = self._get_content(context) xml = etree.fromstring(self._get_content(context)) if self.parent: parent_xml = etree.fromstring(self.parent.render(context)) self.merge(parent_xml, xml) xml = parent_xml for child in children: self.merge(xml, etree.fromstring(child._get_content(context))) self._eval_permissions(xml) resolve_refs(xml) return xml def _eval_permissions(self, xml): _groups = {} return user = self.env.user if not user.is_superuser: objects = self.env['ir.object'] children = xml.xpath("//*[@groups]") for child in children: groups = child.attrib['groups'] if groups not in _groups: has_groups = len( list( objects.objects.only('id').filter( objects.c.model == 'auth.group', objects.c.name.in_(groups.split(',')), objects.c.object_id.in_(user.groups))[:1])) > 0 _groups[groups] = has_groups if not _groups[groups]: child.getparent().remove(child) def _get_content(self, context): if self.view_type == 'report': templ = loader.get_template(self.template_name.split(':')[-1]) else: templ = loader.get_template(self.template_name.split(':')[-1]) # templ = apps.jinja_env.get_or_select_template(self.template_name.split(':')[-1]) return templ.render(context) res = open(templ.template.filename, encoding='utf-8').read() return res def to_string(self): templ = loader.find_template(self.template_name.split(':')[-1]) with open(templ, encoding='utf-8') as f: return f.read() def render(self, context): from orun.template.loader import get_template context['env'] = apps context['_'] = gettext context['exec_query'] = exec_query context['query'] = query context['exec_scalar'] = exec_scalar context['models'] = apps context['ref'] = ref if self.view_type in ('dashboard', 'report'): context['db'] = {'connection': connection} if settings.DEBUG and self.template_name: # context['ref'] = g.env.ref templ = self.template_name.split(':')[-1] if self.view_type in ('dashboard', 'report'): content = get_template(self.template_name) if isinstance(Template, str): return Template(content).render(context) else: return content.render(context) return apps.report_env.get_or_select_template(templ).render( **context) return loader.get_template(templ).render(context) if self.view_type in ('dashboard', 'report') and self.template_name: content = get_template(self.template_name) return content.render(context) # return apps.report_env.from_string(self.content).render(**context) if self.view_type == 'dashboard': return self.content return Template(self.content).render(context) @classmethod def generate_view(self, request, model, view_type='form'): opts = model._meta return render_template(request, [ 'views/%s/%s.html' % (opts.name, view_type), 'views/%s/%s.xml' % (opts.name, view_type), 'views/%s/%s.xml' % (opts.app_label, view_type), 'views/%s.xml' % view_type, ], context=dict(opts=opts, _=gettext)) @classmethod def render_template(self, request, template, context): # find template by ref id templ = apps['ir.object'].get_by_natural_key(template).object children = list(self.objects.filter(mode='primary', parent=templ.id)) if children: for child in children: pass else: views_env.from_string
class Lead(models.Model): name = models.CharField(128, verbose_name=_('Opportunity'), null=False, db_index=True) partner = models.ForeignKey( 'res.partner', verbose_name='Customer', track_visibility='onchange', track_sequence=1, db_index=True, help= "Linked partner (optional). Usually created when converting the lead. You can find a partner by its Name, TIN, Email or Internal Reference." ) active = models.BooleanField(verbose_name='Active', default=True, track_visibility=True) date_action_last = models.DateTimeField(verbose_name='Last Action', readonly=True) email_from = models.EmailField(verbose_name=_('Email'), help="Email address of the contact", track_visibility='onchange', track_sequence=4, db_index=True) website = models.CharField(verbose_name=_('Website'), db_index=True, help="Website of the contact") team = models.ForeignKey( 'crm.team', verbose_name='Sales Team', default=lambda self: self.env['crm.team'].sudo()._get_default_team_id( user_id=self.env.uid), db_index=True, track_visibility='onchange', help= 'When sending mails, the default email address is taken from the Sales Team.' ) kanban_state = models.ChoiceField([('grey', 'No next activity planned'), ('red', 'Next activity late'), ('green', 'Next activity is planned')], verbose_name='Kanban State', compute='_compute_kanban_state') email_cc = models.TextField( 'Global CC', help= "These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma" ) description = models.TextField('Notes') tags = models.ManyToManyField( 'crm.lead.tag', 'crm_lead_tag_rel', 'lead_id', 'tag_id', verbose_name='Tags', help= "Classify and analyze your lead/opportunity categories like: Training, Service" ) contact_name = models.CharField(verbose_name=_('Contact Name'), track_visibility='onchange', track_sequence=3) partner_name = models.CharField( "Customer Name", track_visibility='onchange', track_sequence=2, db_index=True, help= 'The name of the future partner company that will be created while converting the lead into opportunity' ) type = models.ChoiceField( [('lead', 'Lead'), ('opportunity', 'Opportunity')], db_index=True, null=False, default=lambda self: 'lead' if self.env['res.users'].has_group( 'crm.group_use_lead') else 'opportunity', help="Type is used to separate Leads and Opportunities") priority = models.ChoiceField(AVAILABLE_PRIORITIES, verbose_name='Priority', db_index=True, default=AVAILABLE_PRIORITIES[0][0]) date_closed = models.DateTimeField(verbose_name=_('Closed Date'), readonly=True, copy=False) stage = models.ForeignKey( 'crm.stage', verbose_name=_('Stage'), track_visibility='onchange', db_index=True, copy=False, domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]", group_expand='_read_group_stage_ids', default=lambda x: x._default_stage_id()) user = models.ForeignKey('res.users', verbose_name='Salesperson', track_visibility='onchange', default=lambda self: self.env.user) referred = models.CharField(verbose_name=_('Referred By')) date_open = models.DateTimeField(verbose_name=_('Assignation Date'), readonly=True, default=models.DateTimeField.now) day_open = models.FloatField(compute='_compute_day_open', verbose_name='Days to Assign', store=True) day_close = models.FloatField(compute='_compute_day_close', verbose_name='Days to Close', store=True) date_last_stage_update = models.DateTimeField( verbose_name='Last Stage Update', db_index=True, default=models.DateTimeField.now) date_conversion = models.DateTimeField(verbose_name=_('Conversion Date'), readonly=True) # Messaging and marketing message_bounce = models.IntegerField( verbose_name=_('Bounce'), help="Counter of the number of bounced emails for this contact", default=0) # Only used for type opportunity probability = models.FloatField(verbose_name=_('Probability'), group_operator="avg", copy=False, default=lambda x: x._default_probability()) # planned_revenue = models.Monetary('Expected Revenue', currency_field='company_currency', track_visibility='always') # expected_revenue = models.Monetary('Prorated Revenue', currency_field='company_currency', store=True, compute="_compute_expected_revenue") date_deadline = models.DateField( verbose_name=_('Expected Closing'), help="Estimate of the date on which the opportunity will be won.") color = models.IntegerField(verbose_name=_('Color Index'), default=0) partner_address_name = models.CharField('Partner Contact Name', related='partner_id.name', readonly=True) partner_address_email = models.CharField('Partner Contact Email', related='partner_id.email', readonly=True) partner_address_phone = models.CharField('Partner Contact Phone', related='partner_id.phone', readonly=True) partner_is_blacklisted = models.BooleanField( verbose_name=_('Partner is blacklisted'), related='partner_id.is_blacklisted', readonly=True) # company_currency = models.ForeignKey( # verbose_name='Currency', related='company_id.currency_id', readonly=True, # relation="res.currency" # ) user_email = models.CharField(verbose_name=_('User Email'), related='user_id.email', readonly=True) user_login = models.CharField(verbose_name=_('User Login'), related='user_id.login', readonly=True) # Fields for address, due to separation from crm and res.partner street = models.CharField(verbose_name=_('Street')) street2 = models.CharField(verbose_name=_('Street2')) zip = models.CharField(verbose_name=_('Zip'), change_default=True) city = models.CharField(verbose_name=_('City')) state = models.ForeignKey('res.country.state', verbose_name='State') country = models.ForeignKey('res.country', verbose_name='Country') phone = models.CharField(verbose_name=_('Phone'), track_sequence=5) mobile = models.CharField(verbose_name=_('Mobile')) function = models.CharField(verbose_name=_('Job Position')) title = models.ForeignKey('res.partner.title') company = models.ForeignKey( 'res.company', verbose_name='Company', db_index=True, default=lambda self: self.env.user.company_id.id) meeting_count = models.IntegerField(verbose_name=_('# Meetings'), compute='_compute_meeting_count') lost_reason = models.ForeignKey('crm.lost.reason', verbose_name='Lost Reason', db_index=True, track_visibility='onchange') class Meta: name = "crm.lead" verbose_name = "Lead/Opportunity" ordering = ('-priority', 'activity_date_deadline', '-pk')
class WindowAction(Action): VIEW_MODE = ( ('form', 'Form'), ('list', 'List'), ('card', 'Card'), ('search', 'Search'), ('calendar', 'Calendar'), ) view = models.ForeignKey('ui.view', label=_('View')) domain = models.TextField(label=_('Domain')) context = models.TextField(label=_('Context')) model = models.CharField(128, null=False, label=_('Model')) object_id = models.BigIntegerField(label=_('Object ID')) #content_object = GenericForeignKey() view_mode = models.CharField(128, default='list,form', label=_('View Mode')) target = models.CharField(16, label=_('Target'), choices=( ('current', 'Current Window'), ('new', 'New Window'), )) limit = models.IntegerField(default=100, label=_('Limit')) auto_search = models.BooleanField(default=True, label=_('Auto Search')) # views = models.TextField(getter='_get_views', editable=False, serializable=True) view_list = models.OneToManyField('ui.action.window.view') view_type = models.SelectionField(VIEW_MODE, default='form') class Meta: name = 'ui.action.window' field_groups = { 'list_fields': ['name', 'action_type', 'usage', 'view', 'model', 'view_mode', 'limit', 'auto_search'] } def _get_views(self): modes = self.view_mode.split(',') views = self.view_list.all() modes = {mode: None for mode in modes} if self.view_id: modes[self.view_type] = self.view_id for v in views: modes[v.view_mode] = v.view_id if 'search' not in modes: modes['search'] = None return modes @classmethod def from_model(cls, model): if isinstance(model, models.Model): model = model._meta.name return cls.objects.filter(model=model).first() def _get_info(self, context): info = super()._get_info(context) # Send action information as katrid.js protocol modes = info['viewModes'] = info.pop('view_mode').split(',') # info['viewMode'] = info.pop('view_type') info['viewMode'] = modes[0] model = apps[self.model] info['fields'] = model.admin_get_fields_info() info['caption'] = info.pop('name') view_id = self.view_id views_info = info['viewsInfo'] = {} # check if there's a specified view if view_id: views_info[self.view.view_type] = model._admin_get_view_info( view_type=self.view_type, view=view_id, toolbar=True ) views_info.update({ k: model._admin_get_view_info(view_type=k, view=None, toolbar=True) for k in modes if k not in views_info }) info['viewsInfo']['search'] = model._admin_get_view_info(view_type='search') return info
class Query(models.Model): name = models.CharField(label=_('Name'), localize=True) category = models.ForeignKey(Category) sql = models.TextField() context = models.TextField() params = models.TextField() public = models.BooleanField( default=False, label='Public/External access', help_text='Has public external/anonymous access') published = models.BooleanField(default=True) class Meta: name = 'ir.query' def get_by_natural_key(self, category, name): return self.objects.filter({'category': category, 'name': name}).one() def _apply_param(self, param, values): sql = [] for k, v in param.items(): if k == 'OR': return ' OR '.join([self._apply_param(p, values) for p in v]) cond = k.split('__') field = cond[0] if len(cond) > 1: for lookup in cond[1:]: # name = 'param_%s' % (len(values) + 1) if lookup == 'icontains': s = f'"{field}" like ?' v = f'%{v}%' else: s = f'"{field}" = ?' values.append(v) sql.append('(%s)' % s) else: # name = 'param_%s' % (len(values.values()) + 1) values.append(v) s = f'"{field}" = ?' sql.append('(%s)' % s) return '(%s)' % ' OR '.join(sql) def _apply_search(self, sql, params, values): if params: where = [] for param in params: where.append(self._apply_param(param, values)) where = ' AND '.join(where) sql = """SELECT * FROM (%s) as q1 WHERE %s""" % (sql, where) return sql def _prepare_context(self, request): ctx = { 'request': request, 'user_id': self.env.user_id, 'user': self.env.user, } # evaluate query params return eval(self.context, ctx) @api.classmethod def read(cls, id, with_description=False, as_dict=False, fields=None, **kwargs): q = cls.objects.get(pk=id) params = q.context if params: params = q._prepare_context() else: params = {} if 'filter' in kwargs: params.update(kwargs['filter']) sql = Template(q.sql).render(**params) values = [] if 'params' in kwargs: # apply query search params sql = q._apply_search(sql, kwargs['params'], values) if (fields): sql = 'SELECT top 100 %s FROM (%s) as __q' % (', '.join(fields)) cur = connection.cursor() cur.execute(sql, values) desc = cur.cursor.description datatype_map = { datetime.date: 'DateField', datetime.datetime: 'DateTimeField', str: 'CharField', Decimal: 'DecimalField', float: 'FloatField', int: 'IntegerField', } if with_description: fields = [{ 'name': f[0], 'type': datatype_map.get(f[1], 'CharField'), 'size': f[2] } for f in desc] else: fields = [f[0] for f in desc] if as_dict: return { 'fields': fields, 'data': [{ fields[i]: float(col) if isinstance(col, Decimal) else col for i, col in enumerate(row) } for row in cur.fetchall()], } else: return { 'fields': fields, 'data': [[ float(col) if isinstance(col, Decimal) else col for col in row ] for row in cur.fetchall()], } @api.classmethod def list_all(cls): return { 'data': [{ 'id': q.pk, 'category': str(q.category), 'name': q.name, 'params': q.params, } for q in cls.objects.all()] } @api.classmethod def clone(cls, id, data): old_query = cls.objects.get(pk=id) new_query = cls.objects.create() new_query.parent = old_query new_query.sql = old_query.sql new_query.category = old_query.category
class Menu(models.Model): name = models.CharField(null=False, translate=True, full_text_search=True) sequence = models.IntegerField(default=99) parent = models.ForeignKey('self', related_name='children') action = models.ForeignKey('ui.action') groups = models.ManyToManyField('auth.group') icon = models.CharField() css_class = models.TextField() class Meta: name = 'ui.menu' ordering = ('sequence', 'pk') field_groups = { 'list_fields': ['name', 'sequence', 'parent', 'action'] } def __str__(self): return self.get_full_name() @classmethod def search_visible_items(cls, request): visible_items = defaultdict(list) def _iter_item(item): return [{ 'id': menu_item.pk, 'name': gettext(menu_item.name), 'icon': menu_item.icon, 'url': menu_item.get_absolute_url(), 'action': menu_item.action_id, 'children': _iter_item(menu_item.pk) } for menu_item in visible_items[item]] qs = cls.objects.all() # if cls.env.user_id == SUPERUSER or cls.env.user.is_superuser: if True: # todo replace by permisson control items = qs else: Group = cls.env['auth.group'] UserGroups = cls.env['auth.user.groups.rel'] MenuGroups = cls.env['ui.menu.groups.rel'] q = MenuGroups.objects.join(Group).join(UserGroups) q = q.filter(UserGroups.c.from_auth_user_id == cls.env.user_id, MenuGroups.c.from_ui_menu_id == cls.c.pk) items = qs.filter( or_( ~MenuGroups.objects.filter( MenuGroups.c.from_ui_menu_id == cls.c.pk).exists(), q.exists())).all() for item in items: visible_items[item.parent_id].append(item) return _iter_item(None) @property def url(self): return self.get_absolute_url() def get_absolute_url(self): if self.action_id: return '#/app/?action=%s' % self.action_id elif self.parent_id: return '#' return '/web/menu/%s/' % self.pk def get_full_name(self): parent = self.parent objs = [self.name] while parent: objs.insert(0, parent.name) parent = parent.parent return MENU_SEP.join(objs) @classmethod def admin_search_menu(cls, term: str): pass