add portal_event_registration

This commit is contained in:
Luis 2026-05-04 11:47:25 +02:00
parent 18fd73ed1b
commit de0520d5a8
9 changed files with 393 additions and 0 deletions

View file

@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import controllers, models

View file

@ -0,0 +1,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Portal Event Registration",
"summary": "Allows portal users to view their event registrations "
"and upload attachments to the chatter.",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Custom",
"depends": ["event", "portal"],
"data": [
"security/security.xml",
"security/ir.model.access.csv",
"security/ir.model.access.csv",
"views/portal_templates.xml",
],
"installable": True,
}

View file

@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import portal

View file

@ -0,0 +1,117 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, http
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.addons.portal.controllers.portal import pager as portal_pager
class EventRegistrationPortal(CustomerPortal):
def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
if "event_count" in counters:
Registration = request.env["event.registration"]
partner = request.env.user.partner_id
domain = self._get_event_registration_domain(partner)
values["event_count"] = (
Registration.search_count(domain)
if Registration.has_access("read")
else 0
)
return values
def _get_event_registration_domain(self, partner):
return [
("partner_id", "child_of", [partner.commercial_partner_id.id]),
("state", "!=", "cancel"),
]
def _get_event_searchbar_sortings(self):
return {
"date": {"label": _("Event Date"), "order": "event_begin_date desc"},
"name": {"label": _("Event Name"), "order": "event_id asc"},
"state": {"label": _("Status"), "order": "state asc"},
}
@http.route(
["/my/events", "/my/events/page/<int:page>"],
type="http",
auth="user",
website=True,
)
def portal_my_events(self, page=1, sortby=None, **kw):
partner = request.env.user.partner_id
Registration = request.env["event.registration"]
domain = self._get_event_registration_domain(partner)
searchbar_sortings = self._get_event_searchbar_sortings()
if not sortby:
sortby = "date"
order = searchbar_sortings[sortby]["order"]
total = (
Registration.search_count(domain) if Registration.has_access("read") else 0
)
pager = portal_pager(
url="/my/events",
url_args={"sortby": sortby},
total=total,
page=page,
step=self._items_per_page,
)
registrations = (
Registration.search(
domain,
order=order,
limit=self._items_per_page,
offset=pager["offset"],
)
if Registration.has_access("read")
else Registration
)
request.session["my_events_history"] = registrations.ids[:100]
values = self._prepare_portal_layout_values()
values.update(
{
"registrations": registrations,
"page_name": "event",
"pager": pager,
"sortby": sortby,
"searchbar_sortings": searchbar_sortings,
"default_url": "/my/events",
}
)
return request.render("portal_event_registration.portal_my_events", values)
@http.route(
["/my/events/<int:registration_id>"],
type="http",
auth="user",
website=True,
)
def portal_my_event_detail(self, registration_id, access_token=None, **kw):
try:
registration_sudo = self._document_check_access(
"event.registration", registration_id, access_token=access_token
)
except (AccessError, MissingError):
return request.redirect("/my")
values = self._prepare_portal_layout_values()
values["registration"] = registration_sudo
values = self._get_page_view_values(
registration_sudo,
access_token,
values,
"my_events_history",
False,
**kw,
)
return request.render(
"portal_event_registration.portal_my_event_detail", values
)

View file

@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import event_registration

View file

@ -0,0 +1,11 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class EventRegistration(models.Model):
_inherit = "event.registration"
# Allow portal users (who only have read access) to post messages
# and upload attachments via the portal chatter.
_mail_post_access = "read"

View file

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_event_portal,event.event.portal,event.model_event_event,base.group_portal,1,0,0,0
access_event_registration_portal,event.registration.portal,event.model_event_registration,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_event_event_portal event.event.portal event.model_event_event base.group_portal 1 0 0 0
3 access_event_registration_portal event.registration.portal event.model_event_registration base.group_portal 1 0 0 0

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Allow portal users to read events where they have a registration -->
<record id="event_registration_portal_rule" model="ir.rule">
<field name="name">Event Registration: portal users see own registrations</field>
<field name="model_id" ref="event.model_event_registration"/>
<field name="domain_force">
[('partner_id', 'child_of', [user.commercial_partner_id.id])]
</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- Allow portal users to read events where they are registered -->
<record id="event_event_portal_rule" model="ir.rule">
<field name="name">Event Event: portal users see events with own registrations</field>
<field name="model_id" ref="event.model_event_event"/>
<field name="domain_force">
[('registration_ids.partner_id', 'child_of', [user.commercial_partner_id.id])]
</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</odoo>

View file

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ===== Breadcrumbs ===== -->
<template
id="portal_my_home_menu_event"
name="Portal layout: event menu entries"
inherit_id="portal.portal_breadcrumbs"
priority="25"
>
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li
t-if="page_name == 'event' or registration"
t-attf-class="breadcrumb-item #{'active' if not registration else ''}"
>
<a t-if="registration" t-attf-href="/my/events?{{ keep_query() }}">My Events</a>
<t t-else="">My Events</t>
</li>
<li t-if="registration" class="breadcrumb-item active">
<t t-out="registration.event_id.name"/>
</li>
</xpath>
</template>
<!-- ===== Portal Home entry ===== -->
<template
id="portal_my_home_event"
name="My Events"
customize_show="True"
inherit_id="portal.portal_my_home"
priority="25"
>
<xpath expr="//div[hasclass('o_portal_docs')]" position="before">
<t t-set="portal_client_category_enable" t-value="True"/>
</xpath>
<div id="portal_client_category" position="inside">
<t t-call="portal.portal_docs_entry">
<t t-set="title">My Events</t>
<t t-set="url" t-value="'/my/events'"/>
<t t-set="text">View the events you are registered for</t>
<t t-set="placeholder_count" t-value="'event_count'"/>
</t>
</div>
</template>
<!-- ===== List: /my/events ===== -->
<template id="portal_my_events" name="My Event Registrations">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-call="portal.portal_searchbar">
<t t-set="title">My Events</t>
</t>
<div t-if="not registrations" class="alert alert-warning" role="alert">
You have no active registrations for any event.
</div>
<t t-if="registrations" t-call="portal.portal_table">
<thead>
<tr class="active">
<th>Event</th>
<th class="text-end">Start Date</th>
<th class="text-end">End Date</th>
<th class="text-center">Status</th>
</tr>
</thead>
<t t-foreach="registrations" t-as="reg">
<tr>
<td>
<a t-attf-href="/my/events/#{reg.id}">
<t t-out="reg.event_id.name"/>
</a>
</td>
<td class="text-end">
<span t-field="reg.event_begin_date" t-options="{'widget': 'date'}"/>
</td>
<td class="text-end">
<span t-field="reg.event_end_date" t-options="{'widget': 'date'}"/>
</td>
<td class="text-center">
<span
t-if="reg.state == 'open'"
class="badge rounded-pill text-bg-success"
><i class="fa fa-fw fa-check"/> Registered</span>
<span
t-elif="reg.state == 'done'"
class="badge rounded-pill text-bg-primary"
><i class="fa fa-fw fa-star"/> Attended</span>
<span
t-elif="reg.state == 'draft'"
class="badge rounded-pill text-bg-warning"
><i class="fa fa-fw fa-clock-o"/> Pending</span>
</td>
</tr>
</t>
</t>
</t>
</template>
<!-- ===== Detail: /my/events/<id> ===== -->
<template id="portal_my_event_detail" name="Event Registration Detail">
<t t-call="portal.portal_layout">
<div class="row mt16 o_portal_sidebar">
<!-- Sidebar -->
<t t-call="portal.portal_record_sidebar">
<t t-set="classes" t-value="'col-12 col-lg-3 d-print-none'"/>
<t t-set="entries">
<ul class="list-group list-group-flush flex-wrap flex-row flex-lg-column">
<li class="list-group-item flex-grow-1">
<div class="small mb-1 fw-bold text-uppercase">Status</div>
<span
t-if="registration.state == 'open'"
class="badge rounded-pill text-bg-success"
><i class="fa fa-check"/> Registered</span>
<span
t-elif="registration.state == 'done'"
class="badge rounded-pill text-bg-primary"
><i class="fa fa-star"/> Attended</span>
<span
t-elif="registration.state == 'draft'"
class="badge rounded-pill text-bg-warning"
><i class="fa fa-clock-o"/> Pending</span>
<span
t-elif="registration.state == 'cancel'"
class="badge rounded-pill text-bg-danger"
><i class="fa fa-times"/> Cancelled</span>
</li>
<li t-if="registration.event_id.organizer_id" class="list-group-item flex-grow-1">
<div class="small mb-1 fw-bold text-uppercase">Organizer</div>
<t t-out="registration.event_id.organizer_id.name"/>
</li>
</ul>
</t>
</t>
<!-- Main content -->
<div id="portal_event_content" class="col-12 col-lg-9">
<div class="o_portal_html_view shadow p-3">
<div class="o_portal_html_loader text-center">
<i class="fa fa-circle-o-notch fa-spin fa-2x fa-fw text-black-50"/>
</div>
<!-- Event header -->
<div class="pb-3 border-bottom">
<h2>
<t t-out="registration.event_id.name"/>
</h2>
<div class="row">
<div class="col-12 col-md-6">
<strong>Start:</strong>
<span t-field="registration.event_begin_date"/>
</div>
<div class="col-12 col-md-6">
<strong>End:</strong>
<span t-field="registration.event_end_date"/>
</div>
</div>
<t t-if="registration.event_id.address_id">
<div class="mt-2">
<strong>Location:</strong>
<span t-field="registration.event_id.address_id"
t-options="{'widget': 'contact', 'fields': ['address']}"/>
</div>
</t>
</div>
<!-- Attendee details -->
<div class="mt-3">
<h4>Registration Details</h4>
<table class="table table-sm">
<tbody>
<tr>
<th>Name</th>
<td><t t-out="registration.name"/></td>
</tr>
<tr t-if="registration.email">
<th>Email</th>
<td><t t-out="registration.email"/></td>
</tr>
<tr t-if="registration.phone">
<th>Phone</th>
<td><t t-out="registration.phone"/></td>
</tr>
<tr t-if="registration.event_ticket_id">
<th>Ticket Type</th>
<td><t t-out="registration.event_ticket_id.name"/></td>
</tr>
</tbody>
</table>
</div>
<!-- Chatter with attachments -->
<div class="mt-4">
<h4>Messages &amp; Documents</h4>
<t t-call="portal.message_thread"/>
</div>
</div>
</div>
</div>
</t>
</template>
</odoo>