class ResourceCalendarLeaves(models.Model): _name = "resource.calendar.leaves" _description = "Leave Detail" name = fields.Char('Reason') company_id = fields.Many2one( 'res.company', related='calendar_id.company_id', string="Company", readonly=True, store=True) calendar_id = fields.Many2one('resource.calendar', 'Working Hours') date_from = fields.Datetime('Start Date', required=True) date_to = fields.Datetime('End Date', required=True) tz = fields.Selection( _tz_get, string='Timezone', default=lambda self: self._context.get('tz') or self.env.user.tz or 'UTC', help="Timezone used when encoding the leave. It is used to correctly " "localize leave hours when computing time intervals.") resource_id = fields.Many2one( "resource.resource", 'Resource', help="If empty, this is a generic holiday for the company. If a resource is set, the holiday/leave is only for this resource") @api.constrains('date_from', 'date_to') def check_dates(self): if self.filtered(lambda leave: leave.date_from > leave.date_to): raise ValidationError(_('Error! leave start-date must be lower then leave end-date.')) @api.onchange('resource_id') def onchange_resource(self): if self.resource_id: self.calendar_id = self.resource_id.calendar_id
class StockProductionLot(models.Model): _inherit = 'stock.production.lot' life_date = fields.Datetime(string='End of Life Date', help='This is the date on which the goods with this Serial Number may become dangerous and must not be consumed.') use_date = fields.Datetime(string='Best before Date', help='This is the date on which the goods with this Serial Number start deteriorating, without being dangerous yet.') removal_date = fields.Datetime(string='Removal Date', help='This is the date on which the goods with this Serial Number should be removed from the stock.') alert_date = fields.Datetime(string='Alert Date', help='Date to determine the expired lots and serial numbers using the filter "Expiration Alerts".') product_expiry_alert = fields.Boolean(compute='_compute_product_expiry_alert', help="The Alert Date has been reached.") @api.depends('alert_date') def _compute_product_expiry_alert(self): current_date = fields.Datetime.now() for lot in self.filtered(lambda l: l.alert_date): lot.product_expiry_alert = lot.alert_date <= current_date def _get_dates(self, product_id=None): """Returns dates based on number of days configured in current lot's product.""" mapped_fields = { 'life_date': 'life_time', 'use_date': 'use_time', 'removal_date': 'removal_time', 'alert_date': 'alert_time' } res = dict.fromkeys(mapped_fields, False) product = self.env['product.product'].browse(product_id) or self.product_id if product: for field in mapped_fields: duration = getattr(product, mapped_fields[field]) if duration: date = datetime.datetime.now() + datetime.timedelta(days=duration) res[field] = fields.Datetime.to_string(date) return res # Assign dates according to products data @api.model def create(self, vals): dates = self._get_dates(vals.get('product_id') or self.env.context.get('default_product_id')) for d in dates: if not vals.get(d): vals[d] = dates[d] return super(StockProductionLot, self).create(vals) @api.onchange('product_id') def _onchange_product(self): dates_dict = self._get_dates() for field, value in dates_dict.items(): setattr(self, field, value)
class MrpWorkcenterProductivity(models.Model): _name = "mrp.workcenter.productivity" _description = "Workcenter Productivity Log" _order = "id desc" _rec_name = "loss_id" workcenter_id = fields.Many2one('mrp.workcenter', "Work Center", required=True) workorder_id = fields.Many2one('mrp.workorder', 'Work Order') user_id = fields.Many2one('res.users', "User", default=lambda self: self.env.uid) loss_id = fields.Many2one('mrp.workcenter.productivity.loss', "Loss Reason", ondelete='restrict', required=True) loss_type = fields.Selection("Effectiveness", related='loss_id.loss_type', store=True) description = fields.Text('Description') date_start = fields.Datetime('Start Date', default=fields.Datetime.now, required=True) date_end = fields.Datetime('End Date') duration = fields.Float('Duration', compute='_compute_duration', store=True) @api.depends('date_end', 'date_start') def _compute_duration(self): for blocktime in self: if blocktime.date_end: d1 = fields.Datetime.from_string(blocktime.date_start) d2 = fields.Datetime.from_string(blocktime.date_end) diff = d2 - d1 if (blocktime.loss_type not in ('productive', 'performance') ) and blocktime.workcenter_id.resource_calendar_id: r = blocktime.workcenter_id.resource_calendar_id.get_work_hours_count( d1, d2, blocktime.workcenter_id.resource_id.id) blocktime.duration = round(r * 60, 2) else: blocktime.duration = round(diff.total_seconds() / 60.0, 2) else: blocktime.duration = 0.0 @api.multi def button_block(self): self.ensure_one() self.workcenter_id.order_ids.end_all()
class Attendee(models.Model): _inherit = 'calendar.attendee' google_internal_event_id = fields.Char('Google Calendar Event Id') oe_synchro_date = fields.Datetime('izi Synchro Date') _sql_constraints = [ ('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!') ] @api.multi def write(self, values): for attendee in self: meeting_id_to_update = values.get('event_id', attendee.event_id.id) # If attendees are updated, we need to specify that next synchro need an action # Except if it come from an update_from_google if not self._context.get('curr_attendee', False) and not self._context.get( 'NewMeeting', False): self.env['calendar.event'].browse(meeting_id_to_update).write( {'oe_update_date': fields.Datetime.now()}) return super(Attendee, self).write(values)
class User(models.Model): _inherit = 'res.users' google_calendar_rtoken = fields.Char('Refresh Token', copy=False) google_calendar_token = fields.Char('User token', copy=False) google_calendar_token_validity = fields.Datetime('Token Validity', copy=False) google_calendar_last_sync_date = fields.Datetime('Last synchro date', copy=False) google_calendar_cal_id = fields.Char( 'Calendar ID', copy=False, help= 'Last Calendar ID who has been synchronized. If it is changed, we remove all links between GoogleID and izi Google Internal ID' )
class MixedModel(models.Model): _name = 'test_new_api.mixed' number = fields.Float(digits=(10, 2), default=3.14) date = fields.Date() now = fields.Datetime(compute='_compute_now') lang = fields.Selection(string='Language', selection='_get_lang') reference = fields.Reference(string='Related Document', selection='_reference_models') comment1 = fields.Html(sanitize=False) comment2 = fields.Html(sanitize_attributes=True, strip_classes=False) comment3 = fields.Html(sanitize_attributes=True, strip_classes=True) comment4 = fields.Html(sanitize_attributes=True, strip_style=True) currency_id = fields.Many2one( 'res.currency', default=lambda self: self.env.ref('base.EUR')) amount = fields.Monetary() @api.one def _compute_now(self): # this is a non-stored computed field without dependencies self.now = fields.Datetime.now() @api.model def _get_lang(self): return self.env['res.lang'].get_installed() @api.model def _reference_models(self): models = self.env['ir.model'].sudo().search([('state', '!=', 'manual') ]) return [(model.model, model.name) for model in models if not model.model.startswith('ir.')]
class StockQuantityHistory(models.TransientModel): _name = 'stock.quantity.history' _description = 'Stock Quantity History' compute_at_date = fields.Selection([ (0, 'Current Inventory'), (1, 'At a Specific Date') ], string="Compute", help="Choose to analyze the current inventory or from a specific date in the past.") date = fields.Datetime('Inventory at Date', help="Choose a date to get the inventory at that date", default=fields.Datetime.now) def open_table(self): self.ensure_one() if self.compute_at_date: tree_view_id = self.env.ref('stock.view_stock_product_tree').id form_view_id = self.env.ref('stock.product_form_view_procurement_button').id # We pass `to_date` in the context so that `qty_available` will be computed across # moves until date. action = { 'type': 'ir.actions.act_window', 'views': [(tree_view_id, 'tree'), (form_view_id, 'form')], 'view_mode': 'tree,form', 'name': _('Products'), 'res_model': 'product.product', 'context': dict(self.env.context, to_date=self.date), } return action else: self.env['stock.quant']._merge_quants() return self.env.ref('stock.quantsact').read()[0]
class Partner(models.Model): _inherit = 'res.partner' calendar_last_notif_ack = fields.Datetime( 'Last notification marked as read from base Calendar') @api.multi def get_attendee_detail(self, meeting_id): """ Return a list of tuple (id, name, status) Used by base_calendar.js : Many2ManyAttendee """ datas = [] meeting = None if meeting_id: meeting = self.env['calendar.event'].browse( get_real_ids(meeting_id)) for partner in self: data = partner.name_get()[0] data = [data[0], data[1], False, partner.color] if meeting: for attendee in meeting.attendee_ids: if attendee.partner_id.id == partner.id: data[2] = attendee.state datas.append(data) return datas @api.model def _set_calendar_last_notif_ack(self): partner = self.env['res.users'].browse(self.env.uid).partner_id partner.write({'calendar_last_notif_ack': datetime.now()}) return
class ConverterTest(models.Model): _name = 'web_editor.converter.test' # disable translation export for those brilliant field labels and values _translate = False char = fields.Char() integer = fields.Integer() float = fields.Float() numeric = fields.Float(digits=(16, 2)) many2one = fields.Many2one('web_editor.converter.test.sub') binary = fields.Binary() date = fields.Date() datetime = fields.Datetime() selection = fields.Selection([ (1, "réponse A"), (2, "réponse B"), (3, "réponse C"), (4, "réponse <D>"), ]) selection_str = fields.Selection( [ ('A', "Qu'il n'est pas arrivé à Toronto"), ('B', "Qu'il était supposé arriver à Toronto"), ('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"), ('D', "La réponse D"), ], string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et " u"qu'il fait une escale technique à St Claude, on dit:") html = fields.Html() text = fields.Text()
class LeadTest(models.Model): _name = "base.automation.lead.test" _description = "Action Rule Test" name = fields.Char(string='Subject', required=True, index=True) user_id = fields.Many2one('res.users', string='Responsible') state = fields.Selection([('draft', 'New'), ('cancel', 'Cancelled'), ('open', 'In Progress'), ('pending', 'Pending'), ('done', 'Closed')], string="Status", readonly=True, default='draft') active = fields.Boolean(default=True) partner_id = fields.Many2one('res.partner', string='Partner') date_action_last = fields.Datetime(string='Last Action', readonly=True) customer = fields.Boolean(related='partner_id.customer', readonly=True, store=True) line_ids = fields.One2many('base.automation.line.test', 'lead_id') priority = fields.Boolean() deadline = fields.Boolean(compute='_compute_deadline', store=True) is_assigned_to_admin = fields.Boolean(string='Assigned to admin user') @api.depends('priority') def _compute_deadline(self): for record in self: if not record.priority: record.deadline = False else: record.deadline = fields.Datetime.from_string( record.create_date) + relativedelta.relativedelta(days=3)
class BadgeUser(models.Model): """User having received a badge""" _name = 'gamification.badge.user' _description = 'Gamification user badge' _order = "create_date desc" _rec_name = "badge_name" user_id = fields.Many2one('res.users', string="User", required=True, ondelete="cascade", index=True) sender_id = fields.Many2one('res.users', string="Sender", help="The user who has send the badge") badge_id = fields.Many2one('gamification.badge', string='Badge', required=True, ondelete="cascade", index=True) challenge_id = fields.Many2one( 'gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge") comment = fields.Text('Comment') badge_name = fields.Char(related='badge_id.name', string="Badge Name") create_date = fields.Datetime('Created', readonly=True) create_uid = fields.Many2one('res.users', string='Creator', readonly=True) def _send_badge(self): """Send a notification to a user for receiving a badge Does not verify constrains on badge granting. The users are added to the owner_ids (create badge_user if needed) The stats counters are incremented :param ids: list(int) of badge users that will receive the badge """ template = self.env.ref('gamification.email_template_badge_received') for badge_user in self: self.env['mail.thread'].message_post_with_template( template.id, model=badge_user._name, res_id=badge_user.id, composition_mode='mass_mail', partner_ids=badge_user.user_id.partner_id.ids, ) return True @api.model def create(self, vals): self.env['gamification.badge'].browse( vals['badge_id']).check_granting() return super(BadgeUser, self).create(vals)
class BusPresence(models.Model): """ User Presence Its status is 'online', 'away' or 'offline'. This model should be a one2one, but is not attached to res_users to avoid database concurrence errors. Since the 'update' method is executed at each poll, if the user have multiple opened tabs, concurrence errors can happend, but are 'muted-logged'. """ _name = 'bus.presence' _description = 'User Presence' _log_access = False _sql_constraints = [('bus_user_presence_unique', 'unique(user_id)', 'A user can only have one IM status.')] user_id = fields.Many2one('res.users', 'Users', required=True, index=True, ondelete='cascade') last_poll = fields.Datetime('Last Poll', default=lambda self: fields.Datetime.now()) last_presence = fields.Datetime('Last Presence', default=lambda self: fields.Datetime.now()) status = fields.Selection([('online', 'Online'), ('away', 'Away'), ('offline', 'Offline')], 'IM Status', default='offline') @api.model def update(self, inactivity_period): """ Updates the last_poll and last_presence of the current user :param inactivity_period: duration in milliseconds """ presence = self.search([('user_id', '=', self._uid)], limit=1) # compute last_presence timestamp last_presence = datetime.datetime.now() - datetime.timedelta(milliseconds=inactivity_period) values = { 'last_poll': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), } # update the presence or a create a new one if not presence: # create a new presence for the user values['user_id'] = self._uid values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT) self.create(values) else: # update the last_presence if necessary, and write values if datetime.datetime.strptime(presence.last_presence, DEFAULT_SERVER_DATETIME_FORMAT) < last_presence: values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT) # Hide transaction serialization errors, which can be ignored, the presence update is not essential with tools.mute_logger('izi.sql_db'): presence.write(values) # avoid TransactionRollbackError self.env.cr.commit() # TODO : check if still necessary
class StockQuant(models.Model): _inherit = 'stock.quant' removal_date = fields.Datetime(related='lot_id.removal_date', store=True) @api.model def _get_removal_strategy_order(self, removal_strategy): if removal_strategy == 'fefo': return 'removal_date, in_date, id' return super(StockQuant, self)._get_removal_strategy_order(removal_strategy)
class Project(models.Model): _inherit = "project.project" # This method should be called once a day by the scheduler @api.model def _send_rating_all(self): projects = self.search([('rating_status', '=', 'periodic'), ('rating_request_deadline', '<=', fields.Datetime.now())]) projects.mapped('task_ids')._send_task_rating_mail() projects._compute_rating_request_deadline() @api.depends('percentage_satisfaction_task') def _compute_percentage_satisfaction_project(self): domain = [('create_date', '>=', fields.Datetime.to_string(fields.datetime.now() - timedelta(days=30)))] for project in self: activity = project.tasks.rating_get_grades(domain) project.percentage_satisfaction_project = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) else -1 @api.one @api.depends('tasks.rating_ids.rating') def _compute_percentage_satisfaction_task(self): activity = self.tasks.rating_get_grades() self.percentage_satisfaction_task = activity['great'] * 100 / sum(activity.values()) if sum(activity.values()) else -1 percentage_satisfaction_task = fields.Integer( compute='_compute_percentage_satisfaction_task', string="Happy % on Task", store=True, default=-1) percentage_satisfaction_project = fields.Integer( compute="_compute_percentage_satisfaction_project", string="Happy % on Project", store=True, default=-1) rating_request_deadline = fields.Datetime(compute='_compute_rating_request_deadline', store=True) rating_status = fields.Selection([('stage', 'Rating when changing stage'), ('periodic', 'Periodical Rating'), ('no','No rating')], 'Customer(s) Ratings', help="How to get the customer's feedbacks?\n" "- Rating when changing stage: Email will be sent when a task/issue is pulled in another stage\n" "- Periodical Rating: Email will be sent periodically\n\n" "Don't forget to set up the mail templates on the stages for which you want to get the customer's feedbacks.", default="no", required=True) rating_status_period = fields.Selection([ ('daily', 'Daily'), ('weekly', 'Weekly'), ('bimonthly', 'Twice a Month'), ('monthly', 'Once a Month'), ('quarterly', 'Quarterly'), ('yearly', 'Yearly') ], 'Rating Frequency') @api.depends('rating_status', 'rating_status_period') def _compute_rating_request_deadline(self): periods = {'daily': 1, 'weekly': 7, 'bimonthly': 15, 'monthly': 30, 'quarterly': 90, 'yearly': 365} for project in self: project.rating_request_deadline = fields.datetime.now() + timedelta(days=periods.get(project.rating_status_period, 0)) @api.multi def action_view_all_rating(self): """ return the action to see all the rating of the project, and activate default filters """ action = self.env['ir.actions.act_window'].for_xml_id('rating_project', 'rating_rating_action_view_project_rating') action['name'] = _('Ratings of %s') % (self.name,) action_context = safe_eval(action['context']) if action['context'] else {} action_context.update(self._context) action_context['search_default_rating_tasks'] = 1 return dict(action, context=action_context)
class pos_config(models.Model): _inherit = 'pos.config' @api.one @api.depends('cache_ids') def _get_oldest_cache_time(self): pos_cache = self.env['pos.cache'] oldest_cache = pos_cache.search([('config_id', '=', self.id)], order='write_date', limit=1) if oldest_cache: self.oldest_cache_time = oldest_cache.write_date # Use a related model to avoid the load of the cache when the pos load his config cache_ids = fields.One2many('pos.cache', 'config_id') oldest_cache_time = fields.Datetime(compute='_get_oldest_cache_time', string='Oldest cache time', readonly=True) def _get_cache_for_user(self): pos_cache = self.env['pos.cache'] cache_for_user = pos_cache.search([('id', 'in', self.cache_ids.ids), ('compute_user_id', '=', self.env.uid)]) if cache_for_user: return cache_for_user[0] else: return None @api.multi def get_products_from_cache(self, fields, domain): cache_for_user = self._get_cache_for_user() if cache_for_user: return cache_for_user.get_cache(domain, fields) else: pos_cache = self.env['pos.cache'] pos_cache.create({ 'config_id': self.id, 'product_domain': str(domain), 'product_fields': str(fields), 'compute_user_id': self.env.uid }) new_cache = self._get_cache_for_user() return new_cache.get_cache(domain, fields) @api.one def delete_cache(self): # throw away the old caches self.cache_ids.unlink()
class ProductionLot(models.Model): _name = 'stock.production.lot' _inherit = ['mail.thread'] _description = 'Lot/Serial' name = fields.Char( 'Lot/Serial Number', default=lambda self: self.env['ir.sequence'].next_by_code('stock.lot.serial'), required=True, help="Unique Lot/Serial Number") ref = fields.Char('Internal Reference', help="Internal reference number in case it differs from the manufacturer's lot/serial number") product_id = fields.Many2one( 'product.product', 'Product', domain=[('type', 'in', ['product', 'consu'])], required=True) product_uom_id = fields.Many2one( 'product.uom', 'Unit of Measure', related='product_id.uom_id', store=True) quant_ids = fields.One2many('stock.quant', 'lot_id', 'Quants', readonly=True) create_date = fields.Datetime('Creation Date') product_qty = fields.Float('Quantity', compute='_product_qty') _sql_constraints = [ ('name_ref_uniq', 'unique (name, product_id)', 'The combination of serial number and product must be unique !'), ] @api.model def create(self, vals): active_picking_id = self.env.context.get('active_picking_id', False) if active_picking_id: picking_id = self.env['stock.picking'].browse(active_picking_id) if picking_id and not picking_id.picking_type_id.use_create_lots: raise UserError(_("You are not allowed to create a lot for this picking type")) return super(ProductionLot, self).create(vals) @api.multi def write(self, vals): if 'product_id' in vals: move_lines = self.env['stock.move.line'].search([('lot_id', 'in', self.ids)]) if move_lines: raise UserError(_( 'You are not allowed to change the product linked to a serial or lot number ' + 'if some stock moves have already been created with that number. ' + 'This would lead to inconsistencies in your stock.' )) return super(ProductionLot, self).write(vals) @api.one def _product_qty(self): # We only care for the quants in internal or transit locations. quants = self.quant_ids.filtered(lambda q: q.location_id.usage in ['internal', 'transit']) self.product_qty = sum(quants.mapped('quantity'))
class IrLogging(models.Model): _name = 'ir.logging' _order = 'id DESC' create_date = fields.Datetime(readonly=True) create_uid = fields.Integer( string='Uid', readonly=True) # Integer not m2o is intentionnal name = fields.Char(required=True) type = fields.Selection([('client', 'Client'), ('server', 'Server')], required=True, index=True) dbname = fields.Char(string='Database Name', index=True) level = fields.Char(index=True) message = fields.Text(required=True) path = fields.Char(required=True) func = fields.Char(string='Function', required=True) line = fields.Char(required=True)
class MassMailingReport(models.Model): _name = 'mail.statistics.report' _auto = False _description = 'Mass Mailing Statistics' scheduled_date = fields.Datetime(stirng='Scheduled Date', readonly=True) name = fields.Char(string='Mass Mail', readonly=True) campaign = fields.Char(string='Mass Mail Campaign', readonly=True) sent = fields.Integer(readonly=True) delivered = fields.Integer(readonly=True) opened = fields.Integer(readonly=True) bounced = fields.Integer(readonly=True) replied = fields.Integer(readonly=True) state = fields.Selection([('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')], string='Status', readonly=True) email_from = fields.Char('From', readonly=True) @api.model_cr def init(self): """Mass Mail Statistical Report: based on mail.mail.statistics that models the various statistics collected for each mailing, and mail.mass_mailing model that models the various mailing performed. """ tools.drop_view_if_exists(self.env.cr, 'mail_statistics_report') self.env.cr.execute(""" CREATE OR REPLACE VIEW mail_statistics_report AS ( SELECT min(ms.id) as id, ms.scheduled as scheduled_date, utm_source.name as name, utm_campaign.name as campaign, count(ms.bounced) as bounced, count(ms.sent) as sent, (count(ms.sent) - count(ms.bounced)) as delivered, count(ms.opened) as opened, count(ms.replied) as replied, mm.state, mm.email_from FROM mail_mail_statistics as ms left join mail_mass_mailing as mm ON (ms.mass_mailing_id=mm.id) left join mail_mass_mailing_campaign as mc ON (ms.mass_mailing_campaign_id=mc.id) left join utm_campaign as utm_campaign ON (mc.campaign_id = utm_campaign.id) left join utm_source as utm_source ON (mm.source_id = utm_source.id) GROUP BY ms.scheduled, utm_source.name, utm_campaign.name, mm.state, mm.email_from )""")
class ProductPriceHistory(models.Model): """ Keep track of the ``product.template`` standard prices as they are changed. """ _name = 'product.price.history' _rec_name = 'datetime' _order = 'datetime desc' def _get_default_company_id(self): return self._context.get('force_company', self.env.user.company_id.id) company_id = fields.Many2one('res.company', string='Company', default=_get_default_company_id, required=True) product_id = fields.Many2one('product.product', 'Product', ondelete='cascade', required=True) datetime = fields.Datetime('Date', default=fields.Datetime.now) cost = fields.Float('Cost', digits=dp.get_precision('Product Price'))
class IrModelFieldsAnonymizationHistory(models.Model): _name = 'ir.model.fields.anonymization.history' _order = "date desc" date = fields.Datetime(required=True, readonly=True) field_ids = fields.Many2many('ir.model.fields.anonymization', 'anonymized_field_to_history_rel', 'field_id', 'history_id', string='Fields', readonly=True) state = fields.Selection(selection=ANONYMIZATION_HISTORY_STATE, string='Status', required=True, readonly=True) direction = fields.Selection(selection=ANONYMIZATION_DIRECTION, required=True, readonly=True) msg = fields.Text('Message', readonly=True) filepath = fields.Char('File path', readonly=True)
class EventMailRegistration(models.Model): _name = 'event.mail.registration' _description = 'Registration Mail Scheduler' _rec_name = 'scheduler_id' _order = 'scheduled_date DESC' scheduler_id = fields.Many2one('event.mail', 'Mail Scheduler', required=True, ondelete='cascade') registration_id = fields.Many2one('event.registration', 'Attendee', required=True, ondelete='cascade') scheduled_date = fields.Datetime('Scheduled Time', compute='_compute_scheduled_date', store=True) mail_sent = fields.Boolean('Mail Sent') @api.one def execute(self): if self.registration_id.state in ['open', 'done' ] and not self.mail_sent: self.scheduler_id.template_id.send_mail(self.registration_id.id) self.write({'mail_sent': True}) @api.one @api.depends('registration_id', 'scheduler_id.interval_unit', 'scheduler_id.interval_type') def _compute_scheduled_date(self): if self.registration_id: date_open = self.registration_id.date_open date_open_datetime = date_open and datetime.strptime( date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT) or fields.datetime.now() self.scheduled_date = date_open_datetime + _INTERVALS[ self.scheduler_id.interval_unit]( self.scheduler_id.interval_nbr) else: self.scheduled_date = False
class ImLivechatReportOperator(models.Model): """ Livechat Support Report on the Operator """ _name = "im_livechat.report.operator" _description = "Livechat Support Report" _order = 'livechat_channel_id, partner_id' _auto = False partner_id = fields.Many2one('res.partner', 'Operator', readonly=True) livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel', readonly=True) nbr_channel = fields.Integer('# of Sessions', readonly=True, group_operator="sum", help="Number of conversation") channel_id = fields.Many2one('mail.channel', 'Conversation', readonly=True) start_date = fields.Datetime('Start Date of session', readonly=True, help="Start date of the conversation") time_to_answer = fields.Float('Time to answer', digits=(16, 2), readonly=True, group_operator="avg", help="Average time to give the first answer to the visitor") duration = fields.Float('Average duration', digits=(16, 2), readonly=True, group_operator="avg", help="Duration of the conversation (in seconds)") @api.model_cr def init(self): # Note : start_date_hour must be remove when the read_group will allow grouping on the hour of a datetime. Don't forget to change the view ! tools.drop_view_if_exists(self.env.cr, 'im_livechat_report_operator') self.env.cr.execute(""" CREATE OR REPLACE VIEW im_livechat_report_operator AS ( SELECT row_number() OVER () AS id, P.id as partner_id, L.id as livechat_channel_id, count(C.id) as nbr_channel, C.id as channel_id, C.create_date as start_date, EXTRACT('epoch' FROM (max((SELECT (max(M.create_date)) FROM mail_message M JOIN mail_message_mail_channel_rel R ON (R.mail_message_id = M.id) WHERE R.mail_channel_id = C.id))-C.create_date)) as duration, EXTRACT('epoch' from ((SELECT min(M.create_date) FROM mail_message M, mail_message_mail_channel_rel R WHERE M.author_id=P.id AND R.mail_channel_id = C.id AND R.mail_message_id = M.id)-(SELECT min(M.create_date) FROM mail_message M, mail_message_mail_channel_rel R WHERE M.author_id IS NULL AND R.mail_channel_id = C.id AND R.mail_message_id = M.id))) as time_to_answer FROM im_livechat_channel_im_user O JOIN res_users U ON (O.user_id = U.id) JOIN res_partner P ON (U.partner_id = P.id) LEFT JOIN im_livechat_channel L ON (L.id = O.channel_id) LEFT JOIN mail_channel C ON (C.livechat_channel_id = L.id) GROUP BY P.id, L.id, C.id, C.create_date ) """)
class Meeting(models.Model): _inherit = "calendar.event" oe_update_date = fields.Datetime('izi Update Date') @api.model def get_fields_need_update_google(self): recurrent_fields = self._get_recurrent_fields() return recurrent_fields + [ 'name', 'description', 'allday', 'start', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'privacy', 'active', 'start_date', 'start_datetime', 'stop_date', 'stop_datetime' ] @api.multi def write(self, values): sync_fields = set(self.get_fields_need_update_google()) if ( set(values) and sync_fields ) and 'oe_update_date' not in values and 'NewMeeting' not in self._context: values['oe_update_date'] = fields.Datetime.now() return super(Meeting, self).write(values) @api.multi def copy(self, default=None): default = default or {} if default.get('write_type', False): del default['write_type'] elif default.get('recurrent_id', False): default['oe_update_date'] = fields.Datetime.now() else: default['oe_update_date'] = False return super(Meeting, self).copy(default) @api.multi def unlink(self, can_be_deleted=False): return super(Meeting, self).unlink(can_be_deleted=can_be_deleted)
class test_model(models.Model): _name = 'test_converter.test_model' char = fields.Char() integer = fields.Integer() float = fields.Float() numeric = fields.Float(digits=(16, 2)) many2one = fields.Many2one('test_converter.test_model.sub', group_expand='_gbf_m2o') binary = fields.Binary() date = fields.Date() datetime = fields.Datetime() selection = fields.Selection([ (1, "réponse A"), (2, "réponse B"), (3, "réponse C"), (4, "réponse <D>"), ]) selection_str = fields.Selection( [ ('A', u"Qu'il n'est pas arrivé à Toronto"), ('B', u"Qu'il était supposé arriver à Toronto"), ('C', u"Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"), ('D', u"La réponse D"), ], string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et " u"qu'il fait une escale technique à St Claude, on dit:") html = fields.Html() text = fields.Text() # `base` module does not contains any model that implement the functionality # `group_expand`; test this feature here... @api.model def _gbf_m2o(self, subs, domain, order): sub_ids = subs._search([], order=order, access_rights_uid=SUPERUSER_ID) return subs.browse(sub_ids)
class EventEvent(models.Model): """Event""" _name = 'event.event' _description = 'Event' _inherit = ['mail.thread'] _order = 'date_begin' name = fields.Char( string='Event Name', translate=True, required=True, readonly=False, states={'done': [('readonly', True)]}) active = fields.Boolean(default=True) user_id = fields.Many2one( 'res.users', string='Responsible', default=lambda self: self.env.user, track_visibility="onchange", readonly=False, states={'done': [('readonly', True)]}) company_id = fields.Many2one( 'res.company', string='Company', change_default=True, default=lambda self: self.env['res.company']._company_default_get('event.event'), required=False, readonly=False, states={'done': [('readonly', True)]}) organizer_id = fields.Many2one( 'res.partner', string='Organizer', track_visibility="onchange", default=lambda self: self.env.user.company_id.partner_id) event_type_id = fields.Many2one( 'event.type', string='Category', readonly=False, states={'done': [('readonly', True)]}, oldname='type') color = fields.Integer('Kanban Color Index') event_mail_ids = fields.One2many('event.mail', 'event_id', string='Mail Schedule', copy=True) # Seats and computation seats_max = fields.Integer( string='Maximum Attendees Number', oldname='register_max', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help="For each event you can define a maximum registration of seats(number of attendees), above this numbers the registrations are not accepted.") seats_availability = fields.Selection( [('limited', 'Limited'), ('unlimited', 'Unlimited')], 'Maximum Attendees', required=True, default='unlimited') seats_min = fields.Integer( string='Minimum Attendees', oldname='register_min', help="For each event you can define a minimum reserved seats (number of attendees), if it does not reach the mentioned registrations the event can not be confirmed (keep 0 to ignore this rule)") seats_reserved = fields.Integer( oldname='register_current', string='Reserved Seats', store=True, readonly=True, compute='_compute_seats') seats_available = fields.Integer( oldname='register_avail', string='Available Seats', store=True, readonly=True, compute='_compute_seats') seats_unconfirmed = fields.Integer( oldname='register_prospect', string='Unconfirmed Seat Reservations', store=True, readonly=True, compute='_compute_seats') seats_used = fields.Integer( oldname='register_attended', string='Number of Participants', store=True, readonly=True, compute='_compute_seats') seats_expected = fields.Integer( string='Number of Expected Attendees', readonly=True, compute='_compute_seats') # Registration fields registration_ids = fields.One2many( 'event.registration', 'event_id', string='Attendees', readonly=False, states={'done': [('readonly', True)]}) # Date fields date_tz = fields.Selection('_tz_get', string='Timezone', required=True, default=lambda self: self.env.user.tz or 'UTC') date_begin = fields.Datetime( string='Start Date', required=True, track_visibility='onchange', states={'done': [('readonly', True)]}) date_end = fields.Datetime( string='End Date', required=True, track_visibility='onchange', states={'done': [('readonly', True)]}) date_begin_located = fields.Char(string='Start Date Located', compute='_compute_date_begin_tz') date_end_located = fields.Char(string='End Date Located', compute='_compute_date_end_tz') state = fields.Selection([ ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'), ('confirm', 'Confirmed'), ('done', 'Done')], string='Status', default='draft', readonly=True, required=True, copy=False, help="If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'.") auto_confirm = fields.Boolean(string='Autoconfirm Registrations') is_online = fields.Boolean('Online Event') address_id = fields.Many2one( 'res.partner', string='Location', default=lambda self: self.env.user.company_id.partner_id, readonly=False, states={'done': [('readonly', True)]}, track_visibility="onchange") country_id = fields.Many2one('res.country', 'Country', related='address_id.country_id', store=True) twitter_hashtag = fields.Char('Twitter Hashtag') description = fields.Html( string='Description', oldname='note', translate=html_translate, sanitize_attributes=False, readonly=False, states={'done': [('readonly', True)]}) # badge fields badge_front = fields.Html(string='Badge Front') badge_back = fields.Html(string='Badge Back') badge_innerleft = fields.Html(string='Badge Inner Left') badge_innerright = fields.Html(string='Badge Inner Right') event_logo = fields.Html(string='Event Logo') @api.multi @api.depends('seats_max', 'registration_ids.state') def _compute_seats(self): """ Determine reserved, available, reserved but unconfirmed and used seats. """ # initialize fields to 0 for event in self: event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0 # aggregate registrations by event and by state if self.ids: state_field = { 'draft': 'seats_unconfirmed', 'open': 'seats_reserved', 'done': 'seats_used', } query = """ SELECT event_id, state, count(event_id) FROM event_registration WHERE event_id IN %s AND state IN ('draft', 'open', 'done') GROUP BY event_id, state """ self._cr.execute(query, (tuple(self.ids),)) for event_id, state, num in self._cr.fetchall(): event = self.browse(event_id) event[state_field[state]] += num # compute seats_available for event in self: if event.seats_max > 0: event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used) event.seats_expected = event.seats_unconfirmed + event.seats_reserved + event.seats_used @api.model def _tz_get(self): return [(x, x) for x in pytz.all_timezones] @api.one @api.depends('date_tz', 'date_begin') def _compute_date_begin_tz(self): if self.date_begin: self.date_begin_located = format_tz(self.with_context(use_babel=True).env, self.date_begin, tz=self.date_tz) else: self.date_begin_located = False @api.one @api.depends('date_tz', 'date_end') def _compute_date_end_tz(self): if self.date_end: self.date_end_located = format_tz(self.with_context(use_babel=True).env, self.date_end, tz=self.date_tz) else: self.date_end_located = False @api.onchange('event_type_id') def _onchange_type(self): if self.event_type_id: self.seats_min = self.event_type_id.default_registration_min self.seats_max = self.event_type_id.default_registration_max if self.event_type_id.default_registration_max: self.seats_availability = 'limited' if self.event_type_id.auto_confirm: self.auto_confirm = self.event_type_id.auto_confirm if self.event_type_id.use_hashtag: self.twitter_hashtag = self.event_type_id.default_hashtag if self.event_type_id.use_timezone: self.date_tz = self.event_type_id.default_timezone self.is_online = self.event_type_id.is_online if self.event_type_id.event_type_mail_ids: self.event_mail_ids = [(5, 0, 0)] + [{ 'template_id': line.template_id, 'interval_nbr': line.interval_nbr, 'interval_unit': line.interval_unit, 'interval_type': line.interval_type} for line in self.event_type_id.event_type_mail_ids] @api.constrains('seats_min', 'seats_max', 'seats_availability') def _check_seats_min_max(self): if any(event.seats_availability == 'limited' and event.seats_min > event.seats_max for event in self): raise ValidationError(_('Maximum attendees number should be greater than minimum attendees number.')) @api.constrains('seats_max', 'seats_available') def _check_seats_limit(self): if any(event.seats_availability == 'limited' and event.seats_max and event.seats_available < 0 for event in self): raise ValidationError(_('No more available seats.')) @api.one @api.constrains('date_begin', 'date_end') def _check_closing_date(self): if self.date_end < self.date_begin: raise ValidationError(_('Closing Date cannot be set before Beginning Date.')) @api.multi @api.depends('name', 'date_begin', 'date_end') def name_get(self): result = [] for event in self: date_begin = fields.Datetime.from_string(event.date_begin) date_end = fields.Datetime.from_string(event.date_end) dates = [fields.Date.to_string(fields.Datetime.context_timestamp(event, dt)) for dt in [date_begin, date_end] if dt] dates = sorted(set(dates)) result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates)))) return result @api.model def create(self, vals): res = super(EventEvent, self).create(vals) if res.organizer_id: res.message_subscribe([res.organizer_id.id]) if res.auto_confirm: res.button_confirm() return res @api.multi def write(self, vals): res = super(EventEvent, self).write(vals) if vals.get('organizer_id'): self.message_subscribe([vals['organizer_id']]) return res @api.multi def copy(self, default=None): self.ensure_one() default = dict(default or {}, name=_("%s (copy)") % (self.name)) return super(EventEvent, self).copy(default) @api.one def button_draft(self): self.state = 'draft' @api.multi def button_cancel(self): if any('done' in event.mapped('registration_ids.state') for event in self): raise UserError(_("There are already attendees who attended this event. Please reset it to draft if you want to cancel this event.")) self.registration_ids.write({'state': 'cancel'}) self.state = 'cancel' @api.one def button_done(self): self.state = 'done' @api.one def button_confirm(self): self.state = 'confirm' @api.one def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: self.state != 'cancel'): for attendee in self.registration_ids.filtered(filter_func): self.env['mail.template'].browse(template_id).send_mail(attendee.id, force_send=force_send) @api.multi def _is_event_registrable(self): return True
class EventRegistration(models.Model): _name = 'event.registration' _description = 'Attendee' _inherit = ['mail.thread'] _order = 'name, create_date desc' origin = fields.Char( string='Source Document', readonly=True, help="Reference of the document that created the registration, for example a sales order") event_id = fields.Many2one( 'event.event', string='Event', required=True, readonly=True, states={'draft': [('readonly', False)]}) partner_id = fields.Many2one( 'res.partner', string='Contact', states={'done': [('readonly', True)]}) date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.datetime.now()) # weird crash is directly now date_closed = fields.Datetime(string='Attended Date', readonly=True) event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True) event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True) company_id = fields.Many2one( 'res.company', string='Company', related='event_id.company_id', store=True, readonly=True, states={'draft': [('readonly', False)]}) state = fields.Selection([ ('draft', 'Unconfirmed'), ('cancel', 'Cancelled'), ('open', 'Confirmed'), ('done', 'Attended')], string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange') email = fields.Char(string='Email') phone = fields.Char(string='Phone') name = fields.Char(string='Attendee Name', index=True) @api.one @api.constrains('event_id', 'state') def _check_seats_limit(self): if self.event_id.seats_availability == 'limited' and self.event_id.seats_max and self.event_id.seats_available < (1 if self.state == 'draft' else 0): raise ValidationError(_('No more seats available for this event.')) @api.multi def _check_auto_confirmation(self): if self._context.get('registration_force_draft'): return False if any(registration.event_id.state != 'confirm' or not registration.event_id.auto_confirm or (not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self): return False return True @api.model def create(self, vals): registration = super(EventRegistration, self).create(vals) if registration._check_auto_confirmation(): registration.sudo().confirm_registration() return registration @api.model def _prepare_attendee_values(self, registration): """ Method preparing the values to create new attendees based on a sales order line. It takes some registration data (dict-based) that are optional values coming from an external input like a web page. This method is meant to be inherited in various addons that sell events. """ partner_id = registration.pop('partner_id', self.env.user.partner_id) event_id = registration.pop('event_id', False) data = { 'name': registration.get('name', partner_id.name), 'phone': registration.get('phone', partner_id.phone), 'email': registration.get('email', partner_id.email), 'partner_id': partner_id.id, 'event_id': event_id and event_id.id or False, } data.update({key: value for key, value in registration.items() if key in self._fields}) return data @api.one def do_draft(self): self.state = 'draft' @api.one def confirm_registration(self): self.state = 'open' # auto-trigger after_sub (on subscribe) mail schedulers, if needed onsubscribe_schedulers = self.event_id.event_mail_ids.filtered( lambda s: s.interval_type == 'after_sub') onsubscribe_schedulers.execute() @api.one def button_reg_close(self): """ Close Registration """ today = fields.Datetime.now() if self.event_id.date_begin <= today and self.event_id.state == 'confirm': self.write({'state': 'done', 'date_closed': today}) elif self.event_id.state == 'draft': raise UserError(_("You must wait the event confirmation before doing this action.")) else: raise UserError(_("You must wait the event starting day before doing this action.")) @api.one def button_reg_cancel(self): self.state = 'cancel' @api.onchange('partner_id') def _onchange_partner(self): if self.partner_id: contact_id = self.partner_id.address_get().get('contact', False) if contact_id: contact = self.env['res.partner'].browse(contact_id) self.name = contact.name or self.name self.email = contact.email or self.email self.phone = contact.phone or self.phone @api.multi def message_get_suggested_recipients(self): recipients = super(EventRegistration, self).message_get_suggested_recipients() try: for attendee in self: if attendee.partner_id: attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer')) elif attendee.email: attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email')) except AccessError: # no read access rights -> ignore suggested recipients pass return recipients def _message_post_after_hook(self, message): if self.email and not self.partner_id: # we consider that posting a message with a specified recipient (not a follower, a specific one) # on a document without customer means that it was created through the chatter using # suggested recipients. This heuristic allows to avoid ugly hacks in JS. new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email) if new_partner: self.search([ ('partner_id', '=', False), ('email', '=', new_partner.email), ('state', 'not in', ['cancel']), ]).write({'partner_id': new_partner.id}) return super(EventRegistration, self)._message_post_after_hook(message) @api.multi def action_send_badge_email(self): """ Open a window to compose an email, with the template - 'event_badge' message loaded by default """ self.ensure_one() template = self.env.ref('event.event_registration_mail_template_badge') compose_form = self.env.ref('mail.email_compose_message_wizard_form') ctx = dict( default_model='event.registration', default_res_id=self.id, default_use_template=bool(template), default_template_id=template.id, default_composition_mode='comment', ) return { 'name': _('Compose Email'), 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', 'views': [(compose_form.id, 'form')], 'view_id': compose_form.id, 'target': 'new', 'context': ctx, } @api.multi def get_date_range_str(self): self.ensure_one() today = fields.Datetime.from_string(fields.Datetime.now()) event_date = fields.Datetime.from_string(self.event_begin_date) diff = (event_date.date() - today.date()) if diff.days <= 0: return _('today') elif diff.days == 1: return _('tomorrow') elif (diff.days < 7): return _('in %d days') % (diff.days, ) elif (diff.days < 14): return _('next week') elif event_date.month == (today + relativedelta(months=+1)).month: return _('next month') else: return _('on ') + format_tz(self.with_context({'use_babel': True}).env, self.event_begin_date, tz=self.event_id.date_tz or 'UTC') @api.multi def summary(self): self.ensure_one() return {'information': []}
class ProductWishlist(models.Model): _name = 'product.wishlist' _sql_constrains = [ ("session_or_partner_id", "CHECK(session IS NULL != partner_id IS NULL)", "Need a session or partner, but never both."), ("product_unique_session", "UNIQUE(product_id, session)", "Duplicated wishlisted product for this session."), ("product_unique_partner_id", "UNIQUE(product_id, partner_id)", "Duplicated wishlisted product for this partner."), ] partner_id = fields.Many2one('res.partner', string='Owner') session = fields.Char( help="Website session identifier where this product was wishlisted.") product_id = fields.Many2one('product.product', string='Product', required=True) currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True) pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added') price = fields.Monetary( digits=0, currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist') price_new = fields.Float( compute='compute_new_price', string='Current price', help='Current price of this product, using same pricelist, ...') website_id = fields.Many2one('website', required=True) create_date = fields.Datetime('Added Date', readonly=True, required=True) active = fields.Boolean(default=True, required=True) @api.multi @api.depends('pricelist_id', 'currency_id', 'product_id') def compute_new_price(self): for wish in self: wish.price_new = wish.product_id.with_context( pricelist=wish.pricelist_id.id).website_price @api.model def current(self): """Get all wishlist items that belong to current user or session.""" return self.search([ "|", ("partner_id", "=", self.env.user.partner_id.id), "&", ("partner_id", "=", False), ("session", "=", self.env.user.current_session), ]) @api.model def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False, session=False): wish = self.env['product.wishlist'].create({ 'partner_id': partner_id, 'session': session, 'product_id': product_id, 'currency_id': currency_id, 'pricelist_id': pricelist_id, 'price': price, 'website_id': website_id, }) return wish @api.model def _join_current_user_and_session(self): """Assign all dangling session wishlisted products to user.""" session_wishes = self.search([ ("session", "=", self.env.user.current_session), ("partner_id", "=", False), ]) partner_wishes = self.search([ ("partner_id", "=", self.env.user.partner_id.id), ]) partner_products = partner_wishes.mapped("product_id") # Remove session products already present for the user duplicated_wishes = session_wishes.filtered( lambda wish: wish.product_id <= partner_products) session_wishes -= duplicated_wishes duplicated_wishes.unlink() # Assign the rest to the user session_wishes.write({ "partner_id": self.env.user.partner_id.id, "session": False, }) @api.model def _garbage_collector(self, *args, **kwargs): """Remove wishlists for unexisting sessions.""" self.search([ ("create_date", "<", fields.Datetime.to_string(datetime.now() - timedelta( weeks=kwargs.get('wishlist_week', 5)))), ("partner_id", "=", False), ]).unlink()
class Property(models.Model): _name = 'ir.property' name = fields.Char(index=True) res_id = fields.Char( string='Resource', index=True, help="If not set, acts as a default value for new resources", ) company_id = fields.Many2one('res.company', string='Company', index=True) fields_id = fields.Many2one('ir.model.fields', string='Field', ondelete='cascade', required=True, index=True) value_float = fields.Float() value_integer = fields.Integer() value_text = fields.Text() # will contain (char, text) value_binary = fields.Binary() value_reference = fields.Char() value_datetime = fields.Datetime() type = fields.Selection([ ('char', 'Char'), ('float', 'Float'), ('boolean', 'Boolean'), ('integer', 'Integer'), ('text', 'Text'), ('binary', 'Binary'), ('many2one', 'Many2One'), ('date', 'Date'), ('datetime', 'DateTime'), ('selection', 'Selection'), ], required=True, default='many2one', index=True) @api.multi def _update_values(self, values): value = values.pop('value', None) if not value: return values prop = None type_ = values.get('type') if not type_: if self: prop = self[0] type_ = prop.type else: type_ = self._fields['type'].default(self) field = TYPE2FIELD.get(type_) if not field: raise UserError(_('Invalid type')) if field == 'value_reference': if isinstance(value, models.BaseModel): value = '%s,%d' % (value._name, value.id) elif isinstance(value, pycompat.integer_types): field_id = values.get('fields_id') if not field_id: if not prop: raise ValueError() field_id = prop.fields_id else: field_id = self.env['ir.model.fields'].browse(field_id) value = '%s,%d' % (field_id.sudo().relation, value) values[field] = value return values @api.multi def write(self, values): return super(Property, self).write(self._update_values(values)) @api.model def create(self, values): return super(Property, self).create(self._update_values(values)) @api.multi def get_by_record(self): self.ensure_one() if self.type in ('char', 'text', 'selection'): return self.value_text elif self.type == 'float': return self.value_float elif self.type == 'boolean': return bool(self.value_integer) elif self.type == 'integer': return self.value_integer elif self.type == 'binary': return self.value_binary elif self.type == 'many2one': if not self.value_reference: return False model, resource_id = self.value_reference.split(',') return self.env[model].browse(int(resource_id)).exists() elif self.type == 'datetime': return self.value_datetime elif self.type == 'date': if not self.value_datetime: return False return fields.Date.to_string( fields.Datetime.from_string(self.value_datetime)) return False @api.model def get(self, name, model, res_id=False): domain = self._get_domain(name, model) if domain is not None: domain = [('res_id', '=', res_id)] + domain #make the search with company_id asc to make sure that properties specific to a company are given first prop = self.search(domain, limit=1, order='company_id') if prop: return prop.get_by_record() return False def _get_domain(self, prop_name, model): self._cr.execute( "SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", (prop_name, model)) res = self._cr.fetchone() if not res: return None company_id = self._context.get( 'force_company') or self.env['res.company']._company_default_get( model, res[0]).id return [('fields_id', '=', res[0]), ('company_id', 'in', [company_id, False])] @api.model def get_multi(self, name, model, ids): """ Read the property field `name` for the records of model `model` with the given `ids`, and return a dictionary mapping `ids` to their corresponding value. """ if not ids: return {} domain = self._get_domain(name, model) if domain is None: return dict.fromkeys(ids, False) # retrieve the values for the given ids and the default value, too refs = {('%s,%s' % (model, id)): id for id in ids} refs[False] = False domain += [('res_id', 'in', list(refs))] # note: order by 'company_id asc' will return non-null values first props = self.search(domain, order='company_id asc') result = {} for prop in props: # for a given res_id, take the first property only id = refs.pop(prop.res_id, None) if id is not None: result[id] = prop.get_by_record() # set the default value to the ids that are not in result default_value = result.pop(False, False) for id in ids: result.setdefault(id, default_value) return result @api.model def set_multi(self, name, model, values, default_value=None): """ Assign the property field `name` for the records of model `model` with `values` (dictionary mapping record ids to their value). If the value for a given record is the same as the default value, the property entry will not be stored, to avoid bloating the database. If `default_value` is provided, that value will be used instead of the computed default value, to determine whether the value for a record should be stored or not. """ def clean(value): return value.id if isinstance(value, models.BaseModel) else value if not values: return if not default_value: domain = self._get_domain(name, model) if domain is None: raise Exception() # retrieve the default value for the field default_value = clean(self.get(name, model)) # retrieve the properties corresponding to the given record ids self._cr.execute( "SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", (name, model)) field_id = self._cr.fetchone()[0] company_id = self.env.context.get( 'force_company') or self.env['res.company']._company_default_get( model, field_id).id refs = {('%s,%s' % (model, id)): id for id in values} props = self.search([ ('fields_id', '=', field_id), ('company_id', '=', company_id), ('res_id', 'in', list(refs)), ]) # modify existing properties for prop in props: id = refs.pop(prop.res_id) value = clean(values[id]) if value == default_value: # avoid prop.unlink(), as it clears the record cache that can # contain the value of other properties to set on record! prop.check_access_rights('unlink') prop.check_access_rule('unlink') self._cr.execute("DELETE FROM ir_property WHERE id=%s", [prop.id]) elif value != clean(prop.get_by_record()): prop.write({'value': value}) # create new properties for records that do not have one yet for ref, id in refs.items(): value = clean(values[id]) if value != default_value: self.create({ 'fields_id': field_id, 'company_id': company_id, 'res_id': ref, 'name': name, 'value': value, 'type': self.env[model]._fields[name].type, }) @api.model def search_multi(self, name, model, operator, value): """ Return a domain for the records that match the given condition. """ default_matches = False include_zero = False field = self.env[model]._fields[name] if field.type == 'many2one': comodel = field.comodel_name def makeref(value): return value and '%s,%s' % (comodel, value) if operator == "=": value = makeref(value) # if searching properties not set, search those not in those set if value is False: default_matches = True elif operator in ('!=', '<=', '<', '>', '>='): value = makeref(value) elif operator in ('in', 'not in'): value = [makeref(v) for v in value] elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike'): # most probably inefficient... but correct target = self.env[comodel] target_names = target.name_search(value, operator=operator, limit=None) target_ids = [n[0] for n in target_names] operator, value = 'in', [makeref(v) for v in target_ids] elif field.type in ('integer', 'float'): # No record is created in ir.property if the field's type is float or integer with a value # equal to 0. Then to match with the records that are linked to a property field equal to 0, # the negation of the operator must be taken to compute the goods and the domain returned # to match the searched records is just the opposite. if value == 0 and operator == '=': operator = '!=' include_zero = True elif value <= 0 and operator == '>=': operator = '<' include_zero = True elif value < 0 and operator == '>': operator = '<=' include_zero = True elif value >= 0 and operator == '<=': operator = '>' include_zero = True elif value > 0 and operator == '<': operator = '>=' include_zero = True # retrieve the properties that match the condition domain = self._get_domain(name, model) if domain is None: raise Exception() props = self.search(domain + [(TYPE2FIELD[field.type], operator, value)]) # retrieve the records corresponding to the properties that match good_ids = [] for prop in props: if prop.res_id: res_model, res_id = prop.res_id.split(',') good_ids.append(int(res_id)) else: default_matches = True if include_zero: return [('id', 'not in', good_ids)] elif default_matches: # exclude all records with a property that does not match all_ids = [] props = self.search(domain + [('res_id', '!=', False)]) for prop in props: res_model, res_id = prop.res_id.split(',') all_ids.append(int(res_id)) bad_ids = list(set(all_ids) - set(good_ids)) return [('id', 'not in', bad_ids)] else: return [('id', 'in', good_ids)]
class StockScrap(models.Model): _name = 'stock.scrap' _order = 'id desc' def _get_default_scrap_location_id(self): return self.env['stock.location'].search( [('scrap_location', '=', True), ('company_id', 'in', [self.env.user.company_id.id, False])], limit=1).id def _get_default_location_id(self): company_user = self.env.user.company_id warehouse = self.env['stock.warehouse'].search( [('company_id', '=', company_user.id)], limit=1) if warehouse: return warehouse.lot_stock_id.id return None name = fields.Char('Reference', default=lambda self: _('New'), copy=False, readonly=True, required=True, states={'done': [('readonly', True)]}) origin = fields.Char(string='Source Document') product_id = fields.Many2one('product.product', 'Product', required=True, states={'done': [('readonly', True)]}) product_uom_id = fields.Many2one('product.uom', 'Unit of Measure', required=True, states={'done': [('readonly', True)]}) tracking = fields.Selection('Product Tracking', readonly=True, related="product_id.tracking") lot_id = fields.Many2one('stock.production.lot', 'Lot', states={'done': [('readonly', True)]}, domain="[('product_id', '=', product_id)]") package_id = fields.Many2one('stock.quant.package', 'Package', states={'done': [('readonly', True)]}) owner_id = fields.Many2one('res.partner', 'Owner', states={'done': [('readonly', True)]}) move_id = fields.Many2one('stock.move', 'Scrap Move', readonly=True) picking_id = fields.Many2one('stock.picking', 'Picking', states={'done': [('readonly', True)]}) location_id = fields.Many2one('stock.location', 'Location', domain="[('usage', '=', 'internal')]", required=True, states={'done': [('readonly', True)]}, default=_get_default_location_id) scrap_location_id = fields.Many2one( 'stock.location', 'Scrap Location', default=_get_default_scrap_location_id, domain="[('scrap_location', '=', True)]", required=True, states={'done': [('readonly', True)]}) scrap_qty = fields.Float('Quantity', default=1.0, required=True, states={'done': [('readonly', True)]}) state = fields.Selection([('draft', 'Draft'), ('done', 'Done')], string='Status', default="draft") date_expected = fields.Datetime('Expected Date', default=fields.Datetime.now) @api.onchange('picking_id') def _onchange_picking_id(self): if self.picking_id: self.location_id = ( self.picking_id.state == 'done' ) and self.picking_id.location_dest_id.id or self.picking_id.location_id.id @api.onchange('product_id') def onchange_product_id(self): if self.product_id: self.product_uom_id = self.product_id.uom_id.id @api.model def create(self, vals): if 'name' not in vals or vals['name'] == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code( 'stock.scrap') or _('New') scrap = super(StockScrap, self).create(vals) return scrap def unlink(self): if 'done' in self.mapped('state'): raise UserError(_('You cannot delete a scrap which is done.')) return super(StockScrap, self).unlink() def _get_origin_moves(self): return self.picking_id and self.picking_id.move_lines.filtered( lambda x: x.product_id == self.product_id) def _prepare_move_values(self): self.ensure_one() return { 'name': self.name, 'origin': self.origin or self.picking_id.name or self.name, 'product_id': self.product_id.id, 'product_uom': self.product_uom_id.id, 'product_uom_qty': self.scrap_qty, 'location_id': self.location_id.id, 'scrapped': True, 'location_dest_id': self.scrap_location_id.id, 'move_line_ids': [(0, 0, { 'product_id': self.product_id.id, 'product_uom_id': self.product_uom_id.id, 'qty_done': self.scrap_qty, 'location_id': self.location_id.id, 'location_dest_id': self.scrap_location_id.id, 'package_id': self.package_id.id, 'owner_id': self.owner_id.id, 'lot_id': self.lot_id.id, })], # 'restrict_partner_id': self.owner_id.id, 'picking_id': self.picking_id.id } @api.multi def do_scrap(self): for scrap in self: move = self.env['stock.move'].create(scrap._prepare_move_values()) move._action_done() scrap.write({'move_id': move.id, 'state': 'done'}) return True def action_get_stock_picking(self): action = self.env.ref('stock.action_picking_tree_all').read([])[0] action['domain'] = [('id', '=', self.picking_id.id)] return action def action_get_stock_move_lines(self): action = self.env.ref('stock.stock_move_line_action').read([])[0] action['domain'] = [('move_id', '=', self.move_id.id)] return action def action_validate(self): self.ensure_one() precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') available_qty = sum(self.env['stock.quant']._gather( self.product_id, self.location_id, self.lot_id, self.package_id, self.owner_id, strict=True).mapped('quantity')) if float_compare(available_qty, self.scrap_qty, precision_digits=precision) >= 0: return self.do_scrap() else: return { 'name': _('Insufficient Quantity'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'stock.warn.insufficient.qty.scrap', 'view_id': self.env.ref( 'stock.stock_warn_insufficient_qty_scrap_form_view').id, 'type': 'ir.actions.act_window', 'context': { 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_scrap_id': self.id }, 'target': 'new' }
class BlogPost(models.Model): _name = "blog.post" _description = "Blog Post" _inherit = [ 'mail.thread', 'website.seo.metadata', 'website.published.mixin' ] _order = 'id DESC' _mail_post_access = 'read' @api.multi def _compute_website_url(self): super(BlogPost, self)._compute_website_url() for blog_post in self: blog_post.website_url = "/blog/%s/post/%s" % (slug( blog_post.blog_id), slug(blog_post)) @api.multi @api.depends('post_date', 'visits') def _compute_ranking(self): res = {} for blog_post in self: if blog_post.id: # avoid to rank one post not yet saved and so withtout post_date in case of an onchange. age = datetime.now() - fields.Datetime.from_string( blog_post.post_date) res[blog_post.id] = blog_post.visits * ( 0.5 + random.random()) / max(3, age.days) return res def _default_content(self): return ''' <section class="s_text_block"> <div class="container"> <div class="row"> <div class="col-md-12 mb16 mt16"> <p class="o_default_snippet_text">''' + _( "Start writing here...") + '''</p> </div> </div> </div> </section> ''' name = fields.Char('Title', required=True, translate=True, default='') subtitle = fields.Char('Sub Title', translate=True) author_id = fields.Many2one('res.partner', 'Author', default=lambda self: self.env.user.partner_id) active = fields.Boolean('Active', default=True) cover_properties = fields.Text( 'Cover Properties', default= '{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}' ) blog_id = fields.Many2one('blog.blog', 'Blog', required=True, ondelete='cascade') tag_ids = fields.Many2many('blog.tag', string='Tags') content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize=False) teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser') teaser_manual = fields.Text(string='Teaser Content') website_message_ids = fields.One2many( domain=lambda self: [('model', '=', self._name), ('message_type', '=', 'comment'), ('path', '=', False)]) # creation / update stuff create_date = fields.Datetime('Created on', index=True, readonly=True) published_date = fields.Datetime('Published Date') post_date = fields.Datetime( 'Publishing date', compute='_compute_post_date', inverse='_set_post_date', store=True, help= "The blog post will be visible for your visitors as of this date on the website if it is set as published." ) create_uid = fields.Many2one('res.users', 'Created by', index=True, readonly=True) write_date = fields.Datetime('Last Modified on', index=True, readonly=True) write_uid = fields.Many2one('res.users', 'Last Contributor', index=True, readonly=True) author_avatar = fields.Binary(related='author_id.image_small', string="Avatar") visits = fields.Integer('No of Views', copy=False) ranking = fields.Float(compute='_compute_ranking', string='Ranking') @api.multi @api.depends('content', 'teaser_manual') def _compute_teaser(self): for blog_post in self: if blog_post.teaser_manual: blog_post.teaser = blog_post.teaser_manual else: content = html2plaintext(blog_post.content).replace('\n', ' ') blog_post.teaser = content[:150] + '...' @api.multi def _set_teaser(self): for blog_post in self: blog_post.teaser_manual = blog_post.teaser @api.multi @api.depends('create_date', 'published_date') def _compute_post_date(self): for blog_post in self: if blog_post.published_date: blog_post.post_date = blog_post.published_date else: blog_post.post_date = blog_post.create_date @api.multi def _set_post_date(self): for blog_post in self: blog_post.published_date = blog_post.post_date if not blog_post.published_date: blog_post._write(dict(post_date=blog_post.create_date) ) # dont trigger inverse function def _check_for_publication(self, vals): if vals.get('website_published'): for post in self: post.blog_id.message_post_with_view( 'website_blog.blog_post_template_new_post', subject=post.name, values={'post': post}, subtype_id=self.env['ir.model.data'].xmlid_to_res_id( 'website_blog.mt_blog_blog_published')) return True return False @api.model def create(self, vals): post_id = super(BlogPost, self.with_context(mail_create_nolog=True)).create(vals) post_id._check_for_publication(vals) return post_id @api.multi def write(self, vals): result = True for post in self: copy_vals = dict(vals) if 'website_published' in vals and 'published_date' not in vals and ( post.published_date or '') <= fields.Datetime.now(): copy_vals['published_date'] = vals[ 'website_published'] and fields.Datetime.now() or False result &= super(BlogPost, self).write(copy_vals) self._check_for_publication(vals) return result @api.multi def get_access_action(self, access_uid=None): """ Instead of the classic form view, redirect to the post on website directly if user is an employee or if the post is published. """ self.ensure_one() user = access_uid and self.env['res.users'].sudo().browse( access_uid) or self.env.user if user.share and not self.sudo().website_published: return super(BlogPost, self).get_access_action(access_uid) return { 'type': 'ir.actions.act_url', 'url': self.url, 'target': 'self', 'target_type': 'public', 'res_id': self.id, } @api.multi def _notification_recipients(self, message, groups): groups = super(BlogPost, self)._notification_recipients(message, groups) for group_name, group_method, group_data in groups: group_data['has_button_access'] = True return groups @api.multi def message_get_message_notify_values(self, message, message_values): """ Override to avoid keeping all notified recipients of a comment. We avoid tracking needaction on post comments. Only emails should be sufficient. """ if message.message_type == 'comment': return { 'needaction_partner_ids': [], } return {}