This commit is contained in:
parent
be975cfa29
commit
c8aea5747e
|
@ -9,6 +9,7 @@ recursive-include chrono/api/templates *.html
|
|||
recursive-include chrono/agendas/templates *.html *.txt
|
||||
recursive-include chrono/manager/templates *.html *.txt
|
||||
recursive-include chrono/apps/ants_hub/templates *.html
|
||||
recursive-include chrono/apps/journal/templates *.html
|
||||
|
||||
# sql (migrations)
|
||||
recursive-include chrono/agendas/sql *.sql
|
||||
|
|
|
@ -78,6 +78,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from django.utils.translation import ngettext, pgettext_lazy
|
||||
|
||||
from chrono.apps.export_import.models import WithApplicationMixin
|
||||
from chrono.apps.journal.utils import audit
|
||||
from chrono.apps.snapshot.models import (
|
||||
AgendaSnapshot,
|
||||
CategorySnapshot,
|
||||
|
@ -3153,15 +3154,23 @@ class Booking(models.Model):
|
|||
self.secondary_booking_set.update(in_waiting_list=True)
|
||||
self.save()
|
||||
|
||||
def reset_user_was_present(self):
|
||||
def reset_user_was_present(self, request=None):
|
||||
with transaction.atomic():
|
||||
audit(
|
||||
'check',
|
||||
_('reset check of %(user)s in %(event)s') % {'user': self.user_name, 'event': self.event},
|
||||
request=request,
|
||||
agenda=self.event.agenda,
|
||||
)
|
||||
if self.user_check:
|
||||
self.user_check.delete()
|
||||
self.user_check = None
|
||||
self.event.checked = False
|
||||
self.event.save(update_fields=['checked'])
|
||||
|
||||
def mark_user_absence(self, check_type_slug=None, check_type_label=None, start_time=None, end_time=None):
|
||||
def mark_user_absence(
|
||||
self, check_type_slug=None, check_type_label=None, start_time=None, end_time=None, request=None
|
||||
):
|
||||
if not self.user_check:
|
||||
self.user_check = BookingCheck(booking=self)
|
||||
self.user_check.presence = False
|
||||
|
@ -3172,12 +3181,20 @@ class Booking(models.Model):
|
|||
|
||||
self.cancellation_datetime = None
|
||||
with transaction.atomic():
|
||||
audit(
|
||||
'check',
|
||||
_('marked absence of %(user)s in %(event)s') % {'user': self.user_name, 'event': self.event},
|
||||
request=request,
|
||||
agenda=self.event.agenda,
|
||||
)
|
||||
self.user_check.save()
|
||||
self.secondary_booking_set.update(cancellation_datetime=None)
|
||||
self.save()
|
||||
self.event.set_is_checked()
|
||||
|
||||
def mark_user_presence(self, check_type_slug=None, check_type_label=None, start_time=None, end_time=None):
|
||||
def mark_user_presence(
|
||||
self, check_type_slug=None, check_type_label=None, start_time=None, end_time=None, request=None
|
||||
):
|
||||
if not self.user_check:
|
||||
self.user_check = BookingCheck(booking=self)
|
||||
self.user_check.presence = True
|
||||
|
@ -3188,6 +3205,12 @@ class Booking(models.Model):
|
|||
|
||||
self.cancellation_datetime = None
|
||||
with transaction.atomic():
|
||||
audit(
|
||||
'check',
|
||||
_('marked presence of %(user)s in %(event)s') % {'user': self.user_name, 'event': self.event},
|
||||
request=request,
|
||||
agenda=self.event.agenda,
|
||||
)
|
||||
self.user_check.save()
|
||||
self.secondary_booking_set.update(cancellation_datetime=None)
|
||||
self.save()
|
||||
|
|
|
@ -57,6 +57,7 @@ from chrono.agendas.models import (
|
|||
)
|
||||
from chrono.api import serializers
|
||||
from chrono.api.utils import APIError, APIErrorBadRequest, Response
|
||||
from chrono.apps.journal.utils import audit
|
||||
from chrono.utils.publik_urls import translate_to_publik_url
|
||||
from chrono.utils.timezone import localtime, make_aware, now
|
||||
|
||||
|
@ -2448,6 +2449,19 @@ class MultipleAgendasEventsCheckLock(APIView):
|
|||
for event in events:
|
||||
event.async_refresh_booking_computed_times()
|
||||
|
||||
message = (
|
||||
_('marked event %(event)s as locked for checks')
|
||||
if check_locked
|
||||
else _('unmarked event %(event)s as locked for checks')
|
||||
)
|
||||
for event in events:
|
||||
audit(
|
||||
'check',
|
||||
message % {'event': event},
|
||||
request=request,
|
||||
agenda=event.agenda,
|
||||
)
|
||||
|
||||
return Response({'err': 0})
|
||||
|
||||
|
||||
|
@ -2477,6 +2491,17 @@ class MultipleAgendasEventsInvoiced(APIView):
|
|||
)
|
||||
events.update(invoiced=invoiced)
|
||||
|
||||
message = (
|
||||
_('marked event %(event)s as invoiced') if invoiced else _('unmarked event %(event)s as invoiced')
|
||||
)
|
||||
for event in events:
|
||||
audit(
|
||||
'invoice',
|
||||
message % {'event': event},
|
||||
request=request,
|
||||
agenda=event.agenda,
|
||||
)
|
||||
|
||||
return Response({'err': 0})
|
||||
|
||||
|
||||
|
@ -3334,6 +3359,12 @@ class EventCheck(APIView):
|
|||
if not event.checked:
|
||||
event.checked = True
|
||||
event.save(update_fields=['checked'])
|
||||
audit(
|
||||
'check',
|
||||
_('marked event %(event)s as checked') % {'event': event},
|
||||
request=request,
|
||||
agenda=event.agenda,
|
||||
)
|
||||
event.async_notify_checked()
|
||||
response = {
|
||||
'err': 0,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = []
|
||||
operations = []
|
|
@ -0,0 +1,48 @@
|
|||
# Generated by Django 3.2.16 on 2024-03-26 08:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('agendas', '0171_snapshot_models'),
|
||||
('journal', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuditEntry',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('action_type', models.CharField(max_length=100)),
|
||||
('action_text', models.TextField()),
|
||||
(
|
||||
'agenda',
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='audit_entries',
|
||||
to='agendas.agenda',
|
||||
),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-timestamp',),
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,31 @@
|
|||
# chrono - agendas system
|
||||
# Copyright (C) 2016-2024 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
class AuditEntry(models.Model):
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
|
||||
action_type = models.CharField(max_length=100)
|
||||
action_text = models.TextField()
|
||||
agenda = models.ForeignKey(
|
||||
'agendas.Agenda', on_delete=models.SET_NULL, null=True, related_name='audit_entries'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('-timestamp',)
|
|
@ -0,0 +1,26 @@
|
|||
# chrono - agendas system
|
||||
# Copyright (C) 2016-2024 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .models import AuditEntry
|
||||
|
||||
|
||||
def audit(action_type, action_text, request=None, user=None, agenda=None):
|
||||
AuditEntry.objects.create(
|
||||
user=request.user if request else user,
|
||||
action_type=action_type,
|
||||
action_text=action_text,
|
||||
agenda=agenda,
|
||||
)
|
|
@ -92,6 +92,7 @@ from chrono.agendas.models import (
|
|||
VirtualMember,
|
||||
)
|
||||
from chrono.apps.export_import.models import Application
|
||||
from chrono.apps.journal.utils import audit
|
||||
from chrono.apps.snapshot.models import (
|
||||
AgendaSnapshot,
|
||||
CategorySnapshot,
|
||||
|
@ -3334,9 +3335,16 @@ class EventPresenceView(EventCheckMixin, ViewableAgendaMixin, FormView):
|
|||
booking_check = BookingCheck(booking=booking, presence=True, **qs_kwargs)
|
||||
booking_checks_to_create.append(booking_check)
|
||||
|
||||
BookingCheck.objects.filter(booking__in=bookings).update(presence=True, **qs_kwargs)
|
||||
BookingCheck.objects.bulk_create(booking_checks_to_create)
|
||||
self.event.set_is_checked()
|
||||
with transaction.atomic():
|
||||
audit(
|
||||
'check',
|
||||
_('marked unchecked users as present in %(event)s') % {'event': self.event},
|
||||
request=request,
|
||||
agenda=self.event.agenda,
|
||||
)
|
||||
BookingCheck.objects.filter(booking__in=bookings).update(presence=True, **qs_kwargs)
|
||||
BookingCheck.objects.bulk_create(booking_checks_to_create)
|
||||
self.event.set_is_checked()
|
||||
return self.response(request)
|
||||
|
||||
|
||||
|
@ -3365,9 +3373,17 @@ class EventAbsenceView(EventCheckMixin, ViewableAgendaMixin, FormView):
|
|||
booking_check = BookingCheck(booking=booking, presence=False, **qs_kwargs)
|
||||
booking_checks_to_create.append(booking_check)
|
||||
|
||||
BookingCheck.objects.filter(booking__in=bookings).update(presence=False, **qs_kwargs)
|
||||
BookingCheck.objects.bulk_create(booking_checks_to_create)
|
||||
self.event.set_is_checked()
|
||||
with transaction.atomic():
|
||||
audit(
|
||||
'check',
|
||||
_('marked unchecked users as absent in %(event)s') % {'event': self.event},
|
||||
request=request,
|
||||
agenda=self.event.agenda,
|
||||
)
|
||||
|
||||
BookingCheck.objects.filter(booking__in=bookings).update(presence=False, **qs_kwargs)
|
||||
BookingCheck.objects.bulk_create(booking_checks_to_create)
|
||||
self.event.set_is_checked()
|
||||
return self.response(request)
|
||||
|
||||
|
||||
|
@ -3379,6 +3395,12 @@ class EventCheckedView(EventCheckMixin, ViewableAgendaMixin, View):
|
|||
if not self.event.checked:
|
||||
self.event.checked = True
|
||||
self.event.save(update_fields=['checked'])
|
||||
audit(
|
||||
'check',
|
||||
_('marked %(event)s as checked') % {'event': self.event},
|
||||
request=request,
|
||||
agenda=self.agenda,
|
||||
)
|
||||
self.event.async_notify_checked()
|
||||
return self.response(request)
|
||||
|
||||
|
@ -4356,6 +4378,7 @@ class PresenceViewMixin:
|
|||
booking.mark_user_presence(
|
||||
check_type_slug=check_type.slug if check_type else None,
|
||||
check_type_label=check_type.label if check_type else None,
|
||||
request=request,
|
||||
)
|
||||
return self.response(request, booking)
|
||||
|
||||
|
@ -4372,6 +4395,7 @@ class AbsenceViewMixin:
|
|||
booking.mark_user_absence(
|
||||
check_type_slug=check_type.slug if check_type else None,
|
||||
check_type_label=check_type.label if check_type else None,
|
||||
request=request,
|
||||
)
|
||||
return self.response(request, booking)
|
||||
|
||||
|
@ -4393,7 +4417,7 @@ booking_absence = BookingAbsenceView.as_view()
|
|||
class BookingResetView(ViewableAgendaMixin, BookingCheckMixin, FormView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
booking = self.get_booking(**kwargs)
|
||||
booking.reset_user_was_present()
|
||||
booking.reset_user_was_present(request=request)
|
||||
return self.response(request, booking)
|
||||
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ INSTALLED_APPS = (
|
|||
'chrono.manager',
|
||||
'chrono.apps.ants_hub',
|
||||
'chrono.apps.export_import',
|
||||
'chrono.apps.journal',
|
||||
'chrono.apps.snapshot',
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue