Compare commits

..

5 Commits

Author SHA1 Message Date
Lauréline Guérin f44c2ead6a
agendas: object history and compare (#86634)
gitea/chrono/pipeline/head There was a failure building this commit Details
2024-02-22 17:28:31 +01:00
Lauréline Guérin 082dadc99e
agendas: take snapshots (#86634) 2024-02-22 17:28:31 +01:00
Lauréline Guérin e26d559007
snapshot: command to clear instances from snapshot (#86634) 2024-02-22 16:57:16 +01:00
Lauréline Guérin 3fc969c741
snapshot: init models (#86634) 2024-02-22 16:57:16 +01:00
Lauréline Guérin 823ff20396
agendas: fix missing options in agenda import/export (#86634) 2024-02-22 16:57:16 +01:00
4 changed files with 105 additions and 24 deletions

View File

@ -504,7 +504,14 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
agenda['booking_check_filters'] = self.booking_check_filters
agenda['event_display_template'] = self.event_display_template
agenda['mark_event_checked_auto'] = self.mark_event_checked_auto
agenda['disable_check_update'] = self.disable_check_update
agenda['enable_check_for_future_events'] = self.enable_check_for_future_events
agenda['booking_extra_user_block_template'] = self.booking_extra_user_block_template
agenda['events_type'] = self.events_type.slug if self.events_type else None
agenda['partial_bookings'] = self.partial_bookings
if self.partial_bookings:
agenda['invoicing_tolerance'] = self.invoicing_tolerance
agenda['invoicing_unit'] = self.invoicing_unit
elif self.kind == 'meetings':
agenda['meetingtypes'] = [x.export_json() for x in self.meetingtype_set.filter(deleted=False)]
agenda['desks'] = [desk.export_json() for desk in self.desk_set.all()]

View File

@ -46,7 +46,7 @@ from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.safestring import SafeString
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from django.utils.translation import ngettext, pgettext
from django.views.generic import (
CreateView,
DayArchiveView,
@ -751,7 +751,7 @@ class ResourceAddView(CreateView):
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.object.take_snapshot(request=self.request, comment=_('created'))
self.object.take_snapshot(request=self.request, comment=pgettext('snapshot', 'created'))
return response
@ -865,7 +865,7 @@ class CategoryAddView(CreateView):
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.object.take_snapshot(request=self.request, comment=_('created'))
self.object.take_snapshot(request=self.request, comment=pgettext('snapshot', 'created'))
return response
@ -983,7 +983,7 @@ class EventsTypeAddView(CreateView):
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.object.take_snapshot(request=self.request, comment=_('created'))
self.object.take_snapshot(request=self.request, comment=pgettext('snapshot', 'created'))
return response
@ -1139,6 +1139,7 @@ class AgendaAddView(CreateView):
if self.object.kind == 'events':
desk = Desk.objects.create(agenda=self.object, slug='_exceptions_holder')
desk.import_timeperiod_exceptions_from_settings()
self.object.take_snapshot(request=self.request, comment=pgettext('snapshot', 'created'))
return result
@ -1343,12 +1344,18 @@ class AgendaEditView(ManagedAgendaMixin, UpdateView):
title = _('Edit Agenda')
model = Agenda
form_class = AgendaEditForm
comment = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.title
return context
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.agenda.take_snapshot(request=self.request, comment=self.comment)
return response
agenda_edit = AgendaEditView.as_view()
@ -1357,6 +1364,7 @@ class AgendaBookingDelaysView(AgendaEditView):
form_class = AgendaBookingDelaysForm
title = _('Configure booking delays')
tab_anchor = 'delays'
comment = _('changed booking delays')
agenda_booking_delays = AgendaBookingDelaysView.as_view()
@ -1366,6 +1374,7 @@ class AgendaRolesView(AgendaEditView):
form_class = AgendaRolesForm
title = _('Configure roles')
tab_anchor = 'permissions'
comment = _('changed roles')
agenda_roles = AgendaRolesView.as_view()
@ -1375,6 +1384,7 @@ class AgendaDisplaySettingsView(AgendaEditView):
form_class = AgendaDisplaySettingsForm
title = _('Configure display options')
tab_anchor = 'display-options'
comment = _('changed display options')
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(Agenda.objects.exclude(kind='virtual'), pk=kwargs.get('pk'))
@ -1390,6 +1400,7 @@ class AgendaBookingCheckSettingsView(AgendaEditView):
form_class = AgendaBookingCheckSettingsForm
title = _('Configure booking check options')
tab_anchor = 'booking-check-options'
comment = _('changed booking check options')
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
@ -1402,6 +1413,7 @@ class AgendaInvoicingSettingsView(AgendaEditView):
form_class = AgendaInvoicingSettingsForm
title = _('Configure invoicing options')
tab_anchor = 'invoicing-options'
comment = _('changed invoicing options')
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events', partial_bookings=True)
@ -1435,6 +1447,7 @@ class AgendaDeleteView(DeleteView):
context = self.get_context_data()
if context['cannot_delete']:
raise PermissionDenied()
self.object.take_snapshot(request=self.request, deletion=True)
return super().delete(request, *args, **kwargs)
@ -2592,6 +2605,7 @@ class AgendaDuplicate(FormView):
def form_valid(self, form):
self.new_agenda = self.agenda.duplicate(label=form.cleaned_data['label'])
self.new_agenda.take_snapshot(request=self.request, comment=pgettext('snapshot', 'created'))
return super().form_valid(form)
def get_context_data(self, **kwargs):
@ -2762,6 +2776,10 @@ class AgendaDeskManagementToggleView(ManagedAgendaMixin, View):
message = _('Desk global management enabled.')
if message:
messages.info(self.request, message)
if self.agenda.desk_simple_management:
self.agenda.take_snapshot(request=self.request, comment=_('enabled individual management'))
else:
self.agenda.take_snapshot(request=self.request, comment=_('enabled global management'))
return HttpResponseRedirect(reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.pk}))
@ -2787,6 +2805,11 @@ class AgendaNotificationsSettingsView(ManagedAgendaMixin, UpdateView):
self.agenda.event_set.filter(**filter_kwargs).update(**update_kwargs)
return AgendaNotificationsSettings.objects.create(agenda=self.agenda)
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.agenda.take_snapshot(request=self.request, comment=_('changed notification settings'))
return response
agenda_notifications_settings = AgendaNotificationsSettingsView.as_view()
@ -2796,6 +2819,7 @@ class AgendaReminderSettingsView(AgendaEditView):
model = AgendaReminderSettings
form_class = AgendaReminderForm
tab_anchor = 'reminders'
comment = _('changed reminder settings')
def get_object(self):
try:
@ -3136,7 +3160,7 @@ class EventDeleteView(ManagedAgendaMixin, DeleteView):
if context['cannot_delete']:
raise PermissionDenied()
response = super().delete(request, *args, **kwargs)
self.agenda.take_snapshot(request=self.request, comment=_('deleted event %s') % self.object)
self.agenda.take_snapshot(request=self.request, comment=_('removed event %s') % self.object)
return response
def get_success_url(self):
@ -3509,7 +3533,7 @@ class MeetingTypeDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
self.object.slug += '__deleted__' + str(uuid.uuid4())
self.object.save()
self.object.agenda.take_snapshot(
request=self.request, comment=_('deleted meeting type %s') % self.object
request=self.request, comment=_('removed meeting type %s') % self.object
)
return HttpResponseRedirect(success_url)
@ -3564,6 +3588,7 @@ class VirtualAgendaAddTimePeriodView(ManagedAgendaMixin, FormView):
def form_valid(self, form):
process_time_period_add_form(form, agenda=self.agenda)
self.agenda.take_snapshot(request=self.request, comment=_('added excluded period'))
return super().form_valid(form)
@ -3588,13 +3613,18 @@ class TimePeriodEditView(ManagedTimePeriodMixin, UpdateView):
def form_valid(self, form):
response = super().form_valid(form)
if self.object.date:
self.object.desk.agenda.take_snapshot(
request=self.request, comment=_('changed unique period %s') % self.object
)
if self.object.desk:
if self.object.date:
self.object.desk.agenda.take_snapshot(
request=self.request, comment=_('changed unique period %s') % self.object
)
else:
self.object.desk.agenda.take_snapshot(
request=self.request, comment=_('changed repeating period %s') % self.object
)
else:
self.object.desk.agenda.take_snapshot(
request=self.request, comment=_('changed repeating period %s') % self.object
self.object.agenda.take_snapshot(
request=self.request, comment=_('changed excluded period %s') % self.object
)
return response
@ -3608,13 +3638,18 @@ class TimePeriodDeleteView(ManagedTimePeriodMixin, DeleteView):
tab_anchor = 'time-periods'
def take_snapshot(self, time_period):
if time_period.date:
self.time_period.desk.agenda.take_snapshot(
request=self.request, comment=_('deleted unique period %s') % time_period
)
if self.object.desk:
if time_period.date:
self.time_period.desk.agenda.take_snapshot(
request=self.request, comment=_('removed unique period %s') % time_period
)
else:
self.time_period.desk.agenda.take_snapshot(
request=self.request, comment=_('removed repeating period %s') % time_period
)
else:
self.time_period.desk.agenda.take_snapshot(
request=self.request, comment=_('deleted repeating period %s') % time_period
self.object.agenda.take_snapshot(
request=self.request, comment=_('removed excluded period %s') % self.object
)
def delete(self, request, *args, **kwargs):
@ -3775,6 +3810,13 @@ class VirtualMemberAddView(ManagedAgendaMixin, CreateView):
kwargs['instance'] = VirtualMember(virtual_agenda=self.agenda)
return kwargs
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.agenda.take_snapshot(
request=self.request, comment=_('added agenda %s') % self.object.real_agenda
)
return response
agenda_add_virtual_member = VirtualMemberAddView.as_view()
@ -3910,11 +3952,11 @@ class TimePeriodExceptionDeleteView(ManagedTimePeriodExceptionMixin, DeleteView)
response = super().delete(request, *args, **kwargs)
if not self.desk:
self.unavailability_calendar.take_snapshot(
request=self.request, comment=_('deleted unavailability %s') % self.object
request=self.request, comment=_('removed unavailability %s') % self.object
)
else:
self.desk.agenda.take_snapshot(
request=self.request, comment=_('deleted exception %s') % self.object
request=self.request, comment=_('removed exception %s') % self.object
)
if not exception.desk_id:
@ -4573,7 +4615,7 @@ class UnavailabilityCalendarAddView(CreateView):
def form_valid(self, *args, **kwargs):
response = super().form_valid(*args, **kwargs)
self.object.take_snapshot(request=self.request, comment=_('created'))
self.object.take_snapshot(request=self.request, comment=pgettext('snapshot', 'created'))
return response

View File

@ -467,6 +467,7 @@ def test_add_agenda_and_set_role(app, admin_user, manager_user):
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'meetings'
resp = resp.form.submit().follow()
assert AgendaSnapshot.objects.count() == 1
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.desk_set.count() == 1
@ -476,7 +477,7 @@ def test_add_agenda_and_set_role(app, admin_user, manager_user):
resp.form['edit_role'] = manager_user.groups.all()[0].pk
resp = resp.form.submit().follow()
assert 'Edit Role: Managers' in resp.text
assert AgendaSnapshot.objects.count() == 1
assert AgendaSnapshot.objects.count() == 2
# still only one desk
assert agenda.desk_set.count() == 1
@ -554,6 +555,7 @@ def test_options_agenda(app, admin_user):
def test_options_events_agenda_events_type(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
app = login(app)
resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
@ -581,6 +583,7 @@ def test_options_events_agenda_events_type(app, admin_user):
def test_options_events_agenda_delays(settings, app, admin_user):
settings.WORKING_DAY_CALENDAR = None
agenda = Agenda.objects.create(label='Foo bar')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
assert agenda.minimal_booking_delay == 1
app = login(app)
url = '/manage/agendas/%s/booking-delays' % agenda.pk
@ -590,7 +593,6 @@ def test_options_events_agenda_delays(settings, app, admin_user):
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.minimal_booking_delay == 1
assert AgendaSnapshot.objects.count() == 1
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
resp = app.get(url)
@ -598,6 +600,7 @@ def test_options_events_agenda_delays(settings, app, admin_user):
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.minimal_booking_delay_in_working_days is True
assert AgendaSnapshot.objects.count() == 1
def test_options_events_agenda_lingo_link(settings, app, admin_user):
@ -629,6 +632,7 @@ def test_options_virtual_agenda_delays(app, admin_user):
def test_options_agenda_booking_display_options(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
app = login(app)
@ -692,6 +696,7 @@ def test_options_agenda_booking_display_options(app, admin_user):
def test_options_agenda_booking_check_options(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
app = login(app)
@ -2508,6 +2513,7 @@ def test_agenda_view_event(app, manager_user):
def test_agenda_view_edit_event(app, manager_user):
test_agenda_view_event(app, manager_user)
agenda = Agenda.objects.first()
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
resp = app.get('/manage/agendas/%s/month/2019/12/01/' % agenda.id, status=200)
resp = resp.click('xyz')
assert 'Options' not in resp.text
@ -3328,7 +3334,6 @@ def test_booking_cancellation_meetings_agenda(app, admin_user, manager_user, man
resp = resp.form.submit()
booking.refresh_from_db()
assert booking.cancellation_datetime
assert AgendaSnapshot.objects.count() == 1
app.reset()
booking.cancellation_datetime = None

View File

@ -192,6 +192,9 @@ def test_import_export_events_agenda_options(app):
booking_check_filters='foo,bar',
event_display_template='foo bar',
mark_event_checked_auto=True,
disable_check_update=True,
enable_check_for_future_events=True,
booking_extra_user_block_template='foobar',
)
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
@ -213,6 +216,30 @@ def test_import_export_events_agenda_options(app):
assert agenda.booking_check_filters == 'foo,bar'
assert agenda.event_display_template == 'foo bar'
assert agenda.mark_event_checked_auto is True
assert agenda.disable_check_update is True
assert agenda.enable_check_for_future_events is True
assert agenda.booking_extra_user_block_template == 'foobar'
agenda.partial_bookings = True
agenda.invoicing_tolerance = 5
agenda.invoicing_unit = 'minute'
agenda.save()
output = get_output_of_command('export_site')
assert len(json.loads(output)['agendas']) == 1
import_site(data={}, clean=True)
with tempfile.NamedTemporaryFile() as f:
f.write(force_bytes(output))
f.flush()
call_command('import_site', f.name)
assert Agenda.objects.count() == 1
agenda = Agenda.objects.first()
assert agenda.partial_bookings is True
assert agenda.invoicing_tolerance == 5
assert agenda.invoicing_unit == 'minute'
def test_import_export_event_details(app):