manager: partial bookings, allow user check without booking (#80369)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
28c3641d50
commit
ec86a9bbcc
|
@ -2964,6 +2964,12 @@ class Booking(models.Model):
|
|||
# else take next_slot
|
||||
return next_slot
|
||||
|
||||
def get_partial_bookings_check_url(self, agenda, event=None):
|
||||
return reverse(
|
||||
'chrono-manager-partial-booking-check',
|
||||
kwargs={'pk': agenda.pk, 'booking_pk': self.pk},
|
||||
)
|
||||
|
||||
|
||||
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])
|
||||
|
||||
|
@ -3993,6 +3999,12 @@ class Subscription(models.Model):
|
|||
except (VariableDoesNotExist, TemplateSyntaxError):
|
||||
return
|
||||
|
||||
def get_partial_bookings_check_url(self, agenda, event):
|
||||
return reverse(
|
||||
'chrono-manager-partial-booking-subscription-check',
|
||||
kwargs={'pk': agenda.pk, 'event_pk': event.pk, 'subscription_pk': self.pk},
|
||||
)
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
user_external_id = models.CharField(max_length=250, unique=True)
|
||||
|
|
|
@ -609,8 +609,9 @@ class PartialBookingCheckForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
agenda = kwargs.pop('agenda')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.check_types = get_agenda_check_types(self.instance.event.agenda)
|
||||
self.check_types = get_agenda_check_types(agenda)
|
||||
absence_check_types = [(ct.slug, ct.label) for ct in self.check_types if ct.kind == 'absence']
|
||||
presence_check_types = [(ct.slug, ct.label) for ct in self.check_types if ct.kind == 'presence']
|
||||
|
||||
|
@ -626,6 +627,12 @@ class PartialBookingCheckForm(forms.ModelForm):
|
|||
else:
|
||||
del self.fields['absence_check_type']
|
||||
|
||||
if not self.instance.start_time:
|
||||
self.fields['user_check_start_time'].widget = widgets.TimeWidget(step=60)
|
||||
self.fields['user_check_end_time'].widget = widgets.TimeWidget(step=60)
|
||||
self.fields['user_was_present'].widget.choices = ((None, _('Not checked')), (True, _('Present')))
|
||||
self.fields.pop('absence_check_type', None)
|
||||
|
||||
def clean(self):
|
||||
if self.cleaned_data['user_was_present'] is not None:
|
||||
kind = 'presence' if self.cleaned_data['user_was_present'] else 'absence'
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
<section class="partial-booking--registrant">
|
||||
{% spaceless %}
|
||||
<h3 class="registrant--name">
|
||||
{% if user.bookings and allow_check %}
|
||||
{% if allow_check %}
|
||||
<a
|
||||
rel="popup"
|
||||
href="{% url 'chrono-manager-partial-booking-check' pk=agenda.pk booking_pk=user.bookings.0.pk %}"
|
||||
href="{{ user.check_url }}"
|
||||
>{{ user.name }}</a>
|
||||
{% else %}
|
||||
<span>{{ user.name }}</span>
|
||||
|
@ -63,15 +63,17 @@
|
|||
<div class="registrant--datas">
|
||||
<div class="registrant--bar-container">
|
||||
{% for booking in user.bookings %}
|
||||
<p
|
||||
class="registrant--bar clearfix booking"
|
||||
title="{% trans "Booked period" %}"
|
||||
style="left: {{ booking.css_left }}%; width: {{ booking.css_width }}%;"
|
||||
>
|
||||
<strong class="sr-only">{% trans "Booked period:" %}</strong>
|
||||
<time class="start-time" datetime="{{ booking.start_time|time:"H:i" }}">{{ booking.start_time|time:"H:i" }}</time>
|
||||
<time class="end-time" datetime="{{ booking.end_time|time:"H:i" }}">{{ booking.end_time|time:"H:i" }}</time>
|
||||
</p>
|
||||
{% if booking.start_time %}
|
||||
<p
|
||||
class="registrant--bar clearfix booking"
|
||||
title="{% trans "Booked period" %}"
|
||||
style="left: {{ booking.css_left }}%; width: {{ booking.css_width }}%;"
|
||||
>
|
||||
<strong class="sr-only">{% trans "Booked period:" %}</strong>
|
||||
<time class="start-time" datetime="{{ booking.start_time|time:"H:i" }}">{{ booking.start_time|time:"H:i" }}</time>
|
||||
<time class="end-time" datetime="{{ booking.end_time|time:"H:i" }}">{{ booking.end_time|time:"H:i" }}</time>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if user.bookings %}
|
||||
|
|
|
@ -439,6 +439,11 @@ urlpatterns = [
|
|||
views.partial_booking_check_view,
|
||||
name='chrono-manager-partial-booking-check',
|
||||
),
|
||||
path(
|
||||
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/check/<int:event_pk>',
|
||||
views.partial_booking_subscription_check_view,
|
||||
name='chrono-manager-partial-booking-subscription-check',
|
||||
),
|
||||
path(
|
||||
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/extra-user-block',
|
||||
views.subscription_extra_user_block,
|
||||
|
|
|
@ -1652,8 +1652,9 @@ class AgendaDayView(EventChecksMixin, AgendaDateView, DayArchiveView):
|
|||
|
||||
bookings = [x for x in context['results'] if x.kind == 'booking']
|
||||
for booking in bookings:
|
||||
booking.css_left = get_time_ratio(booking.start_time, start_time)
|
||||
booking.css_width = get_time_ratio(booking.end_time, booking.start_time)
|
||||
if booking.start_time:
|
||||
booking.css_left = get_time_ratio(booking.start_time, start_time)
|
||||
booking.css_width = get_time_ratio(booking.end_time, booking.start_time)
|
||||
|
||||
if booking.user_was_present is not None:
|
||||
booking.check_css_class = 'present' if booking.user_was_present else 'absent'
|
||||
|
@ -1672,7 +1673,12 @@ class AgendaDayView(EventChecksMixin, AgendaDateView, DayArchiveView):
|
|||
users_info = {}
|
||||
for result in context['results']:
|
||||
user_info = users_info.setdefault(
|
||||
result.user_external_id, {'name': result.get_user_block(), 'bookings': []}
|
||||
result.user_external_id,
|
||||
{
|
||||
'name': result.get_user_block(),
|
||||
'check_url': result.get_partial_bookings_check_url(self.agenda, event),
|
||||
'bookings': [],
|
||||
},
|
||||
)
|
||||
if result.kind != 'booking':
|
||||
continue
|
||||
|
@ -4486,20 +4492,12 @@ class SharedCustodySettingsView(UpdateView):
|
|||
shared_custody_settings = SharedCustodySettingsView.as_view()
|
||||
|
||||
|
||||
class PartialBookingCheckView(ViewableAgendaMixin, UpdateView):
|
||||
class PartialBookingCheckMixin(ViewableAgendaMixin):
|
||||
template_name = 'chrono/manager_partial_booking_form.html'
|
||||
model = Booking
|
||||
pk_url_kwarg = 'booking_pk'
|
||||
form_class = PartialBookingCheckForm
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
qs = super().get_queryset()
|
||||
return qs.filter(
|
||||
Q(event__checked=False) | Q(event__agenda__disable_check_update=False),
|
||||
Q(event__start_datetime__date__lte=localtime().date())
|
||||
| Q(event__agenda__enable_check_for_future_events=True),
|
||||
event__check_locked=False,
|
||||
)
|
||||
def get_object(self):
|
||||
return self.get_booking(**self.kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
date = self.object.event.start_datetime
|
||||
|
@ -4508,10 +4506,30 @@ class PartialBookingCheckView(ViewableAgendaMixin, UpdateView):
|
|||
kwargs={'pk': self.agenda.pk, 'year': date.year, 'month': date.month, 'day': date.day},
|
||||
)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['agenda'] = self.agenda
|
||||
return kwargs
|
||||
|
||||
|
||||
class PartialBookingCheckView(PartialBookingCheckMixin, BookingCheckMixin, UpdateView):
|
||||
pass
|
||||
|
||||
|
||||
partial_booking_check_view = PartialBookingCheckView.as_view()
|
||||
|
||||
|
||||
class PartialBookingSubscriptionCheckView(PartialBookingCheckMixin, SubscriptionCheckMixin, UpdateView):
|
||||
def get_object(self):
|
||||
if self.request.method == 'POST':
|
||||
return super().get_object()
|
||||
else:
|
||||
return Booking()
|
||||
|
||||
|
||||
partial_booking_subscription_check_view = PartialBookingSubscriptionCheckView.as_view()
|
||||
|
||||
|
||||
def menu_json(request):
|
||||
if not request.user.is_staff:
|
||||
homepage_view = HomepageView(request=request)
|
||||
|
|
|
@ -404,6 +404,75 @@ def test_manager_partial_bookings_check_future_events(app, admin_user, freezer):
|
|||
app.get('/manage/agendas/%s/bookings/%s/check' % (agenda.pk, booking.pk), status=200)
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_manager_partial_bookings_check_subscription(check_types, app, admin_user):
|
||||
check_types.return_value = []
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
||||
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
||||
event = Event.objects.create(
|
||||
label='Event',
|
||||
start_datetime=start_datetime,
|
||||
end_time=datetime.time(18, 00),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
recurrence_days=list(range(1, 8)),
|
||||
recurrence_end_date=start_datetime + datetime.timedelta(days=30),
|
||||
)
|
||||
event.create_all_recurrences()
|
||||
Subscription.objects.create(
|
||||
agenda=agenda,
|
||||
user_external_id='xxx',
|
||||
user_first_name='Jane',
|
||||
user_last_name='Doe',
|
||||
date_start=event.start_datetime,
|
||||
date_end=event.start_datetime + datetime.timedelta(days=2),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
today = start_datetime.date()
|
||||
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
||||
|
||||
assert len(resp.pyquery('.registrant--bar')) == 0
|
||||
|
||||
resp = resp.click('Jane Doe')
|
||||
assert 'Fill with booking start time' not in resp.text
|
||||
assert 'absence_check_type' not in resp.form.fields
|
||||
assert resp.form['user_was_present'].options == [
|
||||
('', True, None),
|
||||
('True', False, None),
|
||||
] # no 'False' option
|
||||
assert not Booking.objects.exists()
|
||||
|
||||
resp.form['user_check_start_time'] = '10:00'
|
||||
resp.form['user_check_end_time'] = '16:00'
|
||||
resp.form['user_was_present'] = 'True'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert len(resp.pyquery('.registrant--bar')) == 2
|
||||
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
|
||||
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
|
||||
assert resp.pyquery('.registrant--bar.check time')[0].text == '10:00'
|
||||
assert resp.pyquery('.registrant--bar.check time')[1].text == '16:00'
|
||||
|
||||
booking = Booking.objects.get()
|
||||
assert booking.user_external_id == 'xxx'
|
||||
assert booking.user_first_name == 'Jane'
|
||||
assert booking.user_last_name == 'Doe'
|
||||
|
||||
resp = resp.click('Jane Doe')
|
||||
assert 'Fill with booking start time' not in resp.text
|
||||
assert 'absence_check_type' not in resp.form.fields
|
||||
assert resp.form['user_was_present'].options == [
|
||||
('', False, None),
|
||||
('True', True, None),
|
||||
] # no 'False' option
|
||||
|
||||
resp.form['user_was_present'] = ''
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert len(resp.pyquery('.registrant--bar')) == 0
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_manager_partial_bookings_check_filters(check_types, app, admin_user):
|
||||
check_types.return_value = [
|
||||
|
@ -481,9 +550,8 @@ def test_manager_partial_bookings_check_filters(check_types, app, admin_user):
|
|||
'User Present Vegan',
|
||||
]
|
||||
|
||||
# one registrant has not booked, no bar is shown and no booking check link
|
||||
# one registrant has not booked, no bar is shown
|
||||
assert len(resp.pyquery('.registrant--bar.booking')) == 3
|
||||
assert len(resp.pyquery('.registrant--name a')) == 3
|
||||
|
||||
resp = app.get(url, params={'booking-status': 'booked'})
|
||||
assert [x.text_content() for x in resp.pyquery('.registrant--name')] == [
|
||||
|
|
Loading…
Reference in New Issue