From 465f4f60fc6169a9598b5cb615b50a5a14bbe40f Mon Sep 17 00:00:00 2001 From: qmo-odoo Date: Tue, 13 Apr 2021 16:27:51 +0000 Subject: [PATCH] [IMP] website_event: improve frontend event creation This commit improves the event creation flow in frontend. Before, you could only set the name of an event and remaining values had to be set either via backend or using editor for some fields. Now you can set the name, start / end date and the address if needed. Task-2488019 PR odoo/odoo#68901 --- addons/website_event/controllers/main.py | 36 ++- .../static/src/js/website_event.editor.js | 273 ++++++++++++++++-- .../static/src/xml/event_create.xml | 37 +++ .../static/tests/tours/website_event.js | 10 +- addons/website_event_sale/controllers/main.py | 21 +- 5 files changed, 335 insertions(+), 42 deletions(-) create mode 100644 addons/website_event/static/src/xml/event_create.xml diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py index 3c1563c84e2..573a5a5e3aa 100644 --- a/addons/website_event/controllers/main.py +++ b/addons/website_event/controllers/main.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- import babel.dates +import pytz import re import werkzeug from ast import literal_eval from collections import defaultdict from datetime import datetime, timedelta +from dateutil.parser import parse from dateutil.relativedelta import relativedelta from werkzeug.datastructures import OrderedMultiDict from werkzeug.exceptions import NotFound @@ -380,22 +382,34 @@ class WebsiteEventController(http.Controller): # ------------------------------------------------------------ @http.route('/event/add_event', type='json', auth="user", methods=['POST'], website=True) - def add_event(self, event_name="New Event", **kwargs): - event = self._add_event(event_name, request.context) + def add_event(self, name, event_start, event_end, address_values, **kwargs): + values = self._prepare_event_values(name, event_start, event_end, address_values) + event = request.env['event.event'].create(values) return "/event/%s/register?enable_editor=1" % slug(event) - def _add_event(self, event_name=None, context=None, **kwargs): - if not event_name: - event_name = _("New Event") - date_begin = datetime.today() + timedelta(days=(14)) - vals = { - 'name': event_name, - 'date_begin': fields.Date.to_string(date_begin), - 'date_end': fields.Date.to_string((date_begin + timedelta(days=(1)))), + def _prepare_event_values(self, name, event_start, event_end, address_values=None): + """ + Return the values to create a new event. + event_start,event_date are datetimes in the user tz. + address_values is used to either choose an existing location or create one as we allow it in the frontend. + """ + date_begin = parse(event_start).astimezone(pytz.utc).replace(tzinfo=None) + date_end = parse(event_end).astimezone(pytz.utc).replace(tzinfo=None) + address_id = request.env['res.partner'] + if address_values: + (address_pid, address_vals) = int(address_values[0]), address_values[1] + address_id = address_pid + if address_pid == 0: + address_id = request.env['res.partner'].create(address_vals).id + return { + 'name': name, + 'date_begin': date_begin, + 'date_end': date_end, + 'address_id': address_id, 'seats_available': 1000, 'website_id': request.website.id, + 'event_ticket_ids': request.env['event.event.ticket'], } - return request.env['event.event'].with_context(context or {}).create(vals) # ------------------------------------------------------------ # TOOLS (JSON) diff --git a/addons/website_event/static/src/js/website_event.editor.js b/addons/website_event/static/src/js/website_event.editor.js index 48f3a995d08..4516e6ba43b 100644 --- a/addons/website_event/static/src/js/website_event.editor.js +++ b/addons/website_event/static/src/js/website_event.editor.js @@ -2,11 +2,257 @@ odoo.define('website_event.editor', function (require) { "use strict"; var core = require('web.core'); -var wUtils = require('website.utils'); +var Dialog = require('web.Dialog'); +var time = require('web.time'); var WebsiteNewMenu = require('website.newMenu'); - var _t = core._t; +var EventCreateDialog = Dialog.extend({ + xmlDependencies: Dialog.prototype.xmlDependencies.concat(['/website_event/static/src/xml/event_create.xml']), + template: 'website_event.event_create_dialog', + events: _.extend({}, Dialog.prototype.events, { + 'change select[name="event_location"]': '_onLocationChanged', + 'click .input-group-append': '_onDatetimeClicked', + }), + cssLibs: [ + '/web/static/lib/daterangepicker/daterangepicker.css', + ], + jsLibs: [ + '/web/static/lib/daterangepicker/daterangepicker.js', + '/web/static/src/legacy/js/libs/daterangepicker.js', + ], + + + /** + * @override + * @param {Object} parent + * @param {Object} options + */ + init: function (parent, options) { + options = _.defaults(options || {}, { + title: _t("New Event"), + size: 'medium', + buttons: [ + { + text: _t("Create"), + classes: 'btn-primary', + click: this._onClickCreate.bind(this), + }, + { + text: _t("Discard"), + close: true + }, + ] + }); + this.eventStart = moment(); + this.eventEnd = moment().add(1, "d"); + this._super(parent, options); + }, + + /** + * @override + */ + start: function () { + var self = this; + return this._super.apply(this, arguments).then(() => { + // stat / end datetimes widgets + self.$('input.daterange-input').each(function () { + self._initDateRangePicker($(this)); + }); + // address select2 configuration + self.$('input.o_wevent_js_address_id').select2(self._getSelect2AddressConfig()); + }); + }, + + /** + * @param {*} errors + */ + _display_errors: function (errors) { + this.$("p.text-danger").toggleClass('d-none', true); + for (let i = 0; i < errors.length; i++) { + this.$("#" + errors[i] + " p.text-danger").toggleClass('d-none', false); + } + }, + + _getSelect2AddressConfig: function () { + var self = this; + return { + allowClear: true, + formatNoMatches: false, + multiple: false, + selection_data: false, + width: '100%', + + createSearchChoice: function (name, data) { + var addedPartner = $(this.opts.element).select2('data'); + if (_.filter(_.union(addedPartner, data), function (partner) { + return partner.text.toLowerCase().localeCompare(name.toLowerCase()) === 0; + }).length === 0) { + return { + create: true, + id: _.uniqueId('address_'), + name: name, + text: _.str.sprintf(_t('Create "%s"'), name), + }; + } + }, + fill_data: function (query, data) { + var that = this; + var partners = {results: []}; + _.each(data, function (partner) { + var contact_address = partner.contact_address.replace(/[\n]+/g, '').trim(); + if (contact_address && that.matcher(query.term, contact_address)) { + partners.results.push({ + id: partner.id, + text: contact_address, + }); + } + }); + query.callback(partners); + }, + formatSelection: function (data) { + if (data.name) { + data.text = data.name; + } + return data.text; + }, + query: function (query) { + var that = this; + // fetch data only once and store it + if (!this.selection_data) { + self._rpc({ + model: 'res.partner', + method: 'search_read', + fields: ['contact_address'], + domain: [], + }).then(function (data) { + that.fill_data(query, data); + that.selection_data = data; + }); + } else { + this.fill_data(query, this.selection_data); + } + } + } + }, + + _getSelect2AddressValues: function () { + var select2Value = this.$('input.o_wevent_js_address_id').select2('data'); + if (select2Value) { + if (select2Value.create) { + return [0, {'name': select2Value.name}]; + } else { + return [select2Value.id, {}]; + } + } + return []; + }, + + /** + * + * @param {*} $dateGroup + */ + _initDateRangePicker: function ($dateGroup) { + var minDate = moment().subtract(1, "d"); + var maxDate = moment().add(200, "y"); + var self = this; + var textDirection = _t.database.parameters.direction; + + $dateGroup.daterangepicker({ + // dates + endDate: this.eventEnd, + maxDate: maxDate, + minDate: minDate, + startDate: this.eventStart, + // display + locale: { + direction: textDirection, + format: time.getLangDatetimeFormat().replace(':ss', ''), + separator: ' 🠖 ', + applyLabel: _t('Apply'), + cancelLabel: _t('Cancel'), + weekLabel: 'W', + customRangeLabel: _t('Custom Range'), + daysOfWeek: moment.weekdaysMin(), + monthNames: moment.monthsShort(), + firstDay: moment.localeData().firstDayOfWeek() + }, + opens: 'left', + timePicker: true, + timePicker24Hour: true, + viewDate: moment(new Date()).hours(minDate.hours()).minutes(minDate.minutes()).seconds(minDate.seconds()).milliseconds(minDate.milliseconds()), + }, function (start, end, label) { + self.eventStart = start; + self.eventEnd = end; + }); + }, + /** + * @private + */ + _prepareFormValues: function () { + return { + address_values: this._getSelect2AddressValues(), + event_start: this.eventStart, + event_end: this.eventEnd, + location: this.$('select[name=event_location]').val(), + name: this.$('input[name=name]').val().trim(), + }; + }, + /** + * @private + * @param {*} values + */ + _validateForm: function (values) { + var errors = []; + if (!values.name){ + errors.push('name'); + } + if (values.location === 'on_site' && !values.address_values.length) { + errors.push('address'); + } + if (!values.event_start || !values.event_end) { + errors.push('event_dates'); + } + return errors; + }, + + /** + * @private + */ + _submitForm: function () { + var self = this; + var eventValues = this._prepareFormValues(); + var errors = this._validateForm(eventValues) + console.log(eventValues, errors); + if (errors.length) { + this._display_errors(errors); + return; + } + return this._rpc({ + route: '/event/add_event', + params: eventValues, + }).then((url) => { + window.location.href = url; + return new Promise(function () {}); + }); + }, + + /** + * @private + */ + _onClickCreate: function () { + return this._submitForm(); + }, + + /** + * @private + * @param {*} ev + */ + _onLocationChanged: function (ev) { + this.$('.show_visibility_address').toggleClass('d-none', ev.target.value === 'online'); + }, +}); + WebsiteNewMenu.include({ actions: _.extend({}, WebsiteNewMenu.prototype.actions || {}, { new_event: '_createNewEvent', @@ -25,25 +271,12 @@ WebsiteNewMenu.include({ */ _createNewEvent: function () { var self = this; - return wUtils.prompt({ - id: "editor_new_event", - window_title: _t("New Event"), - input: _t("Event Name"), - }).then(function (result) { - var eventName = result.val; - if (!eventName) { - return; - } - return self._rpc({ - route: '/event/add_event', - params: { - event_name: eventName, - }, - }).then(function (url) { - window.location.href = url; - return new Promise(function () {}); - }); + var def = new Promise(function (resolve) { + var dialog = new EventCreateDialog(self, {}); + dialog.open(); + dialog.on('closed', self, resolve); }); + return def; }, }); }); diff --git a/addons/website_event/static/src/xml/event_create.xml b/addons/website_event/static/src/xml/event_create.xml new file mode 100644 index 00000000000..52cc5dc3339 --- /dev/null +++ b/addons/website_event/static/src/xml/event_create.xml @@ -0,0 +1,37 @@ + + + +
+
+ +
+ +
+

Please fill in this field

+
+
+ +
+ +
+
+
+ +
+ +
+

Please fill in this field

+
+
+ +
+ +
+

Please fill in this field

+
+
+
+
diff --git a/addons/website_event/static/tests/tours/website_event.js b/addons/website_event/static/tests/tours/website_event.js index 0a3a038705d..56cc9937ee8 100644 --- a/addons/website_event/static/tests/tours/website_event.js +++ b/addons/website_event/static/tests/tours/website_event.js @@ -4,6 +4,7 @@ odoo.define("website_event.tour", function (require) { const {_t} = require("web.core"); const {Markup} = require('web.utils'); var tour = require("web_tour.tour"); + var time = require('web.time'); tour.register("website_event_tour", { test: true, @@ -17,11 +18,16 @@ odoo.define("website_event.tour", function (require) { content: _t("Click here to create a new event."), position: "bottom", }, { - trigger: '.modal-dialog #editor_new_event input[type=text]', + trigger: '.modal-dialog #editor_new_event input[name=name]', content: Markup(_t("Create a name for your new event and click \"Continue\". e.g: Technical Training")), + run: 'text Technical Training', position: "left", }, { - trigger: '.modal-footer button.btn-primary.btn-continue', + trigger: '.modal-dialog #editor_new_event input[name=event_start_end]', + content: _t("Pick a Start date for your event"), + run: 'text ' + moment().format(time.getLangDatetimeFormat()) + ' - ' + moment().add(1, "d").format(time.getLangDatetimeFormat()), + }, { + trigger: '.modal-footer button.btn-primary', extra_trigger: '#editor_new_event input[type=text][value!=""]', content: Markup(_t("Click Continue to create the event.")), position: "right", diff --git a/addons/website_event_sale/controllers/main.py b/addons/website_event_sale/controllers/main.py index 1c1ad8e5532..088873a74bb 100644 --- a/addons/website_event_sale/controllers/main.py +++ b/addons/website_event_sale/controllers/main.py @@ -55,14 +55,17 @@ class WebsiteEventSaleController(WebsiteEventController): return res - def _add_event(self, event_name="New Event", context=None, **kwargs): + def _prepare_event_values(self, name, event_start, event_end, address_values=None): + values = super(WebsiteEventSaleController, self)._prepare_event_values(name, event_start, event_end, address_values) product = request.env.ref('event_sale.product_product_event', raise_if_not_found=False) if product: - context = dict(context or {}, default_event_ticket_ids=[[0, 0, { - 'name': _('Registration'), - 'product_id': product.id, - 'end_sale_datetime': False, - 'seats_max': 1000, - 'price': 0, - }]]) - return super(WebsiteEventSaleController, self)._add_event(event_name, context, **kwargs) + values.update({ + 'event_ticket_ids': [[0, 0, { + 'name': _('Registration'), + 'product_id': product.id, + 'end_sale_datetime': False, + 'seats_max': 1000, + 'price': 0, + }]] + }) + return values