[MERGE][IMP] event[_sale]: improve event views (frontend/backend) and ticket sale flow

PURPOSE

Improve global event usability by adding a graph view as well as various
changes on the ticket sale flow.

We also improved the track proposal layout, added a handy button to invite
partners to your event and prepared some data for the event_barcode module.

SPECIFICATIONS

- Add a "Invite" button on the event, that allows sending invitations though
  the mass_mailing / mass_sms features
- Add a graph view for the event.event model
- Allow styling the description and the biography when sending a track proposal
  with basic plaintext2html usage
- Improve event and sale views integration in the event_sale module
  (stat buttons and next activity generation)
- General improvements to the "auto confirm" flow of registrations
- Prepare data for the event_barcode module, to make it "event agnostic"

More details in underlying commits

LINKS

Task ID : 2093336
Enterprise PR: odoo/enterprise#7760

closes odoo/odoo#43420

Related: odoo/enterprise#7760
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Odoo's Mergebot
2020-02-04 17:22:05 +01:00
committed by GitHub
21 changed files with 227 additions and 71 deletions
+23 -3
View File
@@ -135,7 +135,7 @@ class EventEvent(models.Model):
string='Maximum Attendees Number',
help="For each event you can define a maximum registration of seats(number of attendees), above this numbers the registrations are not accepted.")
seats_availability = fields.Selection(
[('limited', 'Limited'), ('unlimited', 'Unlimited')],
[('unlimited', 'Unlimited'), ('limited', 'Limited')],
'Maximum Attendees', required=True, default='unlimited')
seats_min = fields.Integer(
string='Minimum Attendees',
@@ -170,12 +170,13 @@ class EventEvent(models.Model):
date_end = fields.Datetime(string='End Date', required=True, tracking=True)
date_begin_located = fields.Char(string='Start Date Located', compute='_compute_date_begin_tz')
date_end_located = fields.Char(string='End Date Located', compute='_compute_date_end_tz')
is_ongoing = fields.Boolean('Is Ongoing', compute='_compute_is_ongoing', search='_search_is_ongoing')
is_one_day = fields.Boolean(compute='_compute_field_is_one_day')
start_sale_date = fields.Date('Start sale date', compute='_compute_start_sale_date')
# Location and communication
is_online = fields.Boolean('Online Event')
address_id = fields.Many2one(
'res.partner', string='Location', tracking=True,
'res.partner', string='Venue', tracking=True,
default=lambda self: self.env.company.partner_id,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
country_id = fields.Many2one('res.country', 'Country', related='address_id.country_id', store=True, readonly=False)
@@ -219,7 +220,7 @@ class EventEvent(models.Model):
@api.depends('date_end', 'seats_available', 'seats_availability', 'event_ticket_ids.sale_available')
def _compute_event_registrations_open(self):
for event in self:
event.event_registrations_open = (event.date_end > fields.Datetime.now()) and \
event.event_registrations_open = event.date_end and (event.date_end > fields.Datetime.now()) and \
(event.seats_available or event.seats_availability == 'unlimited') and \
(not event.event_ticket_ids or any(ticket.sale_available for ticket in event.event_ticket_ids))
@@ -251,6 +252,25 @@ class EventEvent(models.Model):
else:
event.date_end_located = False
@api.depends('date_begin', 'date_end')
def _compute_is_ongoing(self):
now = fields.Datetime.now()
for event in self:
event.is_ongoing = event.date_begin <= now < event.date_end
def _search_is_ongoing(self, operator, value):
if operator not in ['=', '!=']:
raise ValueError(_('This operator is not supported'))
if not isinstance(value, bool):
raise ValueError(_('Value should be True or False (not %s)'), value)
now = fields.Datetime.now()
if (operator == '=' and value) or (operator == '!=' and not value):
domain = [('date_begin', '<=', now), ('date_end', '>', now)]
else:
domain = ['|', ('date_begin', '>', now), ('date_end', '<=', now)]
event_ids = self.env['event.event']._search(domain)
return [('id', 'in', event_ids)]
@api.depends('date_begin', 'date_end', 'date_tz')
def _compute_field_is_one_day(self):
for event in self:
+9 -5
View File
@@ -29,7 +29,7 @@ class EventRegistration(models.Model):
partner_id = fields.Many2one(
'res.partner', string='Contact',
states={'done': [('readonly', True)]})
name = fields.Char(string='Attendee Name', index=True)
name = fields.Char(string='Attendee Name', index=True, required=True, tracking=True)
email = fields.Char(string='Email')
phone = fields.Char(string='Phone')
mobile = fields.Char(string='Mobile')
@@ -133,8 +133,6 @@ class EventRegistration(models.Model):
return ret_list
def _check_auto_confirmation(self):
if self._context.get('registration_force_draft'):
return False
if any(not registration.event_id.auto_confirm or
(not registration.event_id.seats_available and registration.event_id.seats_availability == 'limited') for registration in self):
return False
@@ -249,6 +247,12 @@ class EventRegistration(models.Model):
else:
return _('on ') + format_datetime(self.env, self.event_begin_date, tz=self.event_id.date_tz, dt_format='medium')
def summary(self):
def _get_registration_summary(self):
self.ensure_one()
return {'information': []}
return {
'id': self.id,
'partner_id': self.partner_id.id,
'ticket_name': self.event_ticket_id.name or _('None'),
'name': self.name,
'event_name': self.event_id.name,
}
+7 -4
View File
@@ -21,7 +21,7 @@ class TestEventData(TestEventCommon):
event = self.event_0.with_user(self.env.user)
event.write({
'registration_ids': [(0, 0, {'partner_id': self.customer.id})],
'registration_ids': [(0, 0, {'partner_id': self.customer.id, 'name': 'test_reg'})],
'date_begin': datetime(2020, 1, 31, 15, 0, 0),
'date_end': datetime(2020, 4, 5, 18, 0, 0),
})
@@ -79,15 +79,18 @@ class TestEventData(TestEventCommon):
self.assertTrue(event.auto_confirm)
for x in range(5):
reg = self.env['event.registration'].create({
'event_id': event.id
'event_id': event.id,
'name': 'reg_open',
})
self.assertEqual(reg.state, 'open')
reg_draft = self.env['event.registration'].create({
'event_id': event.id
'event_id': event.id,
'name': 'reg_draft',
})
reg_draft.write({'state': 'draft'})
reg_done = self.env['event.registration'].create({
'event_id': event.id
'event_id': event.id,
'name': 'reg_done',
})
reg_done.write({'state': 'done'})
self.assertEqual(event.seats_available, event.event_type_id.default_registration_max - 6)
+2 -2
View File
@@ -83,8 +83,8 @@ class TestEventSecurity(TestEventCommon):
event._onchange_type()
event.flush()
registration = self.env['event.registration'].create({'event_id': event.id})
registration.write({'name': 'Myself'})
registration = self.env['event.registration'].create({'event_id': event.id, 'name': 'Myself'})
registration.write({'name': 'Myself2'})
stage = self.env.ref('event.event_stage_done')
stage.write({'name': 'ManagerEnded'})
+17 -4
View File
@@ -227,8 +227,8 @@
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="kanban_state" widget="state_selection" class="ml-auto"/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" placeholder="Event Name"/></h1>
<label for="name" class="oe_edit_only" string="Event Name"/>
<h1><field name="name" placeholder="e.g. Conference for Architects"/></h1>
<field name="is_online" groups="base.group_no_one"/>
<label for="is_online" string="Online" groups="base.group_no_one"/>
</div>
@@ -485,11 +485,22 @@
</field>
</record>
<record id="event_event_view_graph" model="ir.ui.view" >
<field name="name">event.event.view.graph</field>
<field name="model">event.event</field>
<field name="arch" type="xml">
<graph string="Events">
<field name="name" type="row"/>
<field name="seats_available" type="measure"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_event_view">
<field name="name">Events</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">event.event</field>
<field name="view_mode">kanban,calendar,tree,form,pivot</field>
<field name="view_mode">kanban,calendar,tree,form,pivot,graph</field>
<field name="search_view_id" ref="view_event_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
@@ -525,7 +536,7 @@
<field name="phone" optional="hide"/>
<field name="mobile" optional="hide"/>
<field name="event_id"/>
<field name="event_ticket_id"/>
<field name="event_ticket_id" domain="[('event_id', '=', event_id)]"/>
<field name="origin" optional="hide"/>
<field name="state" readonly="0"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
@@ -658,6 +669,8 @@
<search string="Event Registration">
<field name="id" string="Registration ID"/>
<field name="name" string="Participant" filter_domain="['|', '|', ('name', 'ilike', self), ('email', 'ilike', self), ('origin', 'ilike', self)]"/>
<filter string="Ongoing Events" name="filter_is_ongoing" domain="[('event_id.is_ongoing', '=', True)]"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Expected" name="expected" domain="[('state', 'in', ['draft', 'open', 'done'])]"/>
<separator/>
+1
View File
@@ -27,6 +27,7 @@ this event.
'views/product_views.xml',
'views/sale_order_views.xml',
'data/event_sale_data.xml',
'data/mail_data.xml',
'report/event_event_templates.xml',
'security/ir.model.access.csv',
'security/event_security.xml',
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="event_ticket_id_change_exception" name="Message: Alert on event ticket id change">
<div>
<p>
<span>Registration modification for attendee:</span>
<a href="#" data-oe-model="event.registration" t-att-data-oe-id="registration.id"><t t-esc="registration.name"/></a>.
<span>Manual actions may be needed.</span>
</p>
<div class="mt16">
<p>Exception:</p>
<ul>
<li>
<a href="#" data-oe-model="event.registration" t-att-data-oe-id="registration.id"><t t-esc="registration.name"/></a>:
<span>Ticket changed from <strong><t t-esc="old_ticket_name"/></strong> to <strong><t t-esc="new_ticket_name"/></strong></span>
</li>
</ul>
</div>
</div>
</template>
</odoo>
+1 -1
View File
@@ -12,5 +12,5 @@ class AccountMove(models.Model):
paid confirm attendees. Attendees should indeed not be confirmed before
full payment. """
res = super(AccountMove, self).action_invoice_paid()
self.mapped('line_ids.sale_line_ids')._update_registrations(confirm=True)
self.mapped('line_ids.sale_line_ids')._update_registrations(confirm=True, mark_as_paid=True)
return res
+39 -16
View File
@@ -8,6 +8,7 @@ from odoo.tools import float_is_zero
class EventRegistration(models.Model):
_inherit = 'event.registration'
is_paid = fields.Boolean('Is Paid')
# in addition to origin generic fields, add real relational fields to correctly
# handle attendees linked to sales orders and their lines
# TDE FIXME: maybe add an onchange on sale_order_id + origin
@@ -17,13 +18,11 @@ class EventRegistration(models.Model):
source_id = fields.Many2one('utm.source', 'Source', related="sale_order_id.source_id", store=True)
medium_id = fields.Many2one('utm.medium', 'Medium', related="sale_order_id.medium_id", store=True)
def _check_auto_confirmation(self):
res = super(EventRegistration, self)._check_auto_confirmation()
if res:
orders = self.env['sale.order'].search([('state', '=', 'draft'), ('id', 'in', self.mapped('sale_order_id').ids)], limit=1)
if orders:
res = False
return res
def action_view_sale_order(self):
action = self.env.ref('sale.action_orders').read()[0]
action['views'] = [(False, 'form')]
action['res_id'] = self.sale_order_id.id
return action
@api.model
def create(self, vals):
@@ -46,6 +45,12 @@ class EventRegistration(models.Model):
self.env['sale.order.line'].browse(vals['sale_order_line_id'])
)
vals.update(so_line_vals)
if vals.get('event_ticket_id'):
self.filtered(
lambda registration: registration.event_ticket_id and registration.event_ticket_id.id != vals['event_ticket_id']
)._sale_order_ticket_type_change_notify(self.env['event.event.ticket'].browse(vals['event_ticket_id']))
return super(EventRegistration, self).write(vals)
def _synchronize_so_line_values(self, so_line):
@@ -60,21 +65,39 @@ class EventRegistration(models.Model):
}
return {}
def summary(self):
res = super(EventRegistration, self).summary()
if self.event_ticket_id.product_id.image_128:
res['image'] = '/web/image/product.product/%s/image_128' % self.event_ticket_id.product_id.id
information = res.setdefault('information', {})
information.append((_('Name'), self.name))
information.append((_('Ticket'), self.event_ticket_id.name or _('None')))
def _sale_order_ticket_type_change_notify(self, new_event_ticket):
fallback_user_id = self.env.user.id if not self.env.user._is_public() else self.env.ref("base.user_admin").id
for registration in self:
render_context = {
'registration': registration,
'old_ticket_name': registration.event_ticket_id.name,
'new_ticket_name': new_event_ticket.name
}
user_id = registration.event_id.user_id.id or \
registration.sale_order_id.user_id.id or \
fallback_user_id
registration.sale_order_id.activity_schedule_with_view('mail.mail_activity_data_warning',
user_id=user_id,
views_or_xmlid='event_sale.event_ticket_id_change_exception',
render_context=render_context)
def _action_set_paid(self):
self.write({'is_paid': True})
def _get_registration_summary(self):
res = super(EventRegistration, self)._get_registration_summary()
order = self.sale_order_id.sudo()
order_line = self.sale_order_line_id.sudo()
has_to_pay = False
if not order or float_is_zero(order_line.price_total, precision_digits=order.currency_id.rounding):
payment_status = _('Free')
elif not order.invoice_ids or any(invoice.payment_state != 'paid' for invoice in order.invoice_ids):
payment_status = _('To pay')
res['alert'] = _('The registration must be paid')
has_to_pay = True
else:
payment_status = _('Paid')
information.append((_('Payment'), payment_status))
res.update({
'payment_status': payment_status,
'has_to_pay': has_to_pay
})
return res
+25 -3
View File
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
from odoo import api, fields, models, _
class SaleOrder(models.Model):
_inherit = "sale.order"
attendee_count = fields.Integer('Attendee Count', compute='_compute_attendee_count')
def write(self, vals):
""" Synchronize partner from SO to registrations. This is done notably
in website_sale controller shop/address that updates customer, but not
@@ -32,6 +34,24 @@ class SaleOrder(models.Model):
.for_xml_id('event_sale', 'action_sale_order_event_registration')
return res
def action_view_attendee_list(self):
action = self.env.ref('event.event_registration_action_tree').read()[0]
action['domain'] = [('sale_order_id', 'in', self.ids)]
return action
def _compute_attendee_count(self):
sale_orders_data = self.env['event.registration'].read_group(
[('sale_order_id', 'in', self.ids)],
['sale_order_id'], ['sale_order_id']
)
attendee_count_data = {
sale_order_data['sale_order_id'][0]:
sale_order_data['sale_order_id_count']
for sale_order_data in sale_orders_data
}
for sale_order in self:
sale_order.attendee_count = attendee_count_data.get(sale_order.id, 0)
class SaleOrderLine(models.Model):
@@ -45,7 +65,7 @@ class SaleOrderLine(models.Model):
help="Choose an event ticket and it will automatically create a registration for this event ticket.")
event_ok = fields.Boolean(related='product_id.event_ok', readonly=True)
def _update_registrations(self, confirm=True, cancel_to_draft=False, registration_data=None):
def _update_registrations(self, confirm=True, cancel_to_draft=False, registration_data=None, mark_as_paid=False):
""" Create or update registrations linked to a sales order line. A sale
order line has a product_uom_qty attribute that will be the number of
registrations linked to this line. This method update existing registrations
@@ -56,6 +76,8 @@ class SaleOrderLine(models.Model):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
if confirm:
existing_registrations.filtered(lambda self: self.state not in ['open', 'cancel']).action_confirm()
if mark_as_paid:
existing_registrations.filtered(lambda self: not self.is_paid)._action_set_paid()
if cancel_to_draft:
existing_registrations.filtered(lambda self: self.state == 'cancel').action_set_draft()
@@ -65,7 +87,7 @@ class SaleOrderLine(models.Model):
registration_vals = registration_data.pop()
# TDE CHECK: auto confirmation
registration_vals['sale_order_line_id'] = so_line.id
Registration.with_context(registration_force_draft=True).create(registration_vals)
Registration.create(registration_vals)
return True
@api.onchange('product_id')
@@ -29,8 +29,17 @@
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_form" />
<field name="arch" type="xml">
<xpath expr="//group" position="before">
<div class="oe_button_box" name="button_box">
<button name="action_view_sale_order" type="object"
class="oe_stat_button" icon="fa-usd" string="Sale Order">
</button>
</div>
<field name="is_paid" invisible="1"/>
<widget name="web_ribbon" title="Paid" bg_color="bg-success" attrs="{'invisible': [('is_paid', '=', False)]}"/>
</xpath>
<group name="event" position="after">
<group string="Origin">
<group string="Origin" groups="base.group_no_one">
<field name="sale_order_id"/>
<field name="origin" attrs="{'invisible': [('sale_order_id', '!=', False)]}"/>
<field name="sale_order_line_id" readonly="1" attrs="{'invisible': [('sale_order_id', '=', False)]}"/>
@@ -5,6 +5,12 @@
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_attendee_list" type="object"
class="oe_stat_button" icon="fa-users" attrs="{'invisible': [('attendee_count', '=', 0)]}">
<field name="attendee_count" widget="statinfo" string="Attendees"/>
</button>
</xpath>
<xpath expr="//field[@name='order_line']//form//field[@name='product_id']" position="after">
<field
name="event_id"
+22 -13
View File
@@ -1,22 +1,31 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo import models, _
class Event(models.Model):
_inherit = "event.event"
def action_mass_mailing_attendees(self):
mass_mailing_action = dict(
name='Mass Mail Attendees',
type='ir.actions.act_window',
res_model='mailing.mailing',
view_mode='form',
target='current',
context=dict(
default_mailing_model_id=self.env.ref('event.model_event_registration').id,
default_mailing_domain=repr([('event_id', 'in', self.ids)])
),
)
return mass_mailing_action
return {
'name': 'Mass Mail Attendees',
'type': 'ir.actions.act_window',
'res_model': 'mailing.mailing',
'view_mode': 'form',
'target': 'current',
'context': {
'default_mailing_model_id': self.env.ref('event.model_event_registration').id,
'default_mailing_domain': repr([('event_id', 'in', self.ids)])
},
}
def action_invite_contacts(self):
return {
'name': 'Mass Mail Invitation',
'type': 'ir.actions.act_window',
'res_model': 'mailing.mailing',
'view_mode': 'form',
'target': 'current',
'context': {'default_mailing_model_id': self.env.ref('base.model_res_partner').id},
}
@@ -3,10 +3,15 @@
<record id="event_event_view_form_inherit_mass_mailing" model="ir.ui.view">
<field name="name">event.event.view.form.inherit.mass.mailing</field>
<field name="model">event.event</field>
<field name="priority" eval="15"/>
<field name="priority" eval="4"/>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='stage_id']" position="before">
<field name="event_registrations_open" invisible="1"/>
<button name="action_invite_contacts" type="object" string="Invite"
class="btn btn-primary" attrs="{'invisible': [('event_registrations_open', '=', False)]}"/>
<button name="action_invite_contacts" type="object" string="Invite"
class="btn btn-secondary" attrs="{'invisible': [('event_registrations_open', '=', True)]}"/>
<button name="action_mass_mailing_attendees" string="Contact Attendees" type="object" attrs="{'invisible': [('seats_expected', '=', 0)]}"/>
</xpath>
</field>
@@ -12,3 +12,9 @@ class Event(models.Model):
action = super(Event, self).action_mass_mailing_attendees()
action['view_id'] = self.env.ref('mass_mailing_sms.mailing_mailing_view_form_mixed').id
return action
def action_invite_contacts(self):
# Minimal override: set form view being the one mixing sms and mail (not prioritized one)
action = super(Event, self).action_invite_contacts()
action['view_id'] = self.env.ref('mass_mailing_sms.mailing_mailing_view_form_mixed').id
return action
@@ -27,6 +27,7 @@ class TestEventWebsite(TestEventWebsiteHelper):
'date_end': fields.Datetime.to_string(datetime.today() + timedelta(days=15)),
'registration_ids': [(0, 0, {
'partner_id': self.user_eventuser.partner_id.id,
'name': 'test_reg',
})],
'website_menu': True,
})
@@ -6,11 +6,12 @@ import babel.dates
import collections
import datetime
import pytz
import base64
from werkzeug.exceptions import NotFound
from odoo import fields, http
from odoo.http import request
from odoo.tools import html_escape as escape, html2plaintext
from odoo.tools import plaintext2html, html2plaintext
class WebsiteEventTrackController(http.Controller):
@@ -93,6 +94,7 @@ class WebsiteEventTrackController(http.Controller):
return request.render("website_event_track.agenda", {
'event': event,
'main_object': event,
'days': days,
'tracks_by_days': tracks_by_days,
'tag': tag
@@ -129,7 +131,7 @@ class WebsiteEventTrackController(http.Controller):
if not event.can_access_from_current_website():
raise NotFound()
return request.render("website_event_track.event_track_proposal", {'event': event})
return request.render("website_event_track.event_track_proposal", {'event': event, 'main_object': event})
@http.route(['''/event/<model("event.event"):event>/track_proposal/post'''], type='http', auth="public", methods=['POST'], website=True)
def event_track_proposal_post(self, event, **post):
@@ -146,11 +148,12 @@ class WebsiteEventTrackController(http.Controller):
'partner_name': post['partner_name'],
'partner_email': post['email_from'],
'partner_phone': post['phone'],
'partner_biography': escape(post['biography']),
'partner_biography': plaintext2html(post['biography']),
'event_id': event.id,
'tag_ids': [(6, 0, tags)],
'user_id': False,
'description': escape(post['description'])
'description': plaintext2html(post['description']),
'image': base64.b64encode(post['image'].read()) if post.get('image') else False
})
if request.env.user != request.website.user_id:
track.sudo().message_subscribe(partner_ids=request.env.user.partner_id.ids)
@@ -66,14 +66,14 @@ class Track(models.Model):
date = fields.Datetime('Track Date')
date_end = fields.Datetime('Track End Date', compute='_compute_end_date', store=True)
duration = fields.Float('Duration', default=1.5, help="Track duration in hours.")
location_id = fields.Many2one('event.track.location', 'Room')
location_id = fields.Many2one('event.track.location', 'Location')
event_id = fields.Many2one('event.event', 'Event', required=True)
color = fields.Integer('Color', related="stage_id.color")
priority = fields.Selection([
('0', 'Low'), ('1', 'Medium'),
('2', 'High'), ('3', 'Highest')],
'Priority', required=True, default='1')
image = fields.Image("Image", related='partner_id.image_128', store=True, readonly=False)
image = fields.Image("Image", max_width=128, max_height=128)
@api.depends('name')
def _compute_website_url(self):
@@ -88,7 +88,6 @@ class Track(models.Model):
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.depends('date', 'duration')
def _compute_end_date(self):
@@ -16,6 +16,7 @@ class TestEventWebsiteTrack(TestEventWebsiteHelper):
'date_end': fields.Datetime.to_string(datetime.today() + timedelta(days=15)),
'registration_ids': [(0, 0, {
'partner_id': self.user_eventuser.partner_id.id,
'name': 'test_reg',
})],
'website_menu': True,
'website_track': True,
@@ -222,7 +222,8 @@
<div t-field="track.partner_biography"/>
<div t-if="track.partner_id" class="row mt32">
<div class="col-md-2">
<span t-field="track.partner_id.sudo().image_128" t-options='{"widget": "image", "class": "rounded-circle"}'/>
<span t-if="track.image" t-field="track.image" t-options="{'widget': 'image', 'class': 'rounded-circle'}"/>
<span t-else="" t-field="track.partner_id.sudo().image_128" t-options='{"widget": "image", "class": "rounded-circle"}'/>
</div>
<div class="col-md-10">
<h4 t-field="track.partner_id.name" class="mb4"/>
@@ -338,7 +339,7 @@
<div class="form-group row">
<label class="col-lg-3 col-md-4 col-form-label" for="description">Talk Introduction</label>
<div class="col-lg-7 col-md-8">
<textarea class="form-control" name="description" style="min-height: 120px"/>
<textarea class="form-control" name="description"/>
</div>
</div>
<div class="form-group row" t-if="len(event.allowed_track_tag_ids)">
@@ -376,6 +377,12 @@
<textarea class="form-control" name="biography" rows="5"/>
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-md-4 col-form-label" for="image">Speaker(s) Picture</label>
<div class="col-lg-7 col-md-8">
<input name="image" type="file" accept="image/*"/>
</div>
</div>
<div class="form-group row o_form_buttons">
<div class="offset-lg-3 offset-md-4 col-md-8 col-lg-7">
<button type="submit" class="btn btn-primary">Submit Proposal</button>
@@ -441,8 +448,8 @@
<li><b>Proposed By</b>: <t t-esc="track.partner_name or track.partner_email"/></li>
<li><b>Mail</b>: <a t-attf-href="mailto:#{track.partner_email}"><t t-esc="track.partner_email"/></a></li>
<li><b>Phone</b>: <t t-esc="track.partner_phone"/></li>
<li><b>Speakers Biography</b>: <t t-esc="track.partner_biography"/></li>
<li><b>Talk Introduction</b>: <t t-esc="track.description"/></li>
<li><b>Speakers Biography</b>: <div t-field="track.partner_biography"/></li>
<li><b>Talk Introduction</b>: <div t-field="track.description"/></li>
</ul>
</template>
@@ -99,7 +99,7 @@
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}"/>
<filter string="Date" name="date" context="{'group_by': 'date'}"/>
<filter string="Event" name="event" context="{'group_by': 'event_id'}"/>
<filter string="Room" name="location" context="{'group_by': 'location_id'}"/>
<filter string="Location" name="location" context="{'group_by': 'location_id'}"/>
</group>
</search>
</field>
@@ -118,8 +118,9 @@
<field name="website_url" invisible="1"/>
<field name="is_published" widget="website_redirect_button"/>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="kanban_state" widget="state_selection"/>
<widget name="web_ribbon" text="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="kanban_state" widget="state_selection" class="ml-3"/>
<field name="image" widget="image" class="oe_avatar"/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
@@ -201,7 +202,7 @@
<p class="o_view_nocontent_smiling_face">
Add a new track
</p><p>
Tracks define the schedule of your event. These can be a talk, a round table, a meeting, etc.
Tracks define the schedule of your event.<br/>These can be a talk, a round table, a meeting, etc.
</p>
</field>
</record>
@@ -215,14 +216,14 @@
<p class="o_view_nocontent_smiling_face">
Add a new track
</p><p>
Tracks define the schedule of your event. These can be a talk, a round table, a meeting, etc.
Tracks define the schedule of your event.<br/>These can be a talk, a round table, a meeting, etc.
</p>
</field>
</record>
<menuitem
id="menu_event_track"
name="Event Tracks"
name="Tracks"
action="action_event_track"
parent="event.event_main_menu"
groups="base.group_no_one"/>
@@ -232,7 +233,7 @@
<field name="name">Event Locations</field>
<field name="model">event.track.location</field>
<field name="arch" type="xml">
<form string="Event Location">
<form string="Event Locations">
<sheet>
<group>
<field name="name"/>