# -*- coding: utf-8 -*- from openerp.exceptions import Warning from openerp import models, fields, api from openerp.exceptions import ValidationError from openerp.tools.translate import _ TOO_MUCH_EXPERIMENTS = 2 OVERLAP_EXPERIMENT = 1 CREATE_EXPERIMENT = 0 class Experiment_version(models.Model): """ Allow to define the versions contained in an experiment. The frequency is a ponderation to determine the probability to visit a version in an experiment. The googe_index is the index of a version in an experiment, used to send data to Google Analytics. """ _name = "website_version.experiment.version" _rec_name = "version_id" version_id = fields.Many2one('website_version.version', string="Version", required=True, ondelete='cascade') experiment_id = fields.Many2one('website_version.experiment', string="Experiment", required=True, ondelete='cascade') frequency = fields.Selection([('10', 'Less'), ('50', 'Medium'), ('80', 'More')], string='Frequency', default='50') google_index = fields.Integer(string='Google index') class Goals(models.Model): """ Allow to define the goal of an experiment. The goals are defined in the Google Analytics account and can be synchronised in backend. """ _name = "website_version.goals" name = fields.Char(string="Name", required=True) google_ref = fields.Char(string="Google Reference", required=True) class Experiment(models.Model): """An experiment pointed to some experiment_versions and dispatch each website visitor to a version. """ _name = "website_version.experiment" _inherit = ['mail.thread'] _order = 'sequence' @api.multi @api.constrains('state') def _check_view(self): #No overlap for running experiments for exp in self: if exp.state == 'running': for exp_ver in exp.experiment_version_ids: if exp_ver.search([('version_id.view_ids.key', 'in', [v.key for v in exp_ver.version_id.view_ids]), ('experiment_id', '!=', exp_ver.experiment_id.id), ('experiment_id.website_id', '=', exp_ver.experiment_id.website_id.id), ('experiment_id.state', '=', 'running')]): raise ValidationError('This experiment contains a view which is already used in another running experience') return True @api.multi @api.constrains('website_id', 'experiment_version_ids') def _check_website(self): for exp in self: for exp_ver in exp.experiment_version_ids: if not exp_ver.version_id.website_id.id == exp.website_id.id: raise ValidationError('This experiment must have versions which are in the same website') def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True): """ Override read_group to always display all states. """ if groupby and groupby[0] == "state": # Default result structure # states = self._get_state_list(cr, uid, context=context) states = [('running', 'Running'), ('paused', 'Paused'), ('ended', 'Ended')] read_group_all_states = [{ '__context': {'group_by': groupby[1:]}, '__domain': domain + [('state', '=', state_value)], 'state': state_value, 'state_count': 0, } for state_value, state_name in states] # Get standard results read_group_res = super(Experiment, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) # Update standard results with default results result = [] for state_value, state_name in states: res = filter(lambda x: x['state'] == state_value, read_group_res) if not res: res = filter(lambda x: x['state'] == state_value, read_group_all_states) res[0]['state'] = [state_value, state_name] result.append(res[0]) return result else: return super(Experiment, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) @api.one def _get_version_number(self): for exp in self: exp.version_number = len(exp.experiment_version_ids) + 1 name = fields.Char(string="Title", required=True) experiment_version_ids = fields.One2many('website_version.experiment.version', 'experiment_id', string="Experiment Version") website_id = fields.Many2one('website', string="Website", required=True) state = fields.Selection([('running', 'Running'), ('paused', 'Paused'), ('ended', 'Ended')], 'Status', required=True, copy=False, track_visibility='onchange', default='running') goal_id = fields.Many2one('website_version.goals', string="Objective", required=True) color = fields.Integer('Color Index') version_number = fields.Integer(compute=_get_version_number, string='Version Number') sequence = fields.Integer('Sequence', required=True, default=1) google_id = fields.Char(string="Google id") @api.model def create(self, vals): exp = { 'name': vals['name'], 'objectiveMetric': self.env['website_version.goals'].browse([vals['goal_id']])[0].google_ref, 'status': vals['state'], 'variations': [{'name': 'master', 'url': 'http://localhost/master'}] } version_list = vals.get('experiment_version_ids', []) for version in version_list: if version[0] == 0: name = self.env['website_version.version'].browse([version[2]['version_id']])[0].name #We must give a URL for each version in the experiment exp['variations'].append({'name': name, 'url': 'http://localhost/' + name}) else: raise Warning(_("The experiment you try to create has a bad format.")) if not version_list: raise Warning(_("You must select at least one version in your experiment.")) vals['google_id'] = self.env['google.management'].create_an_experiment(exp, vals['website_id']) return super(Experiment, self).create(vals) @api.multi def write(self, vals): state = vals.get('state') for exp in self: if state and exp.state == 'ended': raise Warning(_("You cannot modify an ended experiment.")) elif state == 'ended': #google_data is the data to send to Googe google_data = { 'name': exp.name, 'status': state, 'variations': [{'name': 'master', 'url': 'http://localhost/master'}], } for exp_v in exp.experiment_version_ids: google_data['variations'].append({'name': exp_v.version_id.name, 'url': 'http://localhost/'+exp_v.version_id.name}) #to check the constraints before to write on the google analytics account self.env['google.management'].update_an_experiment(google_data, exp.google_id, exp.website_id.id) return super(Experiment, self).write(vals) @api.multi def unlink(self): for exp in self: self.env['google.management'].delete_an_experiment(exp.google_id, exp.website_id.id) return super(Experiment, self).unlink() @api.multi def update_goals(self): gm_obj = self.env['google.management'] goals_obj = self.env['website_version.goals'] website_id = self.env.context.get('website_id') if not website_id: raise Warning("You must specify the website.") for goal in gm_obj.get_goal_info(website_id)[1]['items']: if not goals_obj.search([('name', '=', goal['name'])]): vals = {'name': goal['name'], 'google_ref': 'ga:goal' + goal['id'] + 'Completions'} goals_obj.create(vals) def check_no_overlap(self, version_ids): if self.search_count(['|', ('state', '=', 'running'), ('state', '=', 'paused')]) >= 24: return {'existing': TOO_MUCH_EXPERIMENTS, 'name': ""} #Check if version_ids don't overlap with running experiments and return the name of the experiment if there 's an overlap version_keys = set([v['key'] for v in self.env['ir.ui.view'].search_read([('version_id', 'in', version_ids)], ['key'])]) exp_mod = self.env['website_version.experiment'] exps = exp_mod.search([('state', '=', 'running'), ('website_id', '=', self.env.context.get('website_id'))]) for exp in exps: for exp_ver in exp.experiment_version_ids: for view in exp_ver.version_id.view_ids: if view.key in version_keys: return {'existing': OVERLAP_EXPERIMENT, 'name': exp.name} return {'existing': CREATE_EXPERIMENT, 'name': ""}