Files
odoo_source/addons/website_event/controllers/main.py
T
qmo-odoo 70d573343e [IMP] website_event: Improve event registration
Purpose:

Improve the registration on front-end by allowing the manager
to customize it and display more informations to users about the tickets
and the status of the event.

Specs:

For event manager:

 Allow users to decide wether or not they want to unfold/fold
 ticket details on their event. The reason behind this is that
 some events might have 5+ different tickets and having them unfolded
 would bloat the UI quite horribly.

 By default, ticket details should be unfolded with no possibility for the end users
 to fold it back.

 Always display a button for the manager to go configure the event in back-end

For users in general:

 Show price range in case the ticket details are folded.

 Clearly show the user that a particular ticket is sold out/expired

 Cleary show the user that registrations are open/closed/sold out

LINKS:

TaskID:2162438
PR: #43856
2020-03-26 14:59:17 +00:00

371 lines
16 KiB
Python

# -*- coding: utf-8 -*-
import babel.dates
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
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
class WebsiteEventController(http.Controller):
def sitemap_event(env, rule, qs):
if not qs or qs.lower() in '/events':
yield {'loc': '/events'}
@http.route(['/event', '/event/page/<int:page>', '/events', '/events/page/<int:page>'], type='http', auth="public", website=True, sitemap=sitemap_event)
def events(self, page=1, **searches):
Event = request.env['event.event']
EventType = request.env['event.type']
searches.setdefault('search', '')
searches.setdefault('date', 'all')
searches.setdefault('tags', '')
searches.setdefault('type', 'all')
searches.setdefault('country', 'all')
website = request.website
today = datetime.today()
def sdn(date):
return fields.Datetime.to_string(date.replace(hour=23, minute=59, second=59))
def sd(date):
return fields.Datetime.to_string(date)
def get_month_filter_domain(filter_name, months_delta):
first_day_of_the_month = today.replace(day=1)
filter_string = _('This month') if months_delta == 0 \
else format_date(request.env, value=today + relativedelta(months=months_delta),
date_format='LLLL', lang_code=get_lang(request.env).code).capitalize()
return [filter_name, filter_string, [
("date_end", ">=", sd(first_day_of_the_month + relativedelta(months=months_delta))),
("date_begin", "<", sd(first_day_of_the_month + relativedelta(months=months_delta+1)))],
0]
dates = [
['all', _('Upcoming Events'), [("date_end", ">", sd(today))], 0],
['today', _('Today'), [
("date_end", ">", sd(today)),
("date_begin", "<", sdn(today))],
0],
get_month_filter_domain('month', 0),
get_month_filter_domain('nextmonth1', 1),
get_month_filter_domain('nextmonth2', 2),
['old', _('Past Events'), [
("date_end", "<", sd(today))],
0],
]
# search domains
domain_search = {'website_specific': website.website_domain()}
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
for date in dates:
if searches["date"] == date[0]:
domain_search["date"] = date[2]
if date[0] != 'all':
current_date = date[1]
if searches["type"] != 'all':
current_type = EventType.browse(int(searches['type']))
domain_search["type"] = [("event_type_id", "=", int(searches["type"]))]
if searches["country"] != 'all' and searches["country"] != 'online':
current_country = request.env['res.country'].browse(int(searches['country']))
domain_search["country"] = ['|', ("country_id", "=", int(searches["country"])), ("country_id", "=", False)]
elif searches["country"] == 'online':
domain_search["country"] = [("country_id", "=", False)]
def dom_without(without):
domain = []
for key, search in domain_search.items():
if key != without:
domain += search
return domain
# count by domains without self search
for date in dates:
if date[0] != 'old':
date[3] = Event.search_count(dom_without('date') + date[2])
domain = dom_without('type')
domain = dom_without('country')
countries = Event.read_group(domain, ["id", "country_id"], groupby="country_id", orderby="country_id")
countries.insert(0, {
'country_id_count': sum([int(country['country_id_count']) for country in countries]),
'country_id': ("all", _("All Countries"))
})
step = 12 # Number of events per page
event_count = Event.search_count(dom_without("none"))
pager = website.pager(
url="/event",
url_args=searches,
total=event_count,
page=page,
step=step,
scope=5)
order = 'date_begin'
if searches.get('date', 'all') == 'old':
order = 'date_begin desc'
if searches["country"] != 'all': # if we are looking for a specific country
order = 'is_online, ' + order # show physical events first
order = 'is_published desc, ' + order
events = Event.search(dom_without("none"), limit=step, offset=pager['offset'], order=order)
keep = QueryURL('/event', **{key: value for key, value in searches.items() if (key == 'search' or value != 'all')})
values = {
'current_date': current_date,
'current_country': current_country,
'current_type': current_type,
'event_ids': events, # event_ids used in website_event_track so we keep name as it is
'dates': dates,
'categories': request.env['event.tag.category'].search([]),
'countries': countries,
'pager': pager,
'searches': searches,
'search_tags': search_tags,
'keep': keep,
}
if searches['date'] == 'old':
# the only way to display this content is to set date=old so it must be canonical
values['canonical_params'] = OrderedMultiDict([('date', 'old')])
return request.render("website_event.index", values)
@http.route(['''/event/<model("event.event"):event>/page/<path:page>'''], type='http', auth="public", website=True, sitemap=False)
def event_page(self, event, page, **post):
if not event.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
values = {
'event': event,
}
if '.' not in page:
page = 'website_event.%s' % page
try:
# Every event page view should have its own SEO.
values['seo_object'] = request.website.get_template(page)
values['main_object'] = event
except ValueError:
# page not found
values['path'] = re.sub(r"^website_event\.", '', page)
values['from_template'] = 'website_event.default_page' # .strip('website_event.')
page = 'website.%s' % (request.website.is_publisher() and 'page_404' or '404')
return request.render(page, values)
@http.route(['''/event/<model("event.event"):event>'''], type='http', auth="public", website=True, sitemap=True)
def event(self, event, **post):
if not event.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
if event.menu_id and event.menu_id.child_id:
target_url = event.menu_id.child_id[0].url
else:
target_url = '/event/%s/register' % str(event.id)
if post.get('enable_editor') == '1':
target_url += '?enable_editor=1'
return request.redirect(target_url)
@http.route(['''/event/<model("event.event"):event>/register'''], type='http', auth="public", website=True, sitemap=False)
def event_register(self, event, **post):
if not event.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
urls = event._get_event_resource_urls()
values = {
'event': event,
'sold_out': all(not ticket.sale_available and not ticket.is_expired for ticket in event.event_ticket_ids),
'main_object': event,
'range': range,
'registrable': event.sudo().event_registrations_open,
'google_url': urls.get('google_url'),
'iCal_url': urls.get('iCal_url'),
}
return request.render("website_event.event_description_full", values)
@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)
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)))),
'seats_available': 1000,
'website_id': request.website.id,
}
return request.env['event.event'].with_context(context or {}).create(vals)
def get_formated_date(self, event):
start_date = fields.Datetime.from_string(event.date_begin).date()
end_date = fields.Datetime.from_string(event.date_end).date()
month = babel.dates.get_month_names('abbreviated', locale=get_lang(event.env).code)[start_date.month]
return ('%s %s%s') % (month, start_date.strftime("%e"), (end_date != start_date and ("-" + end_date.strftime("%e")) or ""))
@http.route('/event/get_country_event_list', type='json', auth='public', website=True)
def get_country_events(self, **post):
Event = request.env['event.event']
country_code = request.session['geoip'].get('country_code')
result = {'events': [], 'country': False}
events = None
domain = request.website.website_domain()
if country_code:
country = request.env['res.country'].search([('code', '=', country_code)], limit=1)
events = Event.search(domain + ['|', ('address_id', '=', None), ('country_id.code', '=', country_code), ('date_begin', '>=', '%s 00:00:00' % fields.Date.today())], order="date_begin")
if not events:
events = Event.search(domain + [('date_begin', '>=', '%s 00:00:00' % fields.Date.today())], order="date_begin")
for event in events:
if country_code and event.country_id.code == country_code:
result['country'] = country
result['events'].append({
"date": self.get_formated_date(event),
"event": event,
"url": event.website_url})
return request.env['ir.ui.view'].render_template("website_event.country_events_list", result)
def _process_tickets_form(self, event, form_details):
""" Process posted data about ticket order. Generic ticket are supported
for event without tickets (generic registration).
:return: list of order per ticket: [{
'id': if of ticket if any (0 if no ticket),
'ticket': browse record of ticket if any (None if no ticket),
'name': ticket name (or generic 'Registration' name if no ticket),
'quantity': number of registrations for that ticket,
}, {...}]
"""
ticket_order = {}
for key, value in form_details.items():
registration_items = key.split('nb_register-')
if len(registration_items) != 2:
continue
ticket_order[int(registration_items[1])] = int(value)
ticket_dict = dict((ticket.id, ticket) for ticket in request.env['event.event.ticket'].search([
('id', 'in', [tid for tid in ticket_order.keys() if tid]),
('event_id', '=', event.id)
]))
return [{
'id': tid if ticket_dict.get(tid) else 0,
'ticket': ticket_dict.get(tid),
'name': ticket_dict[tid]['name'] if ticket_dict.get(tid) else _('Registration'),
'quantity': count,
} for tid, count in ticket_order.items() if count]
@http.route(['/event/<model("event.event"):event>/registration/new'], type='json', auth="public", methods=['POST'], website=True)
def registration_new(self, event, **post):
tickets = self._process_tickets_form(event, post)
if not tickets:
return False
return request.env['ir.ui.view'].render_template("website_event.registration_attendee_details", {'tickets': tickets, 'event': event})
def _process_attendees_form(self, event, form_details):
""" Process data posted from the attendee details form.
:param details: posted data from frontend registration form, like
{'1-name': 'r', '1-email': 'r@r.com', '1-phone': '', '1-event_ticket_id': '1'}
"""
registration_fields = request.env['event.registration']._fields
registrations = {}
global_values = {}
for key, value in form_details.items():
counter, field_name = key.split('-', 1)
if field_name not in registration_fields:
continue
elif isinstance(registration_fields[field_name], (fields.Many2one, fields.Integer)):
value = int(value) or False # 0 is considered as a void many2one aka False
else:
value = value
if counter == '0':
global_values[field_name] = value
else:
registrations.setdefault(counter, dict())[field_name] = value
for key, value in global_values.items():
for registration in registrations.values():
registration[key] = value
return list(registrations.values())
def _create_attendees_from_registration_post(self, event, registration_data):
attendees_sudo = request.env['event.registration'].sudo()
for registration_values in registration_data:
registration_values['event_id'] = event.id
if not registration_values.get('partner_id'):
registration_values['partner_id'] = request.env.user.partner_id.id
attendees_sudo += request.env['event.registration'].sudo().create(registration_values)
return attendees_sudo
@http.route(['''/event/<model("event.event"):event>/registration/confirm'''], type='http', auth="public", methods=['POST'], website=True)
def registration_confirm(self, event, **post):
if not event.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
registrations = self._process_attendees_form(event, post)
attendees = self._create_attendees_from_registration_post(event, registrations)
urls = event._get_event_resource_urls()
return request.render("website_event.registration_complete", {
'attendees': attendees,
'event': event,
'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