Compare commits
21 Commits
4d7a3c1b17
...
5ce8ea55cd
Author | SHA1 | Date |
---|---|---|
Valentin Deniaud | 5ce8ea55cd | |
Valentin Deniaud | e4e3a30981 | |
Valentin Deniaud | a770d74e25 | |
Valentin Deniaud | c491db6c77 | |
Valentin Deniaud | 41d948ba6a | |
Valentin Deniaud | 5ae3ecb607 | |
Valentin Deniaud | db6f11c7c0 | |
Emmanuel Cazenave | 1827cfe5c6 | |
Benjamin Dauvergne | bdae8cd217 | |
Benjamin Dauvergne | dbd62e3f3f | |
Benjamin Dauvergne | ddc52a4116 | |
Benjamin Dauvergne | 22e8d00e31 | |
Benjamin Dauvergne | 07f996ea76 | |
Lauréline Guérin | be3eedc3fe | |
Lauréline Guérin | dc4dbe62e1 | |
Lauréline Guérin | 1ba55c2f18 | |
Lauréline Guérin | 2b9b2f2607 | |
Lauréline Guérin | 0a9e36883c | |
Lauréline Guérin | 970ba42075 | |
Lauréline Guérin | 6c134bfa3f | |
Lauréline Guérin | 45b72d53de |
|
@ -18,6 +18,6 @@ e07c450d7c8a5f80aafe185c85ebed73fe39d9e7
|
|||
# misc: apply djhtml (#69404)
|
||||
7c0eb04c453f50cdffe0095148aa52580c20143e
|
||||
# misc: bump black version (#75442)
|
||||
03fa44aca0d2c46e84755ec91d91d7525943804f
|
||||
c491db6c777cde0684159f821e779f89939459f0
|
||||
# misc: bump djhtml version (#75442)
|
||||
04b405ba3d0fff638763866ee169ed196ae6b27e
|
||||
a770d74e252f3bdee1425db9c65cb876b5dcc3f3
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
|
||||
from django.apps import apps
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import connection
|
||||
from django.db.models import JSONField
|
||||
from django.db import connection, models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -26,7 +25,7 @@ class Command(BaseCommand):
|
|||
def handle(self, **options):
|
||||
for app in apps.get_models():
|
||||
for field in app._meta.get_fields():
|
||||
if isinstance(field, JSONField):
|
||||
if isinstance(field, models.JSONField):
|
||||
table_name = app._meta.db_table
|
||||
column_name = app._meta.get_field(field.name).column
|
||||
with connection.cursor() as cursor:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@ -15,7 +14,7 @@ class Migration(migrations.Migration):
|
|||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('extra_data', JSONField(null=True)),
|
||||
('extra_data', models.JSONField(null=True)),
|
||||
('event', models.ForeignKey(to='agendas.Event', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={},
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Generated by Django 1.11.18 on 2020-08-11 14:11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@ -20,7 +19,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('seen', models.BooleanField(default=False)),
|
||||
('booking_errors', JSONField(default=dict)),
|
||||
('booking_errors', models.JSONField(default=dict)),
|
||||
('bookings', models.ManyToManyField(to='agendas.Booking')),
|
||||
],
|
||||
options={
|
||||
|
|
|
@ -10,6 +10,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='user_was_present',
|
||||
field=models.NullBooleanField(),
|
||||
field=models.BooleanField(null=True),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@ -11,7 +10,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='subscription',
|
||||
name='extra_data',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(null=True),
|
||||
field=models.JSONField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='subscription',
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@ -17,7 +16,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
('slug', models.SlugField(max_length=160, unique=True, verbose_name='Identifier')),
|
||||
('label', models.CharField(max_length=150, verbose_name='Label')),
|
||||
('custom_fields', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list)),
|
||||
('custom_fields', models.JSONField(blank=True, default=list)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -11,6 +10,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='custom_fields',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('agendas', '0146_event_bigautofield'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='agenda',
|
||||
name='enable_check_for_future_events',
|
||||
field=models.BooleanField(
|
||||
default=False, verbose_name='Enable the check of bookings when event has not passed'
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.18 on 2023-03-08 17:20
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('agendas', '0147_check_future_events'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='agenda',
|
||||
name='minimal_booking_time',
|
||||
field=models.TimeField(
|
||||
blank=True, default=datetime.time(0, 0), null=True, verbose_name='Minimal booking time'
|
||||
),
|
||||
),
|
||||
]
|
|
@ -25,6 +25,7 @@ import sys
|
|||
import uuid
|
||||
from contextlib import contextmanager
|
||||
|
||||
import django
|
||||
import requests
|
||||
import vobject
|
||||
from dateutil.relativedelta import SU, relativedelta
|
||||
|
@ -43,7 +44,6 @@ from django.db.models import (
|
|||
F,
|
||||
Func,
|
||||
IntegerField,
|
||||
JSONField,
|
||||
Max,
|
||||
OuterRef,
|
||||
Prefetch,
|
||||
|
@ -51,7 +51,7 @@ from django.db.models import (
|
|||
Subquery,
|
||||
Value,
|
||||
)
|
||||
from django.db.models.functions import Cast, Coalesce, Concat, ExtractWeek, ExtractWeekDay
|
||||
from django.db.models.functions import Cast, Coalesce, Concat, ExtractWeek, ExtractWeekDay, JSONObject
|
||||
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist, engines
|
||||
from django.urls import reverse
|
||||
from django.utils import functional
|
||||
|
@ -227,6 +227,9 @@ class Agenda(models.Model):
|
|||
disable_check_update = models.BooleanField(
|
||||
_('Prevent the check of bookings when event was marked as checked'), default=False
|
||||
)
|
||||
enable_check_for_future_events = models.BooleanField(
|
||||
_('Enable the check of bookings when event has not passed'), default=False
|
||||
)
|
||||
booking_check_filters = models.CharField(
|
||||
_('Filters'),
|
||||
max_length=250,
|
||||
|
@ -259,6 +262,15 @@ class Agenda(models.Model):
|
|||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
minimal_booking_time = models.TimeField(
|
||||
verbose_name=_('Minimal booking time'),
|
||||
default=datetime.time(0, 0, 0), # booking is possible starting and finishin at 00:00
|
||||
help_text=_(
|
||||
'Ex.: 08:00:00. If left empty, available events will be those that are later than the current time.'
|
||||
),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['label']
|
||||
|
@ -631,31 +643,27 @@ class Agenda(models.Model):
|
|||
if self.maximal_booking_delay is None:
|
||||
return None
|
||||
|
||||
# compute middle of today with localtime
|
||||
# 28 Mar 2021 12:00 +01:00
|
||||
t = localtime(now()).replace(hour=12, minute=0)
|
||||
# reference is now, in local timezone
|
||||
t = localtime(now())
|
||||
|
||||
# advance of self.maximal_booking_delay - 1 days
|
||||
# 28 Mar 2021 12:00 +01:00 == 28 Mars 2021 13:00 +02:00 as DST happend on 28 Mar 2021.
|
||||
# add delay
|
||||
t += datetime.timedelta(days=self.maximal_booking_delay)
|
||||
|
||||
# move to midnight of the day before, DST happen between 2h/3h so it
|
||||
# always exists because +/- timedelta does not move the timezone, only
|
||||
# localtime() does it.
|
||||
# 27 Mar 2021 12:00 +01:00 == 28 Mars 2021 01:00 +02:00
|
||||
return localtime(t).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
# replace time if needed
|
||||
if self.minimal_booking_time:
|
||||
t = datetime.datetime.combine(t.date(), self.minimal_booking_time, tzinfo=t.tzinfo)
|
||||
# t could not exist, recompute it as an existing datetime by converting to UTC then to localtime
|
||||
return localtime(t.astimezone(utc))
|
||||
|
||||
@functional.cached_property
|
||||
def min_booking_datetime(self):
|
||||
if self.minimal_booking_delay is None:
|
||||
return None
|
||||
|
||||
# compute middle of today with localtime
|
||||
# 28 Mar 2021 12:00 +01:00
|
||||
t = localtime(now()).replace(hour=12, minute=0)
|
||||
# reference is now, in local timezone
|
||||
t = localtime(now())
|
||||
|
||||
# advance of self.minimal_booking_delay - 1 days
|
||||
# 28 Mar 2021 12:00 +01:00 == 28 Mars 2021 13:00 +02:00 as DST happend on 28 Mar 2021.
|
||||
# add delay
|
||||
if settings.WORKING_DAY_CALENDAR is not None and self.minimal_booking_delay_in_working_days:
|
||||
source_class = import_string(settings.WORKING_DAY_CALENDAR)
|
||||
calendar = source_class()
|
||||
|
@ -663,11 +671,11 @@ class Agenda(models.Model):
|
|||
else:
|
||||
t += datetime.timedelta(days=self.minimal_booking_delay)
|
||||
|
||||
# move to midnight of the day before, DST happen between 2h/3h so it
|
||||
# always exists because +/- timedelta does not move the timezone, only
|
||||
# localtime() does it.
|
||||
# 27 Mar 2021 12:00 +01:00 == 28 Mars 2021 01:00 +02:00
|
||||
return localtime(t).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
# replace time if needed
|
||||
if self.minimal_booking_time:
|
||||
t = datetime.datetime.combine(t.date(), self.minimal_booking_time, tzinfo=t.tzinfo)
|
||||
# t could not exist, recompute it as an existing datetime by converting to UTC then to localtime
|
||||
return localtime(t.astimezone(utc))
|
||||
|
||||
def get_open_events(
|
||||
self,
|
||||
|
@ -1504,7 +1512,7 @@ class Event(models.Model):
|
|||
meeting_type = models.ForeignKey(MeetingType, null=True, on_delete=models.CASCADE)
|
||||
desk = models.ForeignKey('Desk', null=True, on_delete=models.CASCADE)
|
||||
resources = models.ManyToManyField('Resource')
|
||||
custom_fields = JSONField(blank=True, default=dict)
|
||||
custom_fields = models.JSONField(blank=True, default=dict)
|
||||
|
||||
almost_full_notification_timestamp = models.DateTimeField(null=True, blank=True)
|
||||
full_notification_timestamp = models.DateTimeField(null=True, blank=True)
|
||||
|
@ -1689,8 +1697,6 @@ class Event(models.Model):
|
|||
if agendas:
|
||||
overlapping_events = overlapping_events.filter(agenda__in=agendas)
|
||||
|
||||
from django.db.models.functions import JSONObject
|
||||
|
||||
json_object = JSONObject(
|
||||
slug=F('computed_slug'),
|
||||
days=F('recurrence_days'),
|
||||
|
@ -1699,7 +1705,7 @@ class Event(models.Model):
|
|||
return qs.annotate(
|
||||
overlaps=ArraySubquery(
|
||||
overlapping_events.values(json=json_object),
|
||||
output_field=ArrayField(JSONField()),
|
||||
output_field=ArrayField(models.JSONField()),
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -2041,7 +2047,7 @@ class Event(models.Model):
|
|||
class EventsType(models.Model):
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
custom_fields = JSONField(blank=True, default=list)
|
||||
custom_fields = models.JSONField(blank=True, default=list)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
@ -2111,7 +2117,7 @@ class BookingColor(models.Model):
|
|||
|
||||
class Booking(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
extra_data = JSONField(null=True)
|
||||
extra_data = models.JSONField(null=True)
|
||||
anonymization_datetime = models.DateTimeField(null=True)
|
||||
cancellation_datetime = models.DateTimeField(null=True)
|
||||
email_reminder_datetime = models.DateTimeField(null=True)
|
||||
|
@ -2185,14 +2191,28 @@ class Booking(models.Model):
|
|||
self.secondary_booking_set.update(in_waiting_list=True)
|
||||
self.save()
|
||||
|
||||
def reset_user_was_present(self):
|
||||
self.user_check_type_slug = None
|
||||
self.user_check_type_label = None
|
||||
self.user_was_present = None
|
||||
with transaction.atomic():
|
||||
self.secondary_booking_set.update(user_check_type_slug=None)
|
||||
self.secondary_booking_set.update(user_check_type_label=None)
|
||||
self.secondary_booking_set.update(user_was_present=None)
|
||||
self.save()
|
||||
self.event.checked = False
|
||||
self.event.save(update_fields=['checked'])
|
||||
|
||||
def mark_user_absence(self, check_type_slug=None, check_type_label=None):
|
||||
self.user_check_type_slug = check_type_slug
|
||||
self.user_check_type_label = check_type_label
|
||||
self.user_was_present = False
|
||||
self.cancellation_datetime = None
|
||||
with transaction.atomic():
|
||||
self.secondary_booking_set.update(user_check_type_slug=check_type_slug)
|
||||
self.secondary_booking_set.update(user_check_type_label=check_type_label)
|
||||
self.secondary_booking_set.update(user_was_present=False)
|
||||
self.secondary_booking_set.update(cancellation_datetime=None)
|
||||
self.save()
|
||||
self.event.set_is_checked()
|
||||
|
||||
|
@ -2200,10 +2220,12 @@ class Booking(models.Model):
|
|||
self.user_check_type_slug = check_type_slug
|
||||
self.user_check_type_label = check_type_label
|
||||
self.user_was_present = True
|
||||
self.cancellation_datetime = None
|
||||
with transaction.atomic():
|
||||
self.secondary_booking_set.update(user_check_type_slug=check_type_slug)
|
||||
self.secondary_booking_set.update(user_check_type_label=check_type_label)
|
||||
self.secondary_booking_set.update(user_was_present=True)
|
||||
self.secondary_booking_set.update(cancellation_datetime=None)
|
||||
self.save()
|
||||
self.event.set_is_checked()
|
||||
|
||||
|
@ -3014,7 +3036,7 @@ class EventCancellationReport(models.Model):
|
|||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
seen = models.BooleanField(default=False)
|
||||
bookings = models.ManyToManyField(Booking)
|
||||
booking_errors = JSONField(default=dict)
|
||||
booking_errors = models.JSONField(default=dict)
|
||||
|
||||
def __str__(self):
|
||||
return '%s - %s' % (self.timestamp.strftime('%Y-%m-%d %H:%M:%S'), self.event)
|
||||
|
@ -3258,7 +3280,7 @@ class Subscription(models.Model):
|
|||
user_first_name = models.CharField(max_length=250)
|
||||
user_email = models.EmailField(blank=True)
|
||||
user_phone_number = models.CharField(max_length=30, blank=True)
|
||||
extra_data = JSONField(null=True)
|
||||
extra_data = models.JSONField(null=True)
|
||||
date_start = models.DateField()
|
||||
date_end = models.DateField()
|
||||
|
||||
|
|
|
@ -521,6 +521,7 @@ class AgendaSerializer(serializers.ModelSerializer):
|
|||
'minimal_booking_delay',
|
||||
'minimal_booking_delay_in_working_days',
|
||||
'maximal_booking_delay',
|
||||
'minimal_booking_time',
|
||||
'anonymize_delay',
|
||||
'edit_role',
|
||||
'view_role',
|
||||
|
@ -771,3 +772,9 @@ class SharedCustodyAgendaSerializer(SharedCustodyAgendaMixin, serializers.ModelS
|
|||
|
||||
def get_child_id(self, attrs):
|
||||
return self.instance.child.user_external_id
|
||||
|
||||
|
||||
class MinutesSerializer(serializers.Serializer):
|
||||
minutes = CommaSeparatedStringField(
|
||||
required=False, child=serializers.IntegerField(min_value=0, max_value=59)
|
||||
)
|
||||
|
|
|
@ -265,7 +265,7 @@ def get_all_slots(
|
|||
)
|
||||
booked_events = (
|
||||
Event.objects.filter(
|
||||
agenda__in=agenda_ids,
|
||||
agenda__in=agendas,
|
||||
start_datetime__gte=used_min_datetime - max_meeting_duration_td,
|
||||
start_datetime__lte=used_max_datetime,
|
||||
booking__user_external_id=user_external_id,
|
||||
|
@ -278,7 +278,10 @@ def get_all_slots(
|
|||
# compute exclusion set by desk from all bookings, using
|
||||
# itertools.groupby() to group them by desk_id
|
||||
user_bookings = IntervalSet.from_ordered(
|
||||
(event_start_datetime, event_start_datetime + datetime.timedelta(minutes=event_duration))
|
||||
(
|
||||
event_start_datetime,
|
||||
event_start_datetime + datetime.timedelta(minutes=event_duration),
|
||||
)
|
||||
for event_start_datetime, event_duration in booked_events
|
||||
)
|
||||
|
||||
|
@ -358,6 +361,7 @@ def get_agenda_detail(request, agenda, check_events=False):
|
|||
'kind': agenda.kind,
|
||||
'minimal_booking_delay': agenda.minimal_booking_delay,
|
||||
'maximal_booking_delay': agenda.maximal_booking_delay,
|
||||
'minimal_booking_time': agenda.minimal_booking_time,
|
||||
'edit_role': agenda.edit_role.name if agenda.edit_role else None,
|
||||
'view_role': agenda.view_role.name if agenda.view_role else None,
|
||||
'category': agenda.category.slug if agenda.category else None,
|
||||
|
@ -696,6 +700,13 @@ def get_start_and_end_datetime_from_request(request):
|
|||
return serializer.validated_data.get('date_start'), serializer.validated_data.get('date_end')
|
||||
|
||||
|
||||
def get_minutes_from_request(request):
|
||||
serializer = serializers.MinutesSerializer(data=request.query_params)
|
||||
if not serializer.is_valid():
|
||||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
|
||||
return serializer.validated_data.get('minutes')
|
||||
|
||||
|
||||
def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_list=False, color=None):
|
||||
out_of_min_delay = False
|
||||
if event.agenda.min_booking_datetime and event.start_datetime < event.agenda.min_booking_datetime:
|
||||
|
@ -1017,6 +1028,7 @@ class MeetingDatetimes(APIView):
|
|||
|
||||
resources = get_resources_from_request(request, agenda)
|
||||
start_datetime, end_datetime = get_start_and_end_datetime_from_request(request)
|
||||
minutes = get_minutes_from_request(request)
|
||||
booked_user_external_id = request.GET.get('user_external_id') or None
|
||||
excluded_user_external_id = request.GET.get('exclude_user_external_id') or None
|
||||
if (
|
||||
|
@ -1081,6 +1093,8 @@ class MeetingDatetimes(APIView):
|
|||
for slot in generator_of_unique_slots:
|
||||
if request.GET.get('hide_disabled') and slot.full:
|
||||
continue
|
||||
if minutes and slot.start_datetime.minute not in minutes:
|
||||
continue
|
||||
|
||||
# Make virtual id for a slot, combining meeting_type.id and
|
||||
# iso-format of date and time.
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: chrono 0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-03-02 17:57+0000\n"
|
||||
"PO-Revision-Date: 2023-03-02 18:58+0100\n"
|
||||
"POT-Creation-Date: 2023-03-23 10:33+0100\n"
|
||||
"PO-Revision-Date: 2023-03-23 10:35+0100\n"
|
||||
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
|
||||
"Language: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -205,6 +205,10 @@ msgstr ""
|
|||
"Interdire le pointage des réservations une fois l’événement marqué comme "
|
||||
"pointé"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Enable the check of bookings when event has not passed"
|
||||
msgstr "Activer le pointage des réservations sur les événements à venir"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Filters"
|
||||
msgstr "Filtres"
|
||||
|
@ -243,6 +247,17 @@ msgstr ""
|
|||
msgid "Events type"
|
||||
msgstr "Type d’événements"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Minimal booking time"
|
||||
msgstr "Heure de réservation minimale"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid ""
|
||||
"Ex.: 08:00:00. If left empty, available events will be those that are later "
|
||||
"than the current time."
|
||||
msgstr ""
|
||||
"Ex.: 08:00:00. Si ce champ est laissé vide alors les évènements ou rendez-vous disponibles à la réservation sont ceux dont le début est plus tard que l'heure courante, en prenant en compte les délais minimaux et maximaux de réservation en jours"
|
||||
|
||||
#: agendas/models.py
|
||||
#, python-format
|
||||
msgid "Missing \"%s\" resource"
|
||||
|
@ -408,6 +423,7 @@ msgstr "URL"
|
|||
|
||||
#: agendas/models.py manager/forms.py
|
||||
#: manager/templates/chrono/manager_event_check.html
|
||||
#: manager/templates/chrono/manager_event_check_booking_fragment.html
|
||||
msgid "Cancelled"
|
||||
msgstr "Annulé"
|
||||
|
||||
|
@ -2176,6 +2192,14 @@ msgstr[1] "%(count)s jours"
|
|||
msgid "Maximal booking delay:"
|
||||
msgstr "Délai de réservation maximal :"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Minimal booking time:"
|
||||
msgstr "Heure de réservation minimale :"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "current time"
|
||||
msgstr "heure actuelle"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Edit Role:"
|
||||
msgstr "Rôle d’édition :"
|
||||
|
@ -2435,10 +2459,6 @@ msgstr "Marquer l’événement comme étant pointé"
|
|||
msgid "Mark all bookings without status:"
|
||||
msgstr "Marquer toutes les réservations non précisées :"
|
||||
|
||||
#: manager/templates/chrono/manager_event_check.html
|
||||
msgid "Not booked"
|
||||
msgstr "Non réservé"
|
||||
|
||||
#: manager/templates/chrono/manager_event_check.html
|
||||
#, python-format
|
||||
msgid "Waiting List (%(booked_places)s/%(places)s)"
|
||||
|
@ -2449,10 +2469,19 @@ msgstr "Liste d’attente (%(booked_places)s/%(places)s)"
|
|||
msgid "places"
|
||||
msgstr "places"
|
||||
|
||||
#: manager/templates/chrono/manager_event_check_booking_fragment.html
|
||||
msgid "Not booked"
|
||||
msgstr "Non réservé"
|
||||
|
||||
#: manager/templates/chrono/manager_event_check_booking_fragment.html
|
||||
msgid "Present,Absent,-"
|
||||
msgstr "Présence,Absence,-"
|
||||
|
||||
#: manager/templates/chrono/manager_event_check_booking_fragment.html
|
||||
msgctxt "check"
|
||||
msgid "Reset"
|
||||
msgstr "Réinitialiser"
|
||||
|
||||
#: manager/templates/chrono/manager_event_detail_fragment.html
|
||||
msgid "Publication date:"
|
||||
msgstr "Date de publication :"
|
||||
|
@ -2620,6 +2649,10 @@ msgstr ""
|
|||
"Interdire le pointage des réservations une fois l’événement marqué comme "
|
||||
"pointé :"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Enable the check of bookings when event has not passed:"
|
||||
msgstr "Activer le pointage des réservations sur les événements à venir :"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
#, python-format
|
||||
msgid "%(label)s: %(display_value)s will be notified."
|
||||
|
|
|
@ -129,6 +129,7 @@ class AgendaBookingDelaysForm(forms.ModelForm):
|
|||
'minimal_booking_delay',
|
||||
'minimal_booking_delay_in_working_days',
|
||||
'maximal_booking_delay',
|
||||
'minimal_booking_time',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -1415,6 +1416,7 @@ class AgendaBookingCheckSettingsForm(forms.ModelForm):
|
|||
'booking_check_filters',
|
||||
'mark_event_checked_auto',
|
||||
'disable_check_update',
|
||||
'enable_check_for_future_events',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ $(function() {
|
|||
$form.parent().parent().html(html);
|
||||
if (!$('.booking-status.without-status').length) {
|
||||
$('tr.booking.all-bookings').hide();
|
||||
} else {
|
||||
$('tr.booking.all-bookings').show();
|
||||
}
|
||||
}).fail(function() {
|
||||
location.reload();
|
||||
|
|
|
@ -89,6 +89,10 @@
|
|||
{% if agenda.maximal_booking_delay is not None %}
|
||||
{% blocktrans count count=agenda.maximal_booking_delay %}{{ count }} day{% plural %}{{ count }} days{% endblocktrans %}
|
||||
{% else %}<i>{% trans "undefined" %}</i>{% endif %}</li>
|
||||
{% if agenda.minimal_booking_delay is not None or agenda.maximal_booking_delay is not None %}
|
||||
<li>{% trans "Minimal booking time:" %}
|
||||
{% if agenda.minimal_booking_time is not None %}{{ agenda.minimal_booking_time }}{% else %}{% trans "current time" %}{% endif %}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="panel--buttons">
|
||||
<a rel="popup" class="button" href="{% url 'chrono-manager-agenda-booking-delays' pk=object.id %}">{% trans 'Configure' %}</a>
|
||||
|
|
|
@ -74,23 +74,7 @@
|
|||
{% endif %}
|
||||
{% for result in results %}
|
||||
<tr class="booking">
|
||||
{% if result.kind == 'booking' %}
|
||||
{% if result.cancellation_datetime is None %}
|
||||
{% include "chrono/manager_event_check_booking_fragment.html" with booking=result %}
|
||||
{% else %}
|
||||
<td class="booking-username main-list">{{ result.get_user_block }}</td>
|
||||
<td class="booking-status">({% trans "Cancelled" %})</td>
|
||||
{% if not event.checked or not agenda.disable_check_update %}
|
||||
<td class="booking-actions"></td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% elif result.kind == 'subscription' %}
|
||||
<td class="booking-username main-list">{{ result.get_user_block }}</td>
|
||||
<td class="booking-status">({% trans "Not booked" %})</td>
|
||||
{% if not event.checked or not agenda.disable_check_update %}
|
||||
<td class="booking-actions"></td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% include "chrono/manager_event_check_booking_fragment.html" with booking=result %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -1,43 +1,74 @@
|
|||
{% load i18n %}
|
||||
|
||||
<td class="booking-username main-list">{{ booking.get_user_block }}{% if booking.places_count > 1 %} ({{ booking.places_count }} {% trans "places" %}){% endif %}</td>
|
||||
<td class="booking-status {% if booking.user_was_present is None %}without-status{% endif %}">
|
||||
{{ booking.user_was_present|yesno:_('Present,Absent,-') }}
|
||||
{% if booking.user_was_present is not None and booking.user_check_type_label %}
|
||||
({{ booking.user_check_type_label }})
|
||||
<td class="booking-status {% if booking.kind != "subscription" and booking.cancellation_datetime is None and booking.user_was_present is None %}without-status{% endif %}" data-{{ booking.kind }}-id="{{ booking.id }}">
|
||||
{% if booking.kind == "subscription" %}
|
||||
({% trans "Not booked" %})
|
||||
{% elif booking.cancellation_datetime is None %}
|
||||
{{ booking.user_was_present|yesno:_('Present,Absent,-') }}
|
||||
{% if booking.user_was_present is not None and booking.user_check_type_label %}
|
||||
({{ booking.user_check_type_label }})
|
||||
{% endif %}
|
||||
{% else %}
|
||||
({% trans "Cancelled" %})
|
||||
{% endif %}
|
||||
{% if not event.checked or not agenda.disable_check_update %}
|
||||
{% if booking.user_was_present is not None %}
|
||||
<form method="post" action="{% url 'chrono-manager-booking-reset' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax reset">
|
||||
{% csrf_token %}
|
||||
<a href="#">{% trans "Reset" context "check" %}</a>
|
||||
<script>
|
||||
$(function() {
|
||||
$('td.booking-status[data-{{ booking.kind }}-id="{{ booking.id }}"] form.reset a').on('click',
|
||||
function(e) {
|
||||
e.preventDefault();
|
||||
$(this).parents('form.reset').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if not event.checked or not agenda.disable_check_update %}
|
||||
<td class="booking-actions" data-booking-id="{{ booking.id }}">
|
||||
<form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax presence">
|
||||
{% csrf_token %}
|
||||
<button class="submit-button"
|
||||
{% if booking.user_was_present is True %}disabled{% endif %}
|
||||
>{% trans "Presence" %}</button>
|
||||
{% if booking.presence_form.check_type.field.choices.1 %}{{ booking.presence_form.check_type }}{% endif %}
|
||||
<script>
|
||||
$(function() {
|
||||
$('td[data-booking-id="{{ booking.id }}"] form.presence select').on('change',
|
||||
function() {
|
||||
$(this).parents('form.presence').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
<td class="booking-actions" data-{{ booking.kind }}-id="{{ booking.id }}">
|
||||
{% if booking.kind == "subscription" %}
|
||||
<form method="post" action="{% url 'chrono-manager-subscription-presence' pk=agenda.pk subscription_pk=booking.pk event_pk=event.pk %}" class="with-ajax presence">
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax presence">
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<button class="submit-button"
|
||||
{% if booking.user_was_present is True %}disabled{% endif %}
|
||||
>{% trans "Presence" %}</button>
|
||||
{% if booking.presence_form.check_type.field.choices.1 %}{{ booking.presence_form.check_type }}{% endif %}
|
||||
<script>
|
||||
$(function() {
|
||||
$('td.booking-actions[data-{{ booking.kind }}-id="{{ booking.id }}"] form.presence select').on('change',
|
||||
function() {
|
||||
$(this).parents('form.presence').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
{% if booking.kind == "subscription" %}
|
||||
<form method="post" action="{% url 'chrono-manager-subscription-absence' pk=agenda.pk subscription_pk=booking.pk event_pk=event.pk %}" class="with-ajax absence">
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'chrono-manager-booking-absence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax absence">
|
||||
{% csrf_token %}
|
||||
<button class="submit-button"
|
||||
{% if booking.user_was_present is False %}disabled{% endif %}
|
||||
>{% trans "Absence" %}</button>
|
||||
{% if booking.absence_form.check_type.field.choices.1 %}{{ booking.absence_form.check_type }}{% endif %}
|
||||
<script>
|
||||
$(function() {
|
||||
$('td[data-booking-id="{{ booking.id }}"] form.absence select').on('change',
|
||||
function() {
|
||||
$(this).parents('form.absence').submit();
|
||||
});
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<button class="submit-button"
|
||||
{% if booking.user_was_present is False %}disabled{% endif %}
|
||||
>{% trans "Absence" %}</button>
|
||||
{% if booking.absence_form.check_type.field.choices.1 %}{{ booking.absence_form.check_type }}{% endif %}
|
||||
<script>
|
||||
$(function() {
|
||||
$('td.booking-actions[data-{{ booking.kind }}-id="{{ booking.id }}"] form.absence select').on('change',
|
||||
function() {
|
||||
$(this).parents('form.absence').submit();
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
|
|
@ -56,8 +56,10 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if event.is_day_past and not event.cancelled %}
|
||||
<a href="{% url 'chrono-manager-event-check' pk=agenda.pk event_pk=event.pk %}">{% trans "Check" %}</a>
|
||||
{% if not event.cancelled %}
|
||||
{% if event.is_day_past or agenda.enable_check_for_future_events %}
|
||||
<a href="{% url 'chrono-manager-event-check' pk=agenda.pk event_pk=event.pk %}">{% trans "Check" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
{% endwith %}
|
||||
<li>{% trans "Automatically mark event as checked when all bookings have been checked:" %} {{ agenda.mark_event_checked_auto|yesno }}</li>
|
||||
<li>{% trans "Prevent the check of bookings when event was marked as checked:" %} {{ agenda.disable_check_update|yesno }}</li>
|
||||
<li>{% trans "Enable the check of bookings when event has not passed:" %} {{ agenda.enable_check_for_future_events|yesno }}</li>
|
||||
</ul>
|
||||
<div class="panel--buttons">
|
||||
<a rel="popup" class="button" href="{% url 'chrono-manager-agenda-booking-check-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
|
||||
|
|
|
@ -409,6 +409,21 @@ urlpatterns = [
|
|||
views.booking_absence,
|
||||
name='chrono-manager-booking-absence',
|
||||
),
|
||||
path(
|
||||
'agendas/<int:pk>/bookings/<int:booking_pk>/reset',
|
||||
views.booking_reset,
|
||||
name='chrono-manager-booking-reset',
|
||||
),
|
||||
path(
|
||||
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/presence/<int:event_pk>',
|
||||
views.subscription_presence,
|
||||
name='chrono-manager-subscription-presence',
|
||||
),
|
||||
path(
|
||||
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/absence/<int:event_pk>',
|
||||
views.subscription_absence,
|
||||
name='chrono-manager-subscription-absence',
|
||||
),
|
||||
re_path(
|
||||
r'^agendas/events.csv$',
|
||||
views.agenda_import_events_sample_csv,
|
||||
|
|
|
@ -80,6 +80,7 @@ from chrono.agendas.models import (
|
|||
SharedCustodyPeriod,
|
||||
SharedCustodyRule,
|
||||
SharedCustodySettings,
|
||||
Subscription,
|
||||
TimePeriod,
|
||||
TimePeriodException,
|
||||
TimePeriodExceptionSource,
|
||||
|
@ -2540,7 +2541,11 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
|
|||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = Event.annotate_booking_checks(queryset)
|
||||
return queryset.filter(agenda=self.agenda, start_datetime__date__lte=now().date(), cancelled=False)
|
||||
return queryset.filter(
|
||||
Q(start_datetime__date__lte=now().date()) | Q(agenda__enable_check_for_future_events=True),
|
||||
agenda=self.agenda,
|
||||
cancelled=False,
|
||||
)
|
||||
|
||||
def get_filters(self, booked_queryset, subscription_queryset):
|
||||
agenda_filters = self.agenda.get_booking_check_filters()
|
||||
|
@ -2623,6 +2628,12 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
|
|||
booking.kind = 'booking'
|
||||
results.append(booking)
|
||||
for subscription in subscription_filterset.qs:
|
||||
subscription.absence_form = BookingCheckAbsenceForm(
|
||||
agenda=self.agenda,
|
||||
)
|
||||
subscription.presence_form = BookingCheckPresenceForm(
|
||||
agenda=self.agenda,
|
||||
)
|
||||
subscription.kind = 'subscription'
|
||||
results.append(subscription)
|
||||
# sort results
|
||||
|
@ -2637,9 +2648,8 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
|
|||
|
||||
# set context
|
||||
context['booked_without_status'] = booked_without_status
|
||||
if context['booked_without_status']:
|
||||
context['absence_form'] = BookingCheckAbsenceForm(agenda=self.agenda)
|
||||
context['presence_form'] = BookingCheckPresenceForm(agenda=self.agenda)
|
||||
context['absence_form'] = BookingCheckAbsenceForm(agenda=self.agenda)
|
||||
context['presence_form'] = BookingCheckPresenceForm(agenda=self.agenda)
|
||||
context['filterset'] = booked_filterset
|
||||
context['results'] = results
|
||||
context['waiting'] = waiting_qs
|
||||
|
@ -2660,16 +2670,15 @@ class EventCheckMixin:
|
|||
self.event = get_object_or_404(
|
||||
Event,
|
||||
Q(checked=False) | Q(agenda__disable_check_update=False),
|
||||
Q(start_datetime__date__lte=now().date()) | Q(agenda__enable_check_for_future_events=True),
|
||||
pk=kwargs.get('event_pk'),
|
||||
agenda=self.agenda,
|
||||
start_datetime__date__lte=now().date(),
|
||||
cancelled=False,
|
||||
)
|
||||
|
||||
def get_bookings(self):
|
||||
return self.event.booking_set.filter(
|
||||
event__agenda=self.agenda,
|
||||
event__start_datetime__date__lte=now().date(),
|
||||
event__cancelled=False,
|
||||
cancellation_datetime__isnull=True,
|
||||
in_waiting_list=False,
|
||||
|
@ -3481,17 +3490,20 @@ booking_cancel = BookingCancelView.as_view()
|
|||
|
||||
class BookingCheckMixin:
|
||||
def get_booking(self, **kwargs):
|
||||
return get_object_or_404(
|
||||
booking = get_object_or_404(
|
||||
Booking,
|
||||
Q(event__checked=False) | Q(event__agenda__disable_check_update=False),
|
||||
Q(event__start_datetime__date__lte=now().date())
|
||||
| Q(event__agenda__enable_check_for_future_events=True),
|
||||
pk=kwargs['booking_pk'],
|
||||
event__agenda=self.agenda,
|
||||
event__start_datetime__date__lte=now().date(),
|
||||
event__cancelled=False,
|
||||
cancellation_datetime__isnull=True,
|
||||
in_waiting_list=False,
|
||||
primary_booking__isnull=True,
|
||||
)
|
||||
if not booking.event.agenda.subscriptions.exists() and booking.cancellation_datetime is not None:
|
||||
raise Http404
|
||||
return booking
|
||||
|
||||
def get_check_type(self, kind):
|
||||
form = self.get_form()
|
||||
|
@ -3509,6 +3521,7 @@ class BookingCheckMixin:
|
|||
booking.presence_form = BookingCheckPresenceForm(
|
||||
agenda=self.agenda, initial={'check_type': booking.user_check_type_slug}
|
||||
)
|
||||
booking.kind = 'booking'
|
||||
return render(
|
||||
request,
|
||||
'chrono/manager_event_check_booking_fragment.html',
|
||||
|
@ -3522,9 +3535,7 @@ class BookingCheckMixin:
|
|||
)
|
||||
|
||||
|
||||
class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
|
||||
form_class = BookingCheckPresenceForm
|
||||
|
||||
class PresenceViewMixin:
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['agenda'] = self.agenda
|
||||
|
@ -3540,12 +3551,7 @@ class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
|
|||
return self.response(request, booking)
|
||||
|
||||
|
||||
booking_presence = BookingPresenceView.as_view()
|
||||
|
||||
|
||||
class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
|
||||
form_class = BookingCheckAbsenceForm
|
||||
|
||||
class AbsenceViewMixin:
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['agenda'] = self.agenda
|
||||
|
@ -3561,9 +3567,81 @@ class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
|
|||
return self.response(request, booking)
|
||||
|
||||
|
||||
class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, PresenceViewMixin, FormView):
|
||||
form_class = BookingCheckPresenceForm
|
||||
|
||||
|
||||
booking_presence = BookingPresenceView.as_view()
|
||||
|
||||
|
||||
class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, AbsenceViewMixin, FormView):
|
||||
form_class = BookingCheckAbsenceForm
|
||||
|
||||
|
||||
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()
|
||||
return self.response(request, booking)
|
||||
|
||||
|
||||
booking_reset = BookingResetView.as_view()
|
||||
|
||||
|
||||
class SubscriptionCheckMixin(BookingCheckMixin):
|
||||
def get_booking(self, **kwargs):
|
||||
event = get_object_or_404(
|
||||
Event,
|
||||
Q(checked=False) | Q(agenda__disable_check_update=False),
|
||||
Q(start_datetime__date__lte=now().date()) | Q(agenda__enable_check_for_future_events=True),
|
||||
agenda=self.agenda,
|
||||
cancelled=False,
|
||||
pk=kwargs['event_pk'],
|
||||
)
|
||||
subscription = get_object_or_404(
|
||||
Subscription,
|
||||
agenda=self.agenda,
|
||||
pk=kwargs['subscription_pk'],
|
||||
date_start__lte=event.start_datetime,
|
||||
date_end__gt=event.start_datetime,
|
||||
)
|
||||
try:
|
||||
booking = event.booking_set.get(user_external_id=subscription.user_external_id)
|
||||
raise Http404
|
||||
except Booking.MultipleObjectsReturned:
|
||||
raise Http404
|
||||
except Booking.DoesNotExist:
|
||||
pass
|
||||
booking = event.booking_set.create(
|
||||
user_external_id=subscription.user_external_id,
|
||||
user_last_name=subscription.user_last_name,
|
||||
user_first_name=subscription.user_first_name,
|
||||
user_email=subscription.user_email,
|
||||
user_phone_number=subscription.user_phone_number,
|
||||
extra_data=subscription.extra_data,
|
||||
)
|
||||
|
||||
# create booking
|
||||
return booking
|
||||
|
||||
|
||||
class SubscriptionPresenceView(ViewableAgendaMixin, SubscriptionCheckMixin, PresenceViewMixin, FormView):
|
||||
form_class = BookingCheckPresenceForm
|
||||
|
||||
|
||||
subscription_presence = SubscriptionPresenceView.as_view()
|
||||
|
||||
|
||||
class SubscriptionAbsenceView(ViewableAgendaMixin, SubscriptionCheckMixin, AbsenceViewMixin, FormView):
|
||||
form_class = BookingCheckAbsenceForm
|
||||
|
||||
|
||||
subscription_absence = SubscriptionAbsenceView.as_view()
|
||||
|
||||
|
||||
class EventCancelView(ViewableAgendaMixin, UpdateView):
|
||||
template_name = 'chrono/manager_confirm_event_cancellation.html'
|
||||
model = Event
|
||||
|
|
|
@ -87,6 +87,8 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
||||
|
||||
|
|
|
@ -2608,3 +2608,55 @@ def test_datetimes_end_datetime(app):
|
|||
('2023-03-10 10:00:00', '2023-03-10 10:30:00'),
|
||||
('2023-03-10 10:30:00', '2023-03-10 11:00:00'),
|
||||
]
|
||||
|
||||
|
||||
def test_datetimes_api_meetings_agenda_filter_minutes(app):
|
||||
agenda = Agenda(
|
||||
label='Foo bar Meeting', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=2
|
||||
)
|
||||
agenda.save()
|
||||
meeting_type = MeetingType(agenda=agenda, label='Blah', duration=15)
|
||||
meeting_type.save()
|
||||
tomorrow_weekday = (localtime(now()).weekday() + 1) % 7
|
||||
default_desk, _ = Desk.objects.get_or_create(agenda=agenda, label='Desk 1')
|
||||
time_period = TimePeriod(
|
||||
weekday=tomorrow_weekday,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(12, 0),
|
||||
desk=default_desk,
|
||||
)
|
||||
time_period.save()
|
||||
|
||||
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
|
||||
resp = app.get(api_url)
|
||||
assert len(resp.json['data']) == 8
|
||||
assert datetime_from_str(resp.json['data'][0]['datetime']).minute == 0
|
||||
assert datetime_from_str(resp.json['data'][1]['datetime']).minute == 15
|
||||
assert datetime_from_str(resp.json['data'][2]['datetime']).minute == 30
|
||||
assert datetime_from_str(resp.json['data'][3]['datetime']).minute == 45
|
||||
assert datetime_from_str(resp.json['data'][4]['datetime']).minute == 0
|
||||
assert datetime_from_str(resp.json['data'][5]['datetime']).minute == 15
|
||||
assert datetime_from_str(resp.json['data'][6]['datetime']).minute == 30
|
||||
assert datetime_from_str(resp.json['data'][7]['datetime']).minute == 45
|
||||
|
||||
# filter on minutes
|
||||
api_url = '/api/agenda/%s/meetings/%s/datetimes/?minutes=0' % (
|
||||
meeting_type.agenda.slug,
|
||||
meeting_type.slug,
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
assert len(resp.json['data']) == 2
|
||||
assert datetime_from_str(resp.json['data'][0]['datetime']).minute == 0
|
||||
assert datetime_from_str(resp.json['data'][1]['datetime']).minute == 0
|
||||
|
||||
# filter on minutes, with more choices
|
||||
api_url = '/api/agenda/%s/meetings/%s/datetimes/?minutes=0,30' % (
|
||||
meeting_type.agenda.slug,
|
||||
meeting_type.slug,
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
assert len(resp.json['data']) == 4
|
||||
assert datetime_from_str(resp.json['data'][0]['datetime']).minute == 0
|
||||
assert datetime_from_str(resp.json['data'][1]['datetime']).minute == 30
|
||||
assert datetime_from_str(resp.json['data'][2]['datetime']).minute == 0
|
||||
assert datetime_from_str(resp.json['data'][3]['datetime']).minute == 30
|
||||
|
|
|
@ -2550,3 +2550,70 @@ def test_fillslot_recurring_event_booking_forbidden(app, user):
|
|||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug))
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_class'] == 'event is recurrent'
|
||||
|
||||
|
||||
# monday 15th march at 12:00 CEST
|
||||
@pytest.mark.freeze_time('2021-03-15T12:00:00+01:00')
|
||||
def test_user_external_id(app, user):
|
||||
user_external_id = 'foobar'
|
||||
meeting_agenda_1 = Agenda.objects.create(
|
||||
label='Meeting agenda', slug='mt1', kind='meetings', minimal_booking_delay=0
|
||||
)
|
||||
desk = Desk.objects.create(agenda=meeting_agenda_1, slug='desk')
|
||||
meeting_type = MeetingType.objects.create(agenda=meeting_agenda_1, slug='mtt')
|
||||
TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(17, 00), desk=desk
|
||||
)
|
||||
meeting_agenda_2 = meeting_agenda_1.duplicate()
|
||||
meeting_agenda_2.maximal_booking_delay = 20
|
||||
meeting_agenda_2.save()
|
||||
meeting_agenda_3 = meeting_agenda_1.duplicate()
|
||||
meeting_agenda_3.maximal_booking_delay = 30
|
||||
meeting_agenda_3.save()
|
||||
|
||||
virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
||||
virtual_agenda.real_agendas.add(meeting_agenda_1, meeting_agenda_2, meeting_agenda_3)
|
||||
|
||||
meeting_agenda_1_datetimes_url = f'/api/agenda/{meeting_agenda_1.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
|
||||
meeting_agenda_2_datetimes_url = f'/api/agenda/{meeting_agenda_2.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
|
||||
meeting_agenda_3_datetimes_url = f'/api/agenda/{meeting_agenda_3.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
|
||||
virtual_agenda_datetimes_url = f'/api/agenda/{virtual_agenda.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
|
||||
|
||||
today_at_15 = localtime(now()).replace(hour=15, minute=0)
|
||||
|
||||
# check all slots are free
|
||||
resp = app.get(virtual_agenda_datetimes_url)
|
||||
assert not any(x['disabled'] for x in resp.json['data'])
|
||||
resp = app.get(meeting_agenda_1_datetimes_url)
|
||||
assert not any(x['disabled'] for x in resp.json['data'])
|
||||
resp = app.get(meeting_agenda_2_datetimes_url)
|
||||
assert not any(x['disabled'] for x in resp.json['data'])
|
||||
resp = app.get(meeting_agenda_3_datetimes_url)
|
||||
assert not any(x['disabled'] for x in resp.json['data'])
|
||||
|
||||
for agenda in virtual_agenda.get_real_agendas():
|
||||
meeting_event = Event.objects.create(
|
||||
label='Event',
|
||||
slug='meeting1',
|
||||
start_datetime=today_at_15,
|
||||
agenda=agenda,
|
||||
places=1,
|
||||
meeting_type=agenda.meetingtype_set.all()[0],
|
||||
desk=agenda.desk_set.all()[0],
|
||||
)
|
||||
Booking.objects.create(event=meeting_event, user_external_id=user_external_id)
|
||||
|
||||
# now some slots are disabled
|
||||
resp = app.get(virtual_agenda_datetimes_url)
|
||||
assert any(x['disabled'] for x in resp.json['data'])
|
||||
agenda_datetimes_url = f'/api/agenda/{agenda.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
|
||||
resp = app.get(agenda_datetimes_url)
|
||||
assert any(x['disabled'] for x in resp.json['data'])
|
||||
for other_agenda in virtual_agenda.get_real_agendas():
|
||||
if other_agenda == agenda:
|
||||
continue
|
||||
other_agenda_datetimes_url = f'/api/agenda/{other_agenda.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
|
||||
resp = app.get(other_agenda_datetimes_url)
|
||||
assert not any(x['disabled'] for x in resp.json['data'])
|
||||
|
||||
meeting_event.delete()
|
||||
|
|
|
@ -66,6 +66,7 @@ def test_agendas_api(app):
|
|||
'minimal_booking_delay': 1,
|
||||
'minimal_booking_delay_in_working_days': False,
|
||||
'maximal_booking_delay': 56,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': 'Edit',
|
||||
'view_role': None,
|
||||
'category': 'category-a',
|
||||
|
@ -85,6 +86,7 @@ def test_agendas_api(app):
|
|||
'minimal_booking_delay': 1,
|
||||
'minimal_booking_delay_in_working_days': False,
|
||||
'maximal_booking_delay': 56,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': None,
|
||||
'view_role': None,
|
||||
'category': 'category-a',
|
||||
|
@ -104,6 +106,7 @@ def test_agendas_api(app):
|
|||
'minimal_booking_delay': 1,
|
||||
'minimal_booking_delay_in_working_days': False,
|
||||
'maximal_booking_delay': 56,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': None,
|
||||
'view_role': None,
|
||||
'category': None,
|
||||
|
@ -121,6 +124,7 @@ def test_agendas_api(app):
|
|||
'slug': 'foo-bar-meeting',
|
||||
'minimal_booking_delay': 1,
|
||||
'maximal_booking_delay': 56,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': None,
|
||||
'view_role': 'View',
|
||||
'category': 'category-b',
|
||||
|
@ -144,6 +148,7 @@ def test_agendas_api(app):
|
|||
'slug': 'foo-bar-meeting-2',
|
||||
'minimal_booking_delay': 1,
|
||||
'maximal_booking_delay': 56,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': None,
|
||||
'view_role': None,
|
||||
'category': None,
|
||||
|
@ -164,6 +169,7 @@ def test_agendas_api(app):
|
|||
'slug': 'virtual-agenda',
|
||||
'minimal_booking_delay': 1,
|
||||
'maximal_booking_delay': 56,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': 'Edit',
|
||||
'view_role': 'View',
|
||||
'category': None,
|
||||
|
@ -411,6 +417,7 @@ def test_virtual_agenda_detail(app, virtual_meetings_agenda):
|
|||
'slug': 'virtual-agenda',
|
||||
'minimal_booking_delay': None,
|
||||
'maximal_booking_delay': None,
|
||||
'minimal_booking_time': '00:00:00',
|
||||
'edit_role': None,
|
||||
'view_role': None,
|
||||
'category': None,
|
||||
|
@ -457,25 +464,25 @@ def test_agenda_api_delete_busy(app, user):
|
|||
assert not Agenda.objects.exists()
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-07-09')
|
||||
@pytest.mark.freeze_time('2021-07-09T08:00:00.0+02:00')
|
||||
def test_add_agenda(app, user, settings):
|
||||
events_type = EventsType.objects.create(label='Type A')
|
||||
category_a = Category.objects.create(label='Category A')
|
||||
api_url = '/api/agenda/'
|
||||
|
||||
# no authentication
|
||||
resp = app.post(api_url, status=401)
|
||||
resp = app.post_json(api_url, status=401)
|
||||
assert resp.json['detail'] == 'Authentication credentials were not provided.'
|
||||
|
||||
# wrong password
|
||||
app.authorization = ('Basic', ('john.doe', 'wrong'))
|
||||
resp = app.post(api_url, status=401)
|
||||
resp = app.post_json(api_url, status=401)
|
||||
assert resp.json['detail'] == 'Invalid username/password.'
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# missing fields
|
||||
resp = app.post(api_url, status=400)
|
||||
resp = app.post_json(api_url, status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['errors'] == {'label': ['This field is required.'], 'slug': ['This field is required.']}
|
||||
|
||||
|
@ -492,7 +499,7 @@ def test_add_agenda(app, user, settings):
|
|||
'category': 'oups',
|
||||
'events_type': 'oups',
|
||||
}
|
||||
resp = app.post(api_url, params=params, status=400)
|
||||
resp = app.post_json(api_url, params=params, status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['errors'] == {
|
||||
'kind': ['"oups" is not a valid choice.'],
|
||||
|
@ -512,7 +519,7 @@ def test_add_agenda(app, user, settings):
|
|||
'label': 'foo',
|
||||
'slug': meeting_agenda.slug,
|
||||
}
|
||||
resp = app.post(api_url, params=params, status=400)
|
||||
resp = app.post_json(api_url, params=params, status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['errors'] == {'slug': ['agenda with this Identifier already exists.']}
|
||||
|
||||
|
@ -523,7 +530,7 @@ def test_add_agenda(app, user, settings):
|
|||
'kind': 'meetings',
|
||||
'minimal_booking_delay_in_working_days': True,
|
||||
}
|
||||
resp = app.post(api_url, params=params, status=400)
|
||||
resp = app.post_json(api_url, params=params, status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['errors'] == {
|
||||
'minimal_booking_delay_in_working_days': ['Option not available on meetings agenda']
|
||||
|
@ -534,7 +541,7 @@ def test_add_agenda(app, user, settings):
|
|||
'kind': 'meetings',
|
||||
'events_type': 'type-a',
|
||||
}
|
||||
resp = app.post(api_url, params=params, status=400)
|
||||
resp = app.post_json(api_url, params=params, status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['errors'] == {'events_type': ['Option not available on meetings agenda']}
|
||||
|
||||
|
@ -543,7 +550,7 @@ def test_add_agenda(app, user, settings):
|
|||
'label': 'My Agenda',
|
||||
'slug': 'my-agenda',
|
||||
}
|
||||
resp = app.post(api_url, params=params)
|
||||
resp = app.post_json(api_url, params=params)
|
||||
assert not resp.json['err']
|
||||
assert len(resp.json['data']) == 1
|
||||
agenda = Agenda.objects.get(slug='my-agenda')
|
||||
|
@ -567,11 +574,14 @@ def test_add_agenda(app, user, settings):
|
|||
'mark_event_checked_auto': True,
|
||||
'disable_check_update': False,
|
||||
}
|
||||
resp = app.post(api_url, params=params)
|
||||
resp = app.post_json(api_url, params=params)
|
||||
assert not resp.json['err']
|
||||
assert len(resp.json['data']) == 1
|
||||
agenda = Agenda.objects.get(slug='foo-meetings')
|
||||
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 10)
|
||||
assert agenda.min_booking_datetime == localtime(now()).replace(
|
||||
day=10, hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
assert agenda.minimal_booking_time == datetime.time(0)
|
||||
assert agenda.edit_role == edit_group
|
||||
assert agenda.view_role == view_group
|
||||
assert agenda.category == category_a
|
||||
|
@ -587,6 +597,7 @@ def test_add_agenda(app, user, settings):
|
|||
'minimal_booking_delay': 1,
|
||||
'minimal_booking_delay_in_working_days': True,
|
||||
'maximal_booking_delay': 3,
|
||||
'minimal_booking_time': None,
|
||||
'anonymize_delay': 30,
|
||||
'edit_role': 'Edit',
|
||||
'view_role': 'View',
|
||||
|
@ -596,13 +607,14 @@ def test_add_agenda(app, user, settings):
|
|||
'disable_check_update': True,
|
||||
'booking_check_filters': 'foo,bar,baz',
|
||||
}
|
||||
resp = app.post(api_url, params=params)
|
||||
resp = app.post_json(api_url, params=params)
|
||||
assert not resp.json['err']
|
||||
assert len(resp.json['data']) == 1
|
||||
agenda = Agenda.objects.get(slug='foo-events')
|
||||
assert agenda.edit_role == edit_group
|
||||
assert agenda.view_role == view_group
|
||||
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 12)
|
||||
assert agenda.min_booking_datetime == localtime(now()).replace(day=12)
|
||||
assert agenda.minimal_booking_time is None
|
||||
assert agenda.category == category_a
|
||||
assert agenda.events_type == events_type
|
||||
assert agenda.mark_event_checked_auto is False
|
||||
|
|
|
@ -689,6 +689,14 @@ def test_options_agenda_booking_check_options(app, admin_user):
|
|||
agenda.refresh_from_db()
|
||||
assert agenda.disable_check_update is True
|
||||
|
||||
# check enable check future events
|
||||
assert agenda.enable_check_for_future_events is False
|
||||
resp = app.get(url)
|
||||
resp.form['enable_check_for_future_events'] = True
|
||||
resp = resp.form.submit()
|
||||
agenda.refresh_from_db()
|
||||
assert agenda.enable_check_for_future_events is True
|
||||
|
||||
# check kind
|
||||
agenda.kind = 'meetings'
|
||||
agenda.save()
|
||||
|
|
|
@ -1504,14 +1504,26 @@ def test_event_check(app, admin_user):
|
|||
login(app)
|
||||
|
||||
# event not in past
|
||||
assert agenda.enable_check_for_future_events is False
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) not in resp
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=404)
|
||||
|
||||
# not in past, but check for future events is enabled
|
||||
agenda.enable_check_for_future_events = True
|
||||
agenda.save()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) in resp
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=200)
|
||||
|
||||
# event is today
|
||||
agenda.enable_check_for_future_events = False
|
||||
agenda.save()
|
||||
event.start_datetime = localtime(now()).replace(hour=22, minute=0) # it's ok all the day
|
||||
event.save()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) in resp
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=200)
|
||||
|
||||
# unknown agenda
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (0, event.pk), status=404)
|
||||
|
@ -1608,20 +1620,8 @@ def test_event_check(app, admin_user):
|
|||
assert '<b>Subscription 14</b> Foo Bar' in resp
|
||||
assert '<b>User Waiting</b> Foo Bar' in resp
|
||||
|
||||
# cancelled booking
|
||||
token = resp.context['csrf_token']
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking6.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking6.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# booking in waiting list
|
||||
token = resp.context['csrf_token']
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking7.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
|
@ -1740,6 +1740,27 @@ def test_event_checked(app, admin_user):
|
|||
assert '<span class="meta meta-error">Absents 4</span>' in resp
|
||||
assert 'meta meta-disabled' not in resp
|
||||
|
||||
# event not in past
|
||||
agenda.disable_check_update = False
|
||||
agenda.save()
|
||||
assert agenda.enable_check_for_future_events is False
|
||||
event.start_datetime = now() + datetime.timedelta(days=1)
|
||||
event.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# not in past, but check for future events is enabled
|
||||
agenda.enable_check_for_future_events = True
|
||||
agenda.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_event_check_filters(check_types, app, admin_user):
|
||||
|
@ -2179,12 +2200,32 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
)
|
||||
assert agenda.mark_event_checked_auto is False
|
||||
|
||||
def _test_reset():
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
).follow()
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) not in resp
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is None
|
||||
assert booking.user_check_type_slug is None
|
||||
assert booking.user_check_type_label is None
|
||||
secondary_booking.refresh_from_db()
|
||||
assert secondary_booking.user_was_present is None
|
||||
assert secondary_booking.user_check_type_slug is None
|
||||
assert secondary_booking.user_check_type_label is None
|
||||
event.refresh_from_db()
|
||||
assert event.checked is False
|
||||
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == '-'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 0
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) not in resp
|
||||
|
||||
# set as present
|
||||
token = resp.context['csrf_token']
|
||||
|
@ -2195,6 +2236,9 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) in resp
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is True
|
||||
assert booking.user_check_type_slug is None
|
||||
|
@ -2206,6 +2250,9 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
event.refresh_from_db()
|
||||
assert event.checked is False
|
||||
|
||||
# reset
|
||||
_test_reset()
|
||||
|
||||
agenda.mark_event_checked_auto = True
|
||||
agenda.save()
|
||||
|
||||
|
@ -2235,6 +2282,9 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
assert len(resp.pyquery.find('td.booking-actions form.absence select')) == 1
|
||||
assert len(resp.pyquery.find('td.booking-actions form.presence select')) == 0
|
||||
|
||||
# reset
|
||||
_test_reset()
|
||||
|
||||
# set as absent with check_type
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
|
@ -2249,6 +2299,11 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
assert secondary_booking.user_was_present is False
|
||||
assert secondary_booking.user_check_type_slug == 'foo-reason'
|
||||
assert secondary_booking.user_check_type_label == 'Foo reason'
|
||||
event.refresh_from_db()
|
||||
assert event.checked is True
|
||||
|
||||
# reset
|
||||
_test_reset()
|
||||
|
||||
# set as present without check_type
|
||||
resp = app.post(
|
||||
|
@ -2281,6 +2336,9 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
assert len(resp.pyquery.find('td.booking-actions form.absence select')) == 1
|
||||
assert len(resp.pyquery.find('td.booking-actions form.presence select')) == 1
|
||||
|
||||
# reset
|
||||
_test_reset()
|
||||
|
||||
# set as present with check_type
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
|
@ -2295,14 +2353,59 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
assert secondary_booking.user_was_present is True
|
||||
assert secondary_booking.user_check_type_slug == 'bar-reason'
|
||||
assert secondary_booking.user_check_type_label == 'Bar reason'
|
||||
event.refresh_from_db()
|
||||
assert event.checked is True
|
||||
|
||||
# mark the event as checked
|
||||
event.checked = True
|
||||
event.save()
|
||||
# event is checked
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
resp = app.post(
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) in resp
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
|
||||
# event not in past
|
||||
assert agenda.enable_check_for_future_events is False
|
||||
event.start_datetime = now() + datetime.timedelta(days=1)
|
||||
event.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# not in past, but check for future events is enabled
|
||||
agenda.enable_check_for_future_events = True
|
||||
agenda.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
|
@ -2319,6 +2422,50 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) not in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) not in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) not in resp
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_event_check_cancelled_booking(check_types, app, admin_user):
|
||||
check_types.return_value = []
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
waiting_list_places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42')
|
||||
secondary_booking = Booking.objects.create(
|
||||
event=event, user_first_name='User', user_last_name='42', primary_booking=booking
|
||||
)
|
||||
booking.cancel()
|
||||
Booking.objects.create(event=event, user_first_name='User', user_last_name='43')
|
||||
|
||||
# no suscription: cancelled bookings are not displayed
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert len(resp.pyquery.find('td.booking-status')) == 1
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) not in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) not in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) not in resp
|
||||
token = resp.context['csrf_token']
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
|
@ -2330,6 +2477,52 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
status=404,
|
||||
)
|
||||
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='user:1',
|
||||
user_first_name='Subscription',
|
||||
user_last_name='42',
|
||||
date_start=now(),
|
||||
date_end=now() + datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert len(resp.pyquery.find('td.booking-status')) == 2
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == '(Cancelled)'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 0
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) not in resp
|
||||
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
).follow()
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
|
||||
booking.refresh_from_db()
|
||||
assert booking.cancellation_datetime is None
|
||||
assert booking.user_was_present is True
|
||||
secondary_booking.refresh_from_db()
|
||||
assert secondary_booking.cancellation_datetime is None
|
||||
assert secondary_booking.user_was_present is True
|
||||
|
||||
booking.cancel()
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
).follow()
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Absence'
|
||||
booking.refresh_from_db()
|
||||
assert booking.cancellation_datetime is None
|
||||
assert booking.user_was_present is False
|
||||
secondary_booking.refresh_from_db()
|
||||
assert secondary_booking.cancellation_datetime is None
|
||||
assert secondary_booking.user_was_present is False
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_event_check_booking_ajax(check_types, app, admin_user):
|
||||
|
@ -2358,7 +2551,7 @@ def test_event_check_booking_ajax(check_types, app, admin_user):
|
|||
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
|
||||
)
|
||||
assert '<tr>' not in resp # because this is a fragment
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present\n \n (Bar reason)'
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present\n \n (Bar reason)'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
|
||||
assert '<option value="bar-reason" selected>Bar reason</option>' in resp
|
||||
|
@ -2370,7 +2563,284 @@ def test_event_check_booking_ajax(check_types, app, admin_user):
|
|||
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
|
||||
)
|
||||
assert '<tr>' not in resp # because this is a fragment
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent\n \n (Foo reason)'
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent\n \n (Foo reason)'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text.startswith('Absence')
|
||||
assert '<option value="foo-reason" selected>Foo reason</option>' in resp
|
||||
|
||||
# reset
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
|
||||
)
|
||||
assert '<tr>' not in resp # because this is a fragment
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == '-'
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_event_check_subscription(check_types, app, admin_user):
|
||||
check_types.return_value = [
|
||||
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
||||
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
||||
]
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
waiting_list_places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
|
||||
subscription = Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='user:1',
|
||||
user_first_name='Subscription',
|
||||
user_last_name='42',
|
||||
user_email='foo@bar.com',
|
||||
user_phone_number='06',
|
||||
extra_data={'foo': 'bar'},
|
||||
date_start=now() - datetime.timedelta(days=1),
|
||||
date_end=now(),
|
||||
)
|
||||
|
||||
# existing booking: no check for subscription
|
||||
booking = Booking.objects.create(
|
||||
event=event, user_first_name='User', user_last_name='42', user_external_id='user:1'
|
||||
)
|
||||
booking2 = Booking.objects.create(
|
||||
event=event, user_first_name='User', user_last_name='42', user_external_id='user:1'
|
||||
)
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert (
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
|
||||
)
|
||||
assert (
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
|
||||
)
|
||||
token = resp.context['csrf_token']
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
booking2.delete()
|
||||
booking.cancel()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert (
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
|
||||
)
|
||||
assert (
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
booking.delete()
|
||||
|
||||
# set as present
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
|
||||
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'},
|
||||
)
|
||||
booking = Booking.objects.latest('pk')
|
||||
assert booking.user_was_present is True
|
||||
assert booking.user_check_type_slug == 'bar-reason'
|
||||
assert booking.user_check_type_label == 'Bar reason'
|
||||
assert booking.event == event
|
||||
assert booking.user_external_id == subscription.user_external_id
|
||||
assert booking.user_first_name == subscription.user_first_name
|
||||
assert booking.user_last_name == subscription.user_last_name
|
||||
assert booking.user_email == subscription.user_email
|
||||
assert booking.user_phone_number == subscription.user_phone_number
|
||||
assert booking.extra_data == subscription.extra_data
|
||||
|
||||
booking.delete()
|
||||
|
||||
# set as absent
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
|
||||
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'},
|
||||
)
|
||||
booking = Booking.objects.latest('pk')
|
||||
assert booking.user_was_present is False
|
||||
assert booking.user_check_type_slug == 'foo-reason'
|
||||
assert booking.user_check_type_label == 'Foo reason'
|
||||
assert booking.event == event
|
||||
assert booking.user_external_id == subscription.user_external_id
|
||||
assert booking.user_first_name == subscription.user_first_name
|
||||
assert booking.user_last_name == subscription.user_last_name
|
||||
assert booking.user_email == subscription.user_email
|
||||
assert booking.user_phone_number == subscription.user_phone_number
|
||||
assert booking.extra_data == subscription.extra_data
|
||||
|
||||
booking.delete()
|
||||
|
||||
# mark the event as checked
|
||||
assert agenda.disable_check_update is False
|
||||
event.checked = True
|
||||
event.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
Booking.objects.all().delete()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
Booking.objects.all().delete()
|
||||
|
||||
agenda.disable_check_update = True
|
||||
agenda.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# check subscription dates
|
||||
agenda.disable_check_update = False
|
||||
agenda.save()
|
||||
subscription.date_start = now() - datetime.timedelta(days=2)
|
||||
subscription.date_end = now() - datetime.timedelta(days=1)
|
||||
subscription.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
subscription.date_start = now()
|
||||
subscription.date_end = now() + datetime.timedelta(days=1)
|
||||
subscription.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# event not in past
|
||||
subscription.date_end = now() + datetime.timedelta(days=2)
|
||||
subscription.save()
|
||||
assert agenda.enable_check_for_future_events is False
|
||||
event.start_datetime = now() + datetime.timedelta(days=1)
|
||||
event.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# not in past, but check for future events is enabled
|
||||
agenda.enable_check_for_future_events = True
|
||||
agenda.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
Booking.objects.all().delete()
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_event_check_subscription_ajax(check_types, app, admin_user):
|
||||
check_types.return_value = [
|
||||
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
||||
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
||||
]
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
waiting_list_places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
subscription = Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='user:1',
|
||||
user_first_name='Subscription',
|
||||
user_last_name='42',
|
||||
date_start=now() - datetime.timedelta(days=1),
|
||||
date_end=now(),
|
||||
)
|
||||
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
token = resp.context['csrf_token']
|
||||
|
||||
# set as present
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'},
|
||||
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
|
||||
)
|
||||
assert '<tr>' not in resp # because this is a fragment
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present\n \n (Bar reason)'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
|
||||
assert '<option value="bar-reason" selected>Bar reason</option>' in resp
|
||||
|
||||
Booking.objects.all().delete()
|
||||
|
||||
# set as absent
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'},
|
||||
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
|
||||
)
|
||||
assert '<tr>' not in resp # because this is a fragment
|
||||
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent\n \n (Foo reason)'
|
||||
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text.startswith('Absence')
|
||||
assert '<option value="foo-reason" selected>Foo reason</option>' in resp
|
||||
|
@ -2517,6 +2987,37 @@ def test_event_check_all_bookings(check_types, app, admin_user):
|
|||
status=404,
|
||||
)
|
||||
|
||||
# event not in past
|
||||
agenda.disable_check_update = False
|
||||
agenda.save()
|
||||
assert agenda.enable_check_for_future_events is False
|
||||
event.start_datetime = now() + datetime.timedelta(days=1)
|
||||
event.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# not in past, but check for future events is enabled
|
||||
agenda.enable_check_for_future_events = True
|
||||
agenda.save()
|
||||
app.post(
|
||||
'/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=302,
|
||||
)
|
||||
|
||||
|
||||
def test_event_check_primary_booking(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
|
|
|
@ -36,7 +36,7 @@ from chrono.agendas.models import (
|
|||
UnavailabilityCalendar,
|
||||
VirtualMember,
|
||||
)
|
||||
from chrono.utils.timezone import localtime, make_aware, now
|
||||
from chrono.utils.timezone import localtime, make_aware, make_naive, now
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -268,6 +268,158 @@ def test_agenda_maximal_booking_delay(freezer):
|
|||
assert agenda.max_booking_datetime == make_aware(datetime.datetime(2021, 11, 2, 0, 0, 0))
|
||||
|
||||
|
||||
def delay_parameter_to_label(argvalue):
|
||||
if isinstance(argvalue, str):
|
||||
return argvalue
|
||||
return repr(argvalue)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'current_time,min_booking_datetime',
|
||||
[
|
||||
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
|
||||
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 7)),
|
||||
# summer DST change on sunday 28th
|
||||
('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 29, 1, 30)),
|
||||
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 2, 30)),
|
||||
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 3, 30)),
|
||||
('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)),
|
||||
('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)),
|
||||
# winter DST change on sunday 31th
|
||||
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 1, 30)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
|
||||
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 1, 30)),
|
||||
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 2, 30)),
|
||||
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 2, 30)),
|
||||
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 3, 30)),
|
||||
],
|
||||
ids=delay_parameter_to_label,
|
||||
)
|
||||
def test_agenda_minimal_booking_delay_no_minimal_booking_time(freezer, current_time, min_booking_datetime):
|
||||
freezer.move_to(current_time)
|
||||
agenda = Agenda.objects.create(label='Agenda', minimal_booking_delay=4, minimal_booking_time=None)
|
||||
assert make_naive(agenda.min_booking_datetime) == min_booking_datetime
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'current_time,min_booking_datetime',
|
||||
[
|
||||
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 16, 8)),
|
||||
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 24, 7)),
|
||||
# summer DST change on sunday 28th
|
||||
('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 31, 1, 30)),
|
||||
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 31, 2, 30)),
|
||||
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 31, 3, 30)),
|
||||
('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)),
|
||||
('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)),
|
||||
# winter DST change on sunday 31th
|
||||
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 5, 1, 30)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 5, 2, 30)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 5, 2, 30)),
|
||||
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 5, 1, 30)),
|
||||
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 5, 2, 30)),
|
||||
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 5, 2, 30)),
|
||||
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 5, 3, 30)),
|
||||
],
|
||||
ids=delay_parameter_to_label,
|
||||
)
|
||||
def test_agenda_minimal_booking_delay_in_working_days_no_minimal_booking_time(
|
||||
settings, freezer, current_time, min_booking_datetime
|
||||
):
|
||||
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
|
||||
freezer.move_to(current_time)
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda',
|
||||
minimal_booking_delay=4,
|
||||
minimal_booking_time=None,
|
||||
minimal_booking_delay_in_working_days=True,
|
||||
)
|
||||
assert make_naive(agenda.min_booking_datetime) == min_booking_datetime
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'current_time,min_booking_datetime',
|
||||
[
|
||||
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
|
||||
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 8)),
|
||||
# summer DST change on sunday
|
||||
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
|
||||
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
|
||||
# winter DST change on sunday
|
||||
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
|
||||
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
],
|
||||
ids=delay_parameter_to_label,
|
||||
)
|
||||
def test_agenda_minimal_booking_delay_minimal_booking_time_at_8(freezer, current_time, min_booking_datetime):
|
||||
freezer.move_to(current_time)
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', minimal_booking_delay=4, minimal_booking_time=datetime.time(8)
|
||||
)
|
||||
assert make_naive(agenda.min_booking_datetime) == min_booking_datetime
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'current_time,max_booking_datetime',
|
||||
[
|
||||
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
|
||||
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 7)),
|
||||
# summer DST change on sunday 28th
|
||||
('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 29, 1, 30)),
|
||||
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 2, 30)),
|
||||
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 3, 30)),
|
||||
('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)),
|
||||
('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)),
|
||||
# winter DST change on sunday 31th
|
||||
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 1, 30)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)),
|
||||
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 1, 30)),
|
||||
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 2, 30)),
|
||||
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 2, 30)),
|
||||
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 3, 30)),
|
||||
],
|
||||
ids=delay_parameter_to_label,
|
||||
)
|
||||
def test_agenda_maximal_booking_delay_no_minimal_booking_time(freezer, current_time, max_booking_datetime):
|
||||
freezer.move_to(current_time)
|
||||
agenda = Agenda.objects.create(label='Agenda', maximal_booking_delay=4, minimal_booking_time=None)
|
||||
assert make_naive(agenda.max_booking_datetime) == max_booking_datetime
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'current_time,max_booking_datetime',
|
||||
[
|
||||
('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)),
|
||||
('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 8)),
|
||||
# summer DST change on sunday
|
||||
('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
|
||||
('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 8)),
|
||||
# winter DST change on sunday
|
||||
('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
|
||||
('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)),
|
||||
('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 8)),
|
||||
],
|
||||
ids=delay_parameter_to_label,
|
||||
)
|
||||
def test_agenda_maximal_booking_delay_minimal_booking_time_at_8(freezer, current_time, max_booking_datetime):
|
||||
freezer.move_to(current_time)
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', maximal_booking_delay=4, minimal_booking_time=datetime.time(8)
|
||||
)
|
||||
assert make_naive(agenda.max_booking_datetime) == max_booking_datetime
|
||||
|
||||
|
||||
@pytest.mark.parametrize('with_prefetch', [True, False])
|
||||
def test_agenda_is_available_for_simple_management(settings, with_prefetch):
|
||||
settings.EXCEPTIONS_SOURCES = {
|
||||
|
|
Loading…
Reference in New Issue