From 531d641375f5cee7d3a5e629f6b1eaceee8b2739 Mon Sep 17 00:00:00 2001 From: Mitali Patel Date: Wed, 8 Feb 2017 16:41:18 +0100 Subject: [PATCH] [IMP] event_track: make tracks easier to use Purpose of this commit is to clean and ease the use of tracks in event management. * status is change to stages. Those are global for all event as we consider track acceptation process as similar across events; * change speakers many2many to a one2many. Most event have only one speaker or at least a main speaker; * add various chatter features: tracking of template, subtypes, suggested recipients, ... Also add activities on tracks and their various filters; * add automatic mailing on stages like already done in tasks or issues; * tweak display of tracks on agenda. Event manager see unpublished tracks with the right label in order to know the status of the various event tracks. --- addons/website_event/models/event.py | 64 ++++---- .../website_event_track/controllers/main.py | 14 +- .../website_event_track/data/event_demo.xml | 122 +++++++-------- .../data/event_track_data.xml | 71 +++++++++ addons/website_event_track/models/event.py | 73 +++++---- .../website_event_track/models/event_track.py | 145 ++++++++++++------ .../security/ir.model.access.csv | 2 + .../views/event_track_templates.xml | 38 ++--- .../views/event_track_views.xml | 119 ++++++++++++-- .../website_event_track/views/event_views.xml | 19 ++- 10 files changed, 436 insertions(+), 231 deletions(-) diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py index 6c1e1ddf809..b6596223e3b 100644 --- a/addons/website_event/models/event.py +++ b/addons/website_event/models/event.py @@ -25,9 +25,10 @@ class Event(models.Model): ) is_participating = fields.Boolean("Is Participating", compute="_compute_is_participating") - show_menu = fields.Boolean('Dedicated Menu', compute='_get_show_menu', inverse='_set_show_menu', - help="Creates menus Introduction, Location and Register on the page " - " of the event on the website.", store=True) + website_menu = fields.Boolean( + 'Dedicated Menu', compute='_compute_website_menu', inverse='_set_website_menu', + help="Creates menus Introduction, Location and Register on the page " + " of the event on the website.", store=True) menu_id = fields.Many2one('website.menu', 'Event Menu') def _compute_is_participating(self): @@ -46,46 +47,41 @@ class Event(models.Model): if event.id: # avoid to perform a slug on a not yet saved record in case of an onchange. event.website_url = '/event/%s' % slug(event) - @api.multi - def _get_new_menu_pages(self): - """ Retuns a list of tuple ('Page name', 'relative page url') for the event """ + def _get_menu_entries(self): self.ensure_one() - todo = [ - (_('Introduction'), 'website_event.template_intro'), - (_('Location'), 'website_event.template_location') + return [ + (_('Introduction'), False, 'website_event.template_intro'), + (_('Location'), False, 'website_event.template_location'), + (_('Register'), '/event/%s/register' % slug(self), False), ] - result = [] - for name, path in todo: - complete_name = name + ' ' + self.name - newpath = self.env['website'].new_page(complete_name, path, ispage=False) - url = "/event/" + slug(self) + "/page/" + newpath - result.append((name, url)) - result.append((_('Register'), '/event/%s/register' % slug(self))) - return result @api.multi - def _set_show_menu(self): + def _set_website_menu(self): for event in self: - if event.menu_id and not event.show_menu: + if event.menu_id and not event.website_menu: event.menu_id.unlink() - elif event.show_menu and not event.menu_id: - root_menu = self.env['website.menu'].create({'name': event.name}) - to_create_menus = event._get_new_menu_pages() - seq = 0 - for name, url in to_create_menus: - self.env['website.menu'].create({ - 'name': name, - 'url': url, - 'parent_id': root_menu.id, - 'sequence': seq, - }) - seq += 1 - event.menu_id = root_menu + elif event.website_menu: + if not event.menu_id: + root_menu = self.env['website.menu'].create({'name': event.name}) + event.menu_id = root_menu + + for sequence, (name, url, xml_id) in enumerate(self._get_menu_entries()): + existing_pages = event.menu_id.child_id.mapped('name') + if name not in existing_pages: + if not url: + newpath = self.env['website'].new_page(name + ' ' + self.name, xml_id, ispage=False) + url = "/event/" + slug(self) + "/page/" + newpath + self.env['website.menu'].create({ + 'name': name, + 'url': url, + 'parent_id': event.menu_id.id, + 'sequence': sequence, + }) @api.multi - def _get_show_menu(self): + def _compute_website_menu(self): for event in self: - event.show_menu = bool(event.menu_id) + event.website_menu = bool(event.menu_id) @api.multi def google_map_img(self, zoom=8, width=298, height=298): diff --git a/addons/website_event_track/controllers/main.py b/addons/website_event_track/controllers/main.py index 544c749c635..5a5a096127c 100644 --- a/addons/website_event_track/controllers/main.py +++ b/addons/website_event_track/controllers/main.py @@ -65,7 +65,7 @@ class WebsiteEventTrackController(http.Controller): 'dates': dates } - @http.route(['''/event/ - True - True + True + True - True + True @@ -98,11 +98,11 @@ - + 3 - draft + + blocked - How to integrate hardware materials with the Odoo point of sale. @@ -110,8 +110,9 @@ - - confirmed + + + done How to develop real time apps, the website live chat module explained. @@ -120,8 +121,8 @@ - - confirmed + + How to develop automated tests in the Odoo web client. @@ -130,8 +131,8 @@ - - published + + The new way to promote your modules in the Apps platform and Odoo website. @@ -140,8 +141,8 @@ - - announced + + Detailed roadmap of accounting new modules and improvements for version 8. @@ -150,8 +151,8 @@ - - announced + + A technical explanation of Odoo as a CMS and a eCommerce platform for version 8. @@ -160,8 +161,8 @@ - - published + + Discover Odoo CRM: How to optimize your sales, from leads to sales orders. @@ -171,8 +172,9 @@ 2 - - published + + + blocked How to use Odoo for your HR process: recruitment, leaves management, appraisals, expenses, etc. @@ -183,8 +185,8 @@ 2 - - published + + Raising qualitive insights with the survey app @@ -194,7 +196,7 @@ 5 - draft + Discover Odoo Point-of-Sale: Your shop ready to use in 30 minutes. @@ -203,8 +205,8 @@ - - confirmed + + Manage your events with Odoo, the new training modules. @@ -213,8 +215,8 @@ - - announced + + Advanced reporting with Google Spreadsheets integration. @@ -223,8 +225,8 @@ - - published + + New Paypal modules (portal, handling, installments). @@ -233,8 +235,8 @@ - - published + + Odoo Mobile for Notes, Meetings and Messages. @@ -243,8 +245,8 @@ - - announced + + Odoo as your Enterprise Social Network. @@ -253,8 +255,8 @@ - - draft + + The Art of Making an Odoo Demo. @@ -263,8 +265,8 @@ - - draft + + How to build your marketing strategy for the purpose of generating leads with Odoo. @@ -274,8 +276,8 @@ 5 - - confirmed + + Advanced lead management with Odoo: tips and tricks from the fields @@ -285,8 +287,8 @@ 5 - - confirmed + + New Certification Program (valid from Oct. 2013). @@ -295,8 +297,8 @@ - - confirmed + + Recruiting high skilled talents with Odoo HR apps @@ -306,7 +308,7 @@ 7 - confirmed + Manage your KPIs (recomended to openERP partners). @@ -315,8 +317,8 @@ - - draft + + Key Success factors selling Odoo. @@ -325,8 +327,8 @@ - - announced + + Merge proposals review, code sprint (entire day). @@ -335,8 +337,8 @@ - - announced + + Merge proposals review, code sprint (entire afternoon) @@ -345,8 +347,8 @@ - - announced + + Odoo in 2014 @@ -354,8 +356,8 @@ - - published + + 3 @@ -364,8 +366,8 @@ - - published + + The new marketing strategy. @@ -373,8 +375,8 @@ - - published + + 6 @@ -383,7 +385,7 @@ - confirmed + Lunch @@ -391,7 +393,7 @@ - confirmed + diff --git a/addons/website_event_track/data/event_track_data.xml b/addons/website_event_track/data/event_track_data.xml index 7979921cbb5..88af18033e2 100644 --- a/addons/website_event_track/data/event_track_data.xml +++ b/addons/website_event_track/data/event_track_data.xml @@ -1,6 +1,32 @@ + + + Track: Confirmation + Confirmation of ${object.name} + + + +
+

+ Dear +

+ We are pleased to inform you that your proposal ${object.name} has been accepted and confirmed for the event ${object.event_id.name}. +

+ You will find more details here: +

+

+

Sincerely,

+
+
+ +
Bronze @@ -15,6 +41,36 @@ 1 + + Proposal + 1 + + + Confirmed + 2 + + + + Announced + 3 + + + Published + 4 + + + + Refused + 5 + + + + Cancelled + 6 + + + + New Track @@ -23,5 +79,20 @@ + + + Track Blocked + event.track + + + Track blocked + + + Track Ready + event.track + + + Track Ready for Next Stage +
diff --git a/addons/website_event_track/models/event.py b/addons/website_event_track/models/event.py index 08d8cd35b6c..6bcf0f1a757 100644 --- a/addons/website_event_track/models/event.py +++ b/addons/website_event_track/models/event.py @@ -9,49 +9,58 @@ class Event(models.Model): _inherit = "event.event" track_ids = fields.One2many('event.track', 'event_id', 'Tracks') + track_count = fields.Integer('Tracks', compute='_compute_track_count') + sponsor_ids = fields.One2many('event.sponsor', 'event_id', 'Sponsors') - show_track_proposal = fields.Boolean('Tracks Proposals', compute='_get_show_menu', inverse='_set_show_menu', store=True) - show_tracks = fields.Boolean('Show Tracks on Website', compute='_get_show_menu', inverse='_set_show_menu', store=True) - count_tracks = fields.Integer('Tracks', compute='_count_tracks') + sponsor_count = fields.Integer('Sponsors', compute='_compute_sponsor_count') + + website_track = fields.Boolean('Tracks on Website', compute='_compute_website_track', inverse='_set_website_menu') + website_track_proposal = fields.Boolean('Proposals on Website', compute='_compute_website_track_proposal', inverse='_set_website_menu') + allowed_track_tag_ids = fields.Many2many('event.track.tag', relation='event_allowed_track_tags_rel', string='Available Track Tags') - tracks_tag_ids = fields.Many2many('event.track.tag', relation='event_track_tags_rel', string='Track Tags', compute='_get_tracks_tag_ids', store=True) - count_sponsor = fields.Integer('# Sponsors', compute='_count_sponsor') + tracks_tag_ids = fields.Many2many( + 'event.track.tag', relation='event_track_tags_rel', string='Track Tags', + compute='_compute_tracks_tag_ids', store=True) @api.multi - def _get_new_menu_pages(self): - self.ensure_one() - result = super(Event, self)._get_new_menu_pages() - if self.show_tracks: - result.append((_('Talks'), '/event/%s/track' % slug(self))) - result.append((_('Agenda'), '/event/%s/agenda' % slug(self))) - if self.show_track_proposal: - result.append((_('Talk Proposals'), '/event/%s/track_proposal' % slug(self))) - return result - - @api.multi - def _set_show_menu(self): + def _compute_track_count(self): + data = self.env['event.track'].read_group([('stage_id.is_cancel', '!=', True)], ['event_id'], ['event_id']) + result = dict((data['event_id'][0], data['event_id_count']) for data in data) for event in self: - # if the number of menu items have changed, then menu items must be regenerated - if event.menu_id: - nbr_menu_items = len(event._get_new_menu_pages()) - if nbr_menu_items != len(event.menu_id.child_id): - event.menu_id.unlink() - return super(Event, self)._set_show_menu() + event.track_count = result.get(event.id, 0) @api.multi - def _count_tracks(self): - track_data = self.env['event.track'].read_group([('state', '!=', 'cancel')], ['event_id', 'state'], ['event_id']) - result = dict((data['event_id'][0], data['event_id_count']) for data in track_data) + def _compute_sponsor_count(self): + data = self.env['event.sponsor'].read_group([], ['event_id'], ['event_id']) + result = dict((data['event_id'][0], data['event_id_count']) for data in data) for event in self: - event.count_tracks = result.get(event.id, 0) + event.sponsor_count = result.get(event.id, 0) + + @api.multi + def _compute_website_track(self): + for event in self: + existing_pages = event.menu_id.child_id.mapped('name') + event.website_track = _('Talks') in existing_pages + + @api.multi + def _compute_website_track_proposal(self): + for event in self: + existing_pages = event.menu_id.child_id.mapped('name') + event.website_track_proposal = _('Talk Proposals') in existing_pages @api.multi @api.depends('track_ids.tag_ids') - def _get_tracks_tag_ids(self): + def _compute_tracks_tag_ids(self): for event in self: event.tracks_tag_ids = event.track_ids.mapped('tag_ids').ids - @api.multi - def _count_sponsor(self): - for event in self: - event.count_sponsor = len(event.sponsor_ids) + def _get_menu_entries(self): + self.ensure_one() + res = super(Event, self)._get_menu_entries() + if self.website_track: + res += [ + (_('Talks'), '/event/%s/track' % slug(self), False), + (_('Agenda'), '/event/%s/agenda' % slug(self), False)] + if self.website_track_proposal: + res += [(_('Talk Proposals'), '/event/%s/track_proposal' % slug(self), False)] + return res diff --git a/addons/website_event_track/models/event_track.py b/addons/website_event_track/models/event_track.py index 7560077da61..9b46b618d6e 100644 --- a/addons/website_event_track/models/event_track.py +++ b/addons/website_event_track/models/event_track.py @@ -27,26 +27,58 @@ class TrackLocation(models.Model): name = fields.Char('Room') +class TrackStage(models.Model): + _name = 'event.track.stage' + _description = 'Track Stage' + _order = 'sequence, id' + + name = fields.Char(string='Stage Name', required=True, translate=True) + sequence = fields.Integer(string='Sequence', default=1) + mail_template_id = fields.Many2one( + 'mail.template', string='Email Template', + domain=[('model', '=', 'event.track')], + help="If set an email will be sent to the customer when the track reaches this step.") + fold = fields.Boolean( + string='Folded in Kanban', + help='This stage is folded in the kanban view when there are no records in that stage to display.') + is_done = fields.Boolean(string='Accepted Stage') + is_cancel = fields.Boolean(string='Canceled Stage') + + class Track(models.Model): _name = "event.track" _description = 'Event Track' _order = 'priority, date' - _inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'website.seo.metadata', 'website.published.mixin'] _mail_mass_mailing = _('Track Speakers') + @api.model + def _get_default_stage_id(self): + return self.env['event.track.stage'].search([], limit=1).id + name = fields.Char('Title', required=True, translate=True) active = fields.Boolean(default=True) user_id = fields.Many2one('res.users', 'Responsible', track_visibility='onchange', default=lambda self: self.env.user) - partner_id = fields.Many2one('res.partner', 'Proposed by') - partner_name = fields.Char('Partner Name') - partner_email = fields.Char('Partner Email') - partner_phone = fields.Char('Partner Phone') - partner_biography = fields.Html('Partner Biography') - speaker_ids = fields.Many2many('res.partner', string='Speakers') + partner_id = fields.Many2one('res.partner', 'Speaker') + partner_name = fields.Char('Speaker Name') + partner_email = fields.Char('Speaker Email') + partner_phone = fields.Char('Speaker Phone') + partner_biography = fields.Html('Speaker Biography') tag_ids = fields.Many2many('event.track.tag', string='Tags') - state = fields.Selection([ - ('draft', 'Proposal'), ('confirmed', 'Confirmed'), ('announced', 'Announced'), ('published', 'Published'), ('refused', 'Refused'), ('cancel', 'Cancelled')], - 'Status', default='draft', required=True, copy=False, track_visibility='onchange') + stage_id = fields.Many2one( + 'event.track.stage', string='Stage', + index=True, copy=False, default=_get_default_stage_id, + group_expand='_read_group_stage_ids', + required=True, track_visibility='onchange') + kanban_state = fields.Selection([ + ('normal', 'Grey'), + ('done', 'Green'), + ('blocked', 'Red')], string='Kanban State', + copy=False, default='normal', required=True, track_visibility='onchange', + help="A track's kanban state indicates special situations affecting it:\n" + " * Grey is the default situation\n" + " * Red indicates something is preventing the progress of this track\n" + " * Green indicates the track is ready to be pulled to the next stage") description = fields.Html('Track Description', translate=html_translate, sanitize_attributes=False) date = fields.Datetime('Track Date') duration = fields.Float('Duration', default=1.5) @@ -57,7 +89,7 @@ class Track(models.Model): ('0', 'Low'), ('1', 'Medium'), ('2', 'High'), ('3', 'Highest')], 'Priority', required=True, default='1') - image = fields.Binary('Image', related='speaker_ids.image_medium', store=True, attachment=True) + image = fields.Binary('Image', related='partner_id.image_medium', store=True, attachment=True) @api.multi @api.depends('name') @@ -67,59 +99,72 @@ class Track(models.Model): if not isinstance(track.id, models.NewId): track.website_url = '/event/%s/track/%s' % (slug(track.event_id), slug(track)) + @api.onchange('partner_id') + def _onchange_partner_id(self): + if self.partner_id: + self.partner_name = self.partner_id.name + self.partner_email = self.partner_id.email + self.partner_phone = self.partner_id.phone + self.partner_biography = self.partner_id.website_description + @api.model def create(self, vals): - res = super(Track, self).create(vals) - res.message_subscribe(res.speaker_ids.ids) - res.message_post_with_view( + track = super(Track, self).create(vals) + + track.event_id.message_post_with_view( 'website_event_track.event_track_template_new', - subject=res.name, - subtype_id=self.env['ir.model.data'].xmlid_to_res_id('website_event_track.mt_event_track')) - return res + values={'track': track}, + subject=track.name, + subtype_id=self.env.ref('website_event_track.mt_event_track').id, + ) + + return track @api.multi def write(self, vals): - if vals.get('state') == 'published': - vals.update({'website_published': True}) + if 'stage_id' in vals and 'kanban_state' not in vals: + vals['kanban_state'] = 'normal' res = super(Track, self).write(vals) - if vals.get('speaker_ids'): - self.message_subscribe([speaker['id'] for speaker in self.resolve_2many_commands('speaker_ids', vals['speaker_ids'], ['id'])]) + if vals.get('partner_id'): + self.message_subscribe(vals['partner_id']) return res @api.model - def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): - """ Override read_group to always display all states. """ - if groupby and groupby[0] == "state": - # Default result structure - states = [('draft', 'Proposal'), ('confirmed', 'Confirmed'), ('announced', 'Announced'), ('published', 'Published'), ('cancel', 'Cancelled')] - 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(Track, self).read_group(domain, fields, groupby, offset=offset, limit=limit, 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) - if state_value == 'cancel': - res[0]['__fold'] = True - res[0]['state'] = [state_value, state_name] - result.append(res[0]) - return result - else: - return super(Track, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby) + def _read_group_stage_ids(self, stages, domain, order): + """ Always display all stages """ + return stages.search([], order=order) + + @api.multi + def _track_template(self, tracking): + res = super(Track, self)._track_template(tracking) + track = self[0] + changes, tracking_value_ids = tracking[track.id] + if 'stage_id' in changes and track.stage_id.mail_template_id: + res['stage_id'] = (track.stage_id.mail_template_id, {'composition_mode': 'mass_mail'}) + return res + + @api.multi + def _track_subtype(self, init_values): + self.ensure_one() + if 'kanban_state' in init_values and self.kanban_state == 'blocked': + return 'website_event_track.mt_track_blocked' + elif 'kanban_state' in init_values and self.kanban_state == 'done': + return 'website_event_track.mt_track_ready' + return super(Track, self)._track_subtype(init_values) + + @api.multi + def message_get_suggested_recipients(self): + recipients = super(Track, self).message_get_suggested_recipients() + for track in self: + if track.partner_email != track.partner_id.email: + track._message_add_suggested_recipient(recipients, email=track.partner_email, reason=_('Speaker Email')) + return recipients @api.multi def open_track_speakers_list(self): - self.ensure_one() return { 'name': _('Speakers'), - 'domain': [('id', 'in', [partner.id for partner in self.speaker_ids])], + 'domain': [('id', 'in', self.mapped('partner_id').ids)], 'view_type': 'form', 'view_mode': 'kanban,form', 'res_model': 'res.partner', @@ -130,6 +175,7 @@ class Track(models.Model): class SponsorType(models.Model): _name = "event.sponsor.type" + _description = 'Event Sponsor Type' _order = "sequence" name = fields.Char('Sponsor Type', required=True, translate=True) @@ -138,6 +184,7 @@ class SponsorType(models.Model): class Sponsor(models.Model): _name = "event.sponsor" + _description = 'Event Sponsor' _order = "sequence" event_id = fields.Many2one('event.event', 'Event', required=True) diff --git a/addons/website_event_track/security/ir.model.access.csv b/addons/website_event_track/security/ir.model.access.csv index 88b9061d615..729a0bc42df 100644 --- a/addons/website_event_track/security/ir.model.access.csv +++ b/addons/website_event_track/security/ir.model.access.csv @@ -10,3 +10,5 @@ access_event_track_sponsor_type_manager,event.track.sponsor.type.public,model_ev access_event_track_sponsor_type_public,event.track.sponsor.type.public,model_event_sponsor_type,,1,0,0,0 access_event_track_sponsor_manager,event.track.sponsor.type.public,model_event_sponsor,event.group_event_manager,1,1,1,1 access_event_track_sponsor_public,event.track.sponsor.public,model_event_sponsor,,1,0,0,0 +access_event_track_stage_all,event.track.stage.all,model_event_track_stage,,1,0,0,0 +access_event_track_stage_event_manager,event.track.stage.event.manager,model_event_track_stage,event.group_event_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/website_event_track/views/event_track_templates.xml b/addons/website_event_track/views/event_track_templates.xml index 585527f3b27..9e6e86b0c63 100644 --- a/addons/website_event_track/views/event_track_templates.xml +++ b/addons/website_event_track/views/event_track_templates.xml @@ -80,9 +80,10 @@ + unpublished
- +
@@ -97,7 +98,7 @@
- +
@@ -129,8 +130,8 @@ unpublished
    -
  • - +
  • +
  • @@ -224,17 +225,17 @@ -
    +
    -
    -

    -
    - +

    +
    +
    -
    +

    @@ -301,7 +302,7 @@

    Call for Proposals

    -
    +

    Proposals are closed!

    This event does not accept proposals. @@ -332,7 +333,7 @@

    -
    +
    @@ -448,14 +449,13 @@ diff --git a/addons/website_event_track/views/event_track_views.xml b/addons/website_event_track/views/event_track_views.xml index f10c2c12ce5..7a9e740a673 100644 --- a/addons/website_event_track/views/event_track_views.xml +++ b/addons/website_event_track/views/event_track_views.xml @@ -6,11 +6,14 @@ event.track.kanban event.track - + - + + + +