[IMP] calendar: Make calendar edit more restricted

This task change the people who can edit a calendar event.
This calendar event currently allow every user to modify every
event in the calendar. This cause issue where one user can
modify the time-off event for other employees.

This PR change the right to modify an event from the calendar
view in the calendar app. With these changes, only the people
attending an event will be able to modify it from the calendar
view.

task-id : 3185743

closes odoo/odoo#112964

Signed-off-by: Arnaud Joset <arj@odoo.com>
This commit is contained in:
Florian Damhaut
2023-05-03 13:56:49 +02:00
committed by Arnaud Joset
parent 463e567438
commit e17aef8896
9 changed files with 72 additions and 33 deletions
+7
View File
@@ -221,6 +221,7 @@ class Meeting(models.Model):
declined_count = fields.Integer(compute='_compute_attendees_count')
tentative_count = fields.Integer(compute='_compute_attendees_count')
awaiting_count = fields.Integer(compute="_compute_attendees_count")
user_can_edit = fields.Boolean(compute='_compute_user_can_edit')
@api.depends('attendee_ids', 'attendee_ids.state', 'partner_ids')
def _compute_attendees_count(self):
@@ -241,6 +242,12 @@ class Meeting(models.Model):
'awaiting_count': attendees_count - accepted_count - declined_count - tentative_count
})
@api.depends('partner_ids')
@api.depends_context('uid')
def _compute_user_can_edit(self):
for event in self:
event.user_can_edit = self.env.user in event.partner_ids.user_ids + event.user_id
@api.depends('attendee_ids')
def _compute_invalid_email_partner_ids(self):
for event in self:
@@ -32,7 +32,7 @@ export class AttendeeCalendarCommonPopover extends CalendarCommonPopover {
}
get isCurrentUserAttendee() {
return this.props.record.rawRecord.partner_ids.includes(this.user.partnerId);
return this.props.record.rawRecord.partner_ids.includes(this.user.partnerId) || this.props.record.rawRecord.partner_id[0] === this.user.partnerId;
}
get isCurrentUserOrganizer() {
@@ -69,9 +69,20 @@ export class AttendeeCalendarCommonPopover extends CalendarCommonPopover {
* @override
*/
get isEventEditable() {
return this.isCurrentUserAttendee;
}
get isEventViewable() {
return this.isEventPrivate ? this.isCurrentUserAttendee : super.isEventEditable;
}
/**
* @override
*/
get hasFooter() {
return this.isEventViewable || super.hasFooter;
}
async changeAttendeeStatus(selectedStatus) {
const record = this.props.record;
if (record.attendeeStatus === selectedStatus) {
@@ -7,6 +7,11 @@
</t>
<t t-name="calendar.AttendeeCalendarCommonPopover.footer" t-inherit="web.CalendarCommonPopover.footer" t-inherit-mode="primary" owl="1">
<xpath expr="//t[@t-if='isEventEditable']" position="after">
<t t-elif="isEventViewable">
<a href="#" class="btn btn-primary o_cw_popover_edit" t-on-click="onEditEvent">View</a>
</t>
</xpath>
<xpath expr="//t[@t-if='isEventDeletable']" position="after">
<a t-if="isEventArchivable and isEventDetailsVisible" href="#" class="btn btn-secondary o_cw_popover_archive_g" t-on-click="onClickArchive">Delete</a>
<div t-if="displayAttendeeAnswerChoice" class="d-inline-block">
@@ -1,18 +1,29 @@
/** @odoo-module **/
import { useService } from "@web/core/utils/hooks";
import { CalendarCommonRenderer } from "@web/views/calendar/calendar_common/calendar_common_renderer";
import { AttendeeCalendarCommonPopover } from "@calendar/views/attendee_calendar/common/attendee_calendar_common_popover";
export class AttendeeCalendarCommonRenderer extends CalendarCommonRenderer {
setup() {
super.setup();
this.user = useService("user");
}
/**
* @override
*
* Give a new key to our fc records to be able to iterate through in templates
*/
convertRecordToEvent(record) {
let editable = false;
if (record && record.rawRecord) {
editable = record.rawRecord.partner_ids.includes(this.user.partnerId)
}
return {
...super.convertRecordToEvent(record),
id: record._recordId || record.id,
editable: editable,
};
}
+3 -2
View File
@@ -214,8 +214,9 @@ class TestCalendar(SavepointCaseWithUserDemo):
self.assertEqual(d.minute, 30)
def test_recurring_ny(self):
self.env.user.tz = 'US/Eastern'
f = Form(self.CalendarEvent.with_context(tz='US/Eastern'))
self.user_demo.tz = 'US/Eastern'
event = self.CalendarEvent.create({'user_id': self.user_demo.id, 'name': 'test', 'partner_ids': [Command.link(self.user_demo.partner_id.id)]})
f = Form(event.with_context(tz='US/Eastern').with_user(self.user_demo))
f.name = 'test'
f.start = '2022-07-07 01:00:00' # This is in UTC. In NY, it corresponds to the 6th of july at 9pm.
f.recurrency = True
@@ -161,13 +161,13 @@ class TestEventNotifications(TransactionCase, MailCase, CronMixinCase):
def test_email_alarm(self):
now = fields.Datetime.now()
with self.capture_triggers('calendar.ir_cron_scheduler_alarm') as capt:
alarm = self.env['calendar.alarm'].create({
alarm = self.env['calendar.alarm'].with_user(self.user).create({
'name': 'Alarm',
'alarm_type': 'email',
'interval': 'minutes',
'duration': 20,
})
self.event.write({
self.event.with_user(self.user).write({
'name': 'test event',
'start': now + relativedelta(minutes=15),
'stop': now + relativedelta(minutes=18),
+28 -27
View File
@@ -114,28 +114,29 @@
<field name="res_id" invisible="1" />
<field name="attendee_status" invisible="1"/>
<field name="active" invisible="1"/>
<field name="user_can_edit" invisible="1"/>
<div class="oe_title mb-3">
<div>
<label for="name"/>
</div>
<h1>
<field name="name" placeholder="e.g. Business Lunch"/>
<field name="name" placeholder="e.g. Business Lunch" attrs="{'readonly': [('user_can_edit', '=', False)]}"/>
</h1>
</div>
<group>
<group>
<field name="start_date" string="from" attrs="{'required': [('allday','=',True)], 'invisible': [('allday','=',False)]}" force_save="1"/>
<field name="stop_date" string="to" attrs="{'required': [('allday','=',True)],'invisible': [('allday','=',False)]}" force_save="1"/>
<field name="start" string="from" attrs="{'required': [('allday','=',False)], 'invisible': [('allday','=',True)]}"/>
<field name="stop" string="to" attrs="{'invisible': [('allday','=',True)]}"/>
<field name="start_date" string="from" attrs="{'required': [('allday','=',True)],'invisible': [('allday','=',False)], 'readonly': [('user_can_edit', '=', False)]}" force_save="1"/>
<field name="stop_date" string="to" attrs="{'required': [('allday','=',True)],'invisible': [('allday','=',False)], 'readonly': [('user_can_edit', '=', False)]}" force_save="1"/>
<field name="start" string="from" attrs="{'required': [('allday','=',False)], 'invisible': [('allday','=',True)], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="stop" string="to" attrs="{'required': [('allday','=',False)], 'invisible': [('allday','=',True)], 'readonly': [('user_can_edit', '=', False)]}"/>
<label for="duration" attrs="{'invisible': [('allday','=',True)]}"/>
<div attrs="{'invisible': [('allday','=',True)]}">
<field name="duration" widget="float_time" string="Duration" class="oe_inline" attrs="{'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/>
<field name="duration" widget="float_time" string="Duration" class="oe_inline" attrs="{'readonly': ['|', '&amp;', ('id', '!=', False), ('recurrency', '=', True), ('user_can_edit', '=', False)]}"/>
<span> hours</span>
</div>
<field name="event_tz" attrs="{'invisible': [('recurrency', '=', False)]}"/>
<field name="allday" force_save="1"/>
<field name="event_tz" attrs="{'invisible': [('recurrency', '=', False)], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="allday" force_save="1" attrs="{'readonly': [('user_can_edit', '=', False)]}"/>
</group>
<group>
<field name="alarm_ids" widget="many2many_tags" options="{'no_quick_create': True}"/>
@@ -166,10 +167,10 @@
<field name="access_token" invisible="1" force_save="1"/>
<label for="privacy"/>
<div class="o_row">
<field name="show_as" nolabel="1"/>
<field name="privacy" nolabel="1"/>
<field name="show_as" attrs="{'readonly': [('user_can_edit', '=', False)]}" nolabel="1"/>
<field name="privacy" attrs="{'readonly': [('user_can_edit', '=', False)]}" nolabel="1"/>
</div>
<field name="user_id" widget="many2one_avatar_user"/>
<field name="user_id" widget="many2one_avatar_user" attrs="{'readonly': [('user_can_edit', '=', False)]}"/>
<field name="description"/>
</group>
<group>
@@ -193,6 +194,7 @@
context="{'force_email':True}"
domain="[('type','!=','private')]"
class="oe_inline"
attrs="{'readonly': [('user_can_edit', '=', False)]}"
/>
</div>
<div class="alert alert-warning o_form_header mt-2" attrs="{'invisible': [('invalid_email_partner_ids', '=', [])]}" role="status">
@@ -206,23 +208,23 @@
<group>
<div>
<group>
<field name="recurrency"/>
<field name="recurrency" attrs="{'readonly': [('user_can_edit', '=', False)]}"/>
</group>
<div attrs="{'invisible': [('recurrency', '=', False)]}">
<group>
<label for="interval"/>
<div class="o_col">
<div class="o_row">
<field name="interval" class="oe_inline" attrs="{'required': [('recurrency', '=', True)]}"/>
<field name="rrule_type" attrs="{'required': [('recurrency', '=', True)]}"/>
<field name="interval" class="oe_inline" attrs="{'required': [('recurrency', '=', True)], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="rrule_type" attrs="{'required': [('recurrency', '=', True)], 'readonly': [('user_can_edit', '=', False)]}"/>
</div>
<widget name="week_days" attrs="{'invisible': [('rrule_type', '!=', 'weekly')]}"/>
<widget name="week_days" attrs="{'invisible': [('rrule_type', '!=', 'weekly')], 'readonly': [('user_can_edit', '=', False)]}"/>
</div>
<label string="Until" for="end_type"/>
<div class="o_row">
<field name="end_type" attrs="{'required': [('recurrency', '=', True)]}"/>
<field name="count" attrs="{'invisible': [('end_type', '!=', 'count')], 'required': [('recurrency', '=', True)]}"/>
<field name="until" attrs="{'invisible': [('end_type', '!=', 'end_date')], 'required': [('end_type', '=', 'end_date'), ('recurrency', '=', True)]}"/>
<field name="end_type" attrs="{'required': [('recurrency', '=', True)], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="count" attrs="{'invisible': [('end_type', '!=', 'count')], 'required': [('recurrency', '=', True)], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="until" attrs="{'invisible': [('end_type', '!=', 'end_date')], 'required': [('end_type', '=', 'end_date'), ('recurrency', '=', True)], 'readonly': [('user_can_edit', '=', False)]}"/>
</div>
</group>
<group attrs="{'invisible': [('rrule_type', '!=', 'monthly')]}">
@@ -231,13 +233,13 @@
<field name="month_by"/>
<field name="day"
attrs="{'required': [('month_by', '=', 'date'), ('rrule_type', '=', 'monthly')],
'invisible': [('month_by', '!=', 'date')]}"/>
'invisible': [('month_by', '!=', 'date')], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="byday" string="The"
attrs="{'required': [('recurrency', '=', True), ('month_by', '=', 'day'), ('rrule_type', '=', 'monthly')],
'invisible': [('month_by', '!=', 'day')]}"/>
'invisible': [('month_by', '!=', 'day')], 'readonly': [('user_can_edit', '=', False)]}"/>
<field name="weekday" nolabel="1"
attrs="{'required': [('recurrency', '=', True), ('month_by', '=', 'day'), ('rrule_type', '=', 'monthly')],
'invisible': [('month_by', '!=', 'day')]}"/>
'invisible': [('month_by', '!=', 'day')], 'readonly': [('user_can_edit', '=', False)]}"/>
</div>
</group>
</div>
@@ -248,7 +250,7 @@
</group>
</page>
<page name="page_invitations" string="Invitations" groups="base.group_no_one">
<page name="page_invitations" string="Invitations" groups="base.group_no_one" attrs="{'invisible': [('user_can_edit', '=', False)]}">
<button name="action_sendmail" type="object" string="Send Invitations" icon="fa-envelope" class="oe_link"/>
<field name="attendee_ids" widget="one2many" mode="tree,kanban" readonly="1">
<tree string="Invitation details" editable="top" create="false" delete="false">
@@ -256,8 +258,7 @@
<field name="email" widget="email"/>
<field name="phone" widget="phone"/>
<field name="state" />
<button name="do_tentative" states="needsAction,declined,accepted" string="Uncertain" type="object" icon="fa-asterisk" />
<button name="do_tentative" states="needsAction,declined,accepted" string="Uncertain" type="object" icon="fa-asterisk"/>
<button name="do_accept" string="Accept" states="needsAction,tentative,declined" type="object" icon="fa-check text-success"/>
<button name="do_decline" string="Decline" states="needsAction,tentative,accepted" type="object" icon="fa-times-circle text-danger"/>
</tree>
@@ -287,8 +288,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids" />
<field name="message_follower_ids" attrs="{'readonly': [('user_can_edit', '=', False)]}"/>
<field name="message_ids" attrs="{'readonly': [('user_can_edit', '=', False)]}"/>
</div>
</form>
</field>
@@ -24,6 +24,9 @@ export class CalendarCommonPopover extends Component {
get isEventDeletable() {
return this.props.model.canDelete;
}
get hasFooter() {
return this.isEventEditable || this.isEventDeletable;
}
isInvisible(fieldName, record) {
const { invisible } = this.props.model.popoverFields[fieldName].modifiers;
@@ -26,7 +26,7 @@
</div>
<div class="o_cw_body">
<t t-call="{{ constructor.subTemplates.body }}" />
<div class="card-footer border-top" t-att-class="{ 'o_footer_shrink': !isEventEditable and !isEventDeletable }">
<div class="card-footer border-top" t-att-class="{ 'o_footer_shrink': !hasFooter }">
<t t-call="{{ constructor.subTemplates.footer }}" />
</div>
</div>