[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:
committed by
Thibault Delavallée
parent
367136091f
commit
465f4f60fc
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user