[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
This commit is contained in:
qmo-odoo
2021-08-23 14:59:00 +00:00
committed by Thibault Delavallée
parent 367136091f
commit 465f4f60fc
5 changed files with 335 additions and 42 deletions
+25 -11
View File
@@ -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)
@@ -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;
},
});
});
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="website_event.event_create_dialog">
<form id="editor_new_event" style="overflow:visible;">
<div class="form-group row" id="name">
<label for="name" class="col-md-2 col-form-label">Event Name</label>
<div class="col-md-10">
<input type="text" name="name" class="form-control" required="required"/>
</div>
<p class="text-danger mt-1 mb-0 col-md-8 d-none">Please fill in this field</p>
</div>
<div class="form-group row">
<label class="col-form-label col-md-2" for="event_location">Location</label>
<div class="col-md-10">
<select class="form-control" id="event_location" name="event_location">
<option value="online">Online</option>
<option value="on_site">On Site</option>
</select>
</div>
</div>
<div class="form-group row d-none show_visibility_address" id="address">
<label class="col-form-label col-md-2" for="address_id">Venue</label>
<div class="col-md-10">
<input type="text" class="form-control o_wevent_js_address_id" name="address_id" placeholder="Select Venue"/>
</div>
<p class="text-danger mt-1 mb-0 col-md-8 d-none">Please fill in this field</p>
</div>
<div class="form-group row d-flex justify-content-between align-items-center" id="event_dates">
<label class="col-form-label col-md-2" for="event_start_end">Start 🠖 End</label>
<div class="col-md-10 s_website_form_datetime input-group date o_wevent_form_datetime" data-target-input="nearest">
<input type="text" name="event_start_end" class="form-control daterange-input bg-transparent text-dark rounded-0 p-1"/>
</div>
<p class="text-danger mt-1 col-md-8 d-none">Please fill in this field</p>
</div>
</form>
</t>
</templates>
@@ -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 <em>\"Continue\"</em>. 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 <em>Continue</em> to create the event.")),
position: "right",
+12 -9
View File
@@ -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