From 91eded2eb280ddb2dc4acfe92cab5c98a7554fee Mon Sep 17 00:00:00 2001 From: qmo-odoo Date: Thu, 16 Jan 2020 08:36:13 +0000 Subject: [PATCH] [IMP] website_event, event: Add event.tag.category and event.tag + rename event.type display name This commit changes the display name for the model event.type from "Event Category" to "Event Template" This change was needed before introducing the new model event.tag.category which will be used to group tags (event.tag) This commit introduces two new models: event.tag.category & event.tag The tags will be grouped by category and will be used to filter events. In the front-end, if the user activates the "filter by categories", each event.tag.category will generate a new dropdown of related tags. Clicking on one of these tags will add a new "tag badge" above the events (same way as in eLearning). LINKS: TaskID:2162438 PR: #43856 --- addons/event/__manifest__.py | 1 + addons/event/data/event_demo.xml | 50 ++++++++++ addons/event/models/__init__.py | 1 + addons/event/models/event_event.py | 13 ++- addons/event/models/event_tag.py | 26 +++++ addons/event/security/ir.model.access.csv | 4 + addons/event/views/event_menu_views.xml | 6 +- addons/event/views/event_tag_views.xml | 79 +++++++++++++++ addons/event/views/event_views.xml | 13 ++- addons/website_event/controllers/main.py | 37 +++++-- .../security/ir.model.access.csv | 2 + .../static/src/scss/website_event.scss | 8 -- .../website_event/views/event_templates.xml | 98 +++++++++++-------- 13 files changed, 276 insertions(+), 62 deletions(-) create mode 100644 addons/event/models/event_tag.py create mode 100644 addons/event/views/event_tag_views.xml diff --git a/addons/event/__manifest__.py b/addons/event/__manifest__.py index e49f260e24a..2c1e03c7d1c 100644 --- a/addons/event/__manifest__.py +++ b/addons/event/__manifest__.py @@ -32,6 +32,7 @@ Key Features 'views/res_config_settings_views.xml', 'views/event_templates.xml', 'views/res_partner_views.xml', + 'views/event_tag_views.xml' ], 'demo': [ 'data/res_users_demo.xml', diff --git a/addons/event/data/event_demo.xml b/addons/event/data/event_demo.xml index 91651959471..01022610b7e 100644 --- a/addons/event/data/event_demo.xml +++ b/addons/event/data/event_demo.xml @@ -25,6 +25,50 @@ Europe/Brussels + + + Age + + + + Activity + + + + 5-10 + + + + + 10-14 + + + + + 15-18 + + + + + 18+ + + + + + Culture + + + + + Music + + + + + Sport + + + Design Fair Los Angeles @@ -37,6 +81,7 @@ US/Pacific + Free @@ -72,6 +117,7 @@ blocked + @@ -125,6 +171,7 @@ limited 200 + Standard @@ -160,6 +207,7 @@ + Standard @@ -200,6 +248,7 @@ done + General Admission @@ -224,6 +273,7 @@ + diff --git a/addons/event/models/__init__.py b/addons/event/models/__init__.py index 943b57206f9..4aefa5d6068 100644 --- a/addons/event/models/__init__.py +++ b/addons/event/models/__init__.py @@ -5,6 +5,7 @@ from . import event_event from . import event_mail from . import event_registration from . import event_stage +from . import event_tag from . import event_ticket from . import res_config_settings from . import res_partner diff --git a/addons/event/models/event_event.py b/addons/event/models/event_event.py index 301cfb99266..a091da8ff6b 100644 --- a/addons/event/models/event_event.py +++ b/addons/event/models/event_event.py @@ -21,10 +21,10 @@ except ImportError: class EventType(models.Model): _name = 'event.type' - _description = 'Event Category' + _description = 'Event Template' _order = 'sequence, id' - name = fields.Char('Event Category', required=True, translate=True) + name = fields.Char('Event Template', required=True, translate=True) sequence = fields.Integer() # tickets use_ticket = fields.Boolean('Ticketing') @@ -32,6 +32,7 @@ class EventType(models.Model): 'event.type.ticket', 'event_type_id', string='Tickets', compute='_compute_event_type_ticket_ids', readonly=False, store=True) + tag_ids = fields.Many2many('event.tag', string="Tags", copy=True) # registration has_seats_limitation = fields.Boolean('Limited Seats') seats_max = fields.Integer( @@ -91,7 +92,6 @@ class EventType(models.Model): if not template.has_seats_limitation: template.seats_max = 0 - class EventEvent(models.Model): """Event""" _name = 'event.event' @@ -118,11 +118,13 @@ class EventEvent(models.Model): 'res.partner', string='Organizer', tracking=True, default=lambda self: self.env.company.partner_id, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") - event_type_id = fields.Many2one('event.type', string='Category', ondelete='set null') + event_type_id = fields.Many2one('event.type', string='Template', ondelete='set null') color = fields.Integer('Kanban Color Index') event_mail_ids = fields.One2many( 'event.mail', 'event_id', string='Mail Schedule', copy=True, compute='_compute_from_event_type', readonly=False, store=True) + tag_ids = fields.Many2many('event.tag', string="Tags", readonly=False, + copy=True, store=True, compute="_compute_from_event_type") # Kanban fields kanban_state = fields.Selection([('normal', 'In Progress'), ('done', 'Done'), ('blocked', 'Blocked')], default='normal') kanban_state_label = fields.Char( @@ -379,6 +381,9 @@ class EventEvent(models.Model): }) for line in event.event_type_id.event_type_ticket_ids] + if event.event_type_id.tag_ids: + event.tag_ids = event.event_type_id.tag_ids + @api.constrains('seats_max', 'seats_available', 'seats_availability') def _check_seats_limit(self): if any(event.seats_availability == 'limited' and event.seats_max and event.seats_available < 0 for event in self): diff --git a/addons/event/models/event_tag.py b/addons/event/models/event_tag.py new file mode 100644 index 00000000000..fc5f733386a --- /dev/null +++ b/addons/event/models/event_tag.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import _, api, fields, models + + +class EventTagCategory(models.Model): + _name = "event.tag.category" + _description = "Event Tag Category" + _order = "sequence" + + name = fields.Char("Name", required=True, translate=True) + sequence = fields.Integer('Sequence', default=0) + tag_ids = fields.One2many('event.tag', 'category_id', string="Tags") + +class EventTag(models.Model): + _name = "event.tag" + _description = "Event Tag" + _order = "sequence" + + name = fields.Char("Name", required=True, translate=True) + sequence = fields.Integer('Sequence', default=0) + category_id = fields.Many2one("event.tag.category", string="Category", required=True, ondelete='cascade') + + def name_get(self): + return [(tag.id, _("%s: %s" % (tag.category_id.name, tag.name))) for tag in self] diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv index 9d680924f5d..909f21e4b80 100644 --- a/addons/event/security/ir.model.access.csv +++ b/addons/event/security/ir.model.access.csv @@ -19,3 +19,7 @@ access_event_type_mail_event_user,event.type.mail.event.user,model_event_type_ma access_event_type_mail_event_manager,event.type.mail.event.manager,model_event_type_mail,event.group_event_manager,1,1,1,1 access_event_stage_user,event.stage.user,model_event_stage,event.group_event_user,1,1,1,1 access_event_stage_manager,event.stage.manager,model_event_stage,event.group_event_manager,1,1,1,1 +access_event_category,event.tag.category,model_event_tag_category,event.group_event_user,1,0,0,0 +access_event_category_manager,event.tag.category manager,model_event_tag_category,event.group_event_manager,1,1,1,1 +access_event_tag,event.tag,model_event_tag,event.group_event_user,1,0,0,0 +access_event_tag_manager,event.tag manager,model_event_tag,event.group_event_manager,1,1,1,1 diff --git a/addons/event/views/event_menu_views.xml b/addons/event/views/event_menu_views.xml index 3f42322e9b3..ffb58639d61 100644 --- a/addons/event/views/event_menu_views.xml +++ b/addons/event/views/event_menu_views.xml @@ -28,7 +28,7 @@ sequence="99" parent="event_main_menu" groups="event.group_event_user"/> - @@ -37,5 +37,9 @@ sequence="10" parent="menu_event_configuration" groups="base.group_no_one"/> + diff --git a/addons/event/views/event_tag_views.xml b/addons/event/views/event_tag_views.xml new file mode 100644 index 00000000000..6d2ae530bb9 --- /dev/null +++ b/addons/event/views/event_tag_views.xml @@ -0,0 +1,79 @@ + + + + + + event.tag.category.view.tree + event.tag.category + + + + + + + + + + + event.tag.category.view.form + event.tag.category + +
+ +
+

+
+ + + + + + + + +
+
+
+
+ + + Event Tags Categories + event.tag.category + tree,form + + + + + event.tag.view.tree + event.tag + + + + + + + + + + + event.tag.view.form + event.tag + +
+ +
+
+ +
+
+
+
+ + + + + +
+
diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml index 43f778f9a5e..7a9842507f2 100644 --- a/addons/event/views/event_views.xml +++ b/addons/event/views/event_views.xml @@ -86,6 +86,10 @@ +
+

Attendees

@@ -131,7 +135,7 @@ event.type.tree event.type - + @@ -142,7 +146,7 @@ event.type.search event.type - + @@ -150,7 +154,7 @@ - Event Categories + Event Templates event.type @@ -227,6 +231,7 @@ + @@ -446,7 +451,7 @@ domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/> - + diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index 570074d7b4b..8691222101d 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -5,6 +5,8 @@ import re import werkzeug from werkzeug.datastructures import OrderedMultiDict +from ast import literal_eval +from collections import defaultdict from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta @@ -12,6 +14,7 @@ from odoo import fields, http, _ from odoo.addons.http_routing.models.ir_http import slug from odoo.addons.website.controllers.main import QueryURL from odoo.http import request +from odoo.osv import expression from odoo.tools.misc import get_lang, format_date @@ -28,6 +31,7 @@ class WebsiteEventController(http.Controller): searches.setdefault('search', '') searches.setdefault('date', 'all') + searches.setdefault('tags', '') searches.setdefault('type', 'all') searches.setdefault('country', 'all') @@ -70,6 +74,19 @@ class WebsiteEventController(http.Controller): if searches['search']: domain_search['search'] = [('name', 'ilike', searches['search'])] + search_tags = self._extract_searched_event_tags(searches) + if search_tags: + # Example: You filter on age: 10-12 and activity: football. + # Doing it this way allows to only get events who are tagged "age: 10-12" AND "activity: football". + # Add another tag "age: 12-15" to the search and it would fetch the ones who are tagged: + # ("age: 10-12" OR "age: 12-15") AND "activity: football + grouped_tags = defaultdict(list) + for tag in search_tags: + grouped_tags[tag.category_id].append(tag) + domain_search['tags'] = [] + for group in grouped_tags: + domain_search['tags'] = expression.AND([domain_search['tags'], [('tag_ids', 'in', [tag.id for tag in grouped_tags[group]])]]) + current_date = None current_type = None current_country = None @@ -102,11 +119,6 @@ class WebsiteEventController(http.Controller): date[3] = Event.search_count(dom_without('date') + date[2]) domain = dom_without('type') - types = Event.read_group(domain, ["id", "event_type_id"], groupby=["event_type_id"], orderby="event_type_id") - types.insert(0, { - 'event_type_id_count': sum([int(type['event_type_id_count']) for type in types]), - 'event_type_id': ("all", _("All Categories")) - }) domain = dom_without('country') countries = Event.read_group(domain, ["id", "country_id"], groupby="country_id", orderby="country_id") @@ -141,10 +153,11 @@ class WebsiteEventController(http.Controller): 'current_type': current_type, 'event_ids': events, # event_ids used in website_event_track so we keep name as it is 'dates': dates, - 'types': types, + 'categories': request.env['event.tag.category'].search([]), 'countries': countries, 'pager': pager, 'searches': searches, + 'search_tags': search_tags, 'keep': keep, } @@ -342,3 +355,15 @@ class WebsiteEventController(http.Controller): 'google_url': urls.get('google_url'), 'iCal_url': urls.get('iCal_url') }) + + def _extract_searched_event_tags(self, searches): + tags = request.env['event.tag'] + if searches.get('tags'): + try: + tag_ids = literal_eval(searches['tags']) + except: + pass + else: + # perform a search to filter on existing / valid tags implicitely + tags = request.env['event.tag'].search([('id', 'in', tag_ids)]) + return tags diff --git a/addons/website_event/security/ir.model.access.csv b/addons/website_event/security/ir.model.access.csv index 1024bdc8213..e55cfc58bbf 100644 --- a/addons/website_event/security/ir.model.access.csv +++ b/addons/website_event/security/ir.model.access.csv @@ -4,3 +4,5 @@ access_event_event_portal,event.event.portal,event.model_event_event,base.group_ access_event_type_public,event.type.public,event.model_event_type,,1,0,0,0 access_event_event_ticket_public,event.event.ticket.public,event.model_event_event_ticket,base.group_public,1,0,0,0 access_event_event_ticket_portal,event.event.ticket.portal,event.model_event_event_ticket,base.group_portal,1,0,0,0 +access_event_category_public,event.tag.category.public,event.model_event_tag_category,,1,0,0,0 +access_event_tag_public,event.tag.public,event.model_event_tag,,1,0,0,0 diff --git a/addons/website_event/static/src/scss/website_event.scss b/addons/website_event/static/src/scss/website_event.scss index 4be4eeacba2..127874736f7 100644 --- a/addons/website_event/static/src/scss/website_event.scss +++ b/addons/website_event/static/src/scss/website_event.scss @@ -1,11 +1,3 @@ -.o_wevent_event_searchbar_form { - flex: 0 0 100%; - - @include media-breakpoint-up(lg) { - flex: 0 0 33%; - } -} - .o_wevent_index_topbar_filters { .dropdown-toggle { border: $border-width solid gray('400'); diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml index 3bd32369a92..35592fed462 100644 --- a/addons/website_event/views/event_templates.xml +++ b/addons/website_event/views/event_templates.xml @@ -15,6 +15,9 @@
+
+ +
@@ -42,20 +45,40 @@ + + +<<<<<<< HEAD