manager: display extra user block on demand on check page (#75413)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2023-03-16 14:55:06 +01:00
parent 1a5cfa7436
commit 22721d9cfa
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
11 changed files with 254 additions and 11 deletions

View File

@ -0,0 +1,22 @@
from django.db import migrations, models
import chrono.agendas.models
class Migration(migrations.Migration):
dependencies = [
('agendas', '0148_agenda_minimal_booking_time'),
]
operations = [
migrations.AddField(
model_name='agenda',
name='booking_extra_user_block_template',
field=models.TextField(
blank=True,
help_text='Displayed on check page',
validators=[chrono.agendas.models.django_template_validator],
verbose_name='Extra user block template',
),
),
]

View File

@ -51,7 +51,14 @@ from django.db.models import (
Value,
)
from django.db.models.functions import Cast, Coalesce, Concat, ExtractWeek, ExtractWeekDay, JSONObject
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist, engines
from django.template import (
Context,
RequestContext,
Template,
TemplateSyntaxError,
VariableDoesNotExist,
engines,
)
from django.urls import reverse
from django.utils import functional
from django.utils.dates import WEEKDAYS
@ -240,6 +247,12 @@ class Agenda(models.Model):
blank=True,
validators=[django_template_validator],
)
booking_extra_user_block_template = models.TextField(
_('Extra user block template'),
blank=True,
validators=[django_template_validator],
help_text=_('Displayed on check page'),
)
event_display_template = models.CharField(
_('Event display template'),
max_length=256,
@ -2240,6 +2253,18 @@ class Booking(models.Model):
except (VariableDoesNotExist, TemplateSyntaxError):
return
def get_extra_user_block(self, request):
context = RequestContext(request)
context.update(
{
'booking': self,
}
)
try:
return Template(self.event.agenda.booking_extra_user_block_template).render(context)
except (VariableDoesNotExist, TemplateSyntaxError):
return
@classmethod
def anonymize_bookings(cls, bookings_queryset):
bookings_queryset.update(
@ -3303,6 +3328,18 @@ class Subscription(models.Model):
except (VariableDoesNotExist, TemplateSyntaxError):
return
def get_extra_user_block(self, request):
context = RequestContext(request)
context.update(
{
'booking': self,
}
)
try:
return Template(self.agenda.booking_extra_user_block_template).render(context)
except (VariableDoesNotExist, TemplateSyntaxError):
return
class Person(models.Model):
user_external_id = models.CharField(max_length=250, unique=True)

View File

@ -1417,7 +1417,9 @@ class AgendaBookingCheckSettingsForm(forms.ModelForm):
'mark_event_checked_auto',
'disable_check_update',
'enable_check_for_future_events',
'booking_extra_user_block_template',
]
widgets = {'booking_extra_user_block_template': forms.Textarea(attrs={'rows': 3})}
class AgendaNotificationsForm(forms.ModelForm):

View File

@ -574,3 +574,24 @@ div.agenda-settings .pk-tabs--container {
}
}
}
span.togglable {
padding: 1em 2ex;
cursor: pointer;
}
.untoggled span.togglable:after {
font-family: FontAwesome;
content: "\f107"; /* angle-down */
cursor: pointer;
}
.toggled span.togglable:after {
font-family: FontAwesome;
content: "\f106"; /* angle-up */
cursor: pointer;
}
.extra-user-block {
padding-left: 2em;
}

View File

@ -73,7 +73,7 @@
{% endif %}
{% endif %}
{% for result in results %}
<tr class="booking">
<tr class="booking {% if agenda.booking_extra_user_block_template %}untoggled{% endif %}">
{% include "chrono/manager_event_check_booking_fragment.html" with booking=result %}
</tr>
{% endfor %}
@ -91,8 +91,11 @@
<table class="main check-bookings">
<tbody>
{% for booking in waiting %}
<tr>
<td class="booking-username waiting">{{ booking.get_user_block }}{% if booking.places_count > 1 %} ({{ booking.places_count }} {% trans "places" %}){% endif %}</td>
<tr class="booking {% if agenda.booking_extra_user_block_template %}untoggled{% endif %}">
<td class="booking-username waiting" {% if agenda.booking_extra_user_block_template %}data-extra-user-block-url="{% url 'chrono-manager-booking-extra-user-block' pk=agenda.pk booking_pk=booking.pk %}{% endif %}">
{% if agenda.booking_extra_user_block_template %}<span class="togglable"></span>{% endif %}
{{ booking.get_user_block }}{% if booking.places_count > 1 %} ({{ booking.places_count }} {% trans "places" %}){% endif %}
</td>
<td>{% if booking.cancellation_datetime %}({% trans "Cancelled" %}){% endif %}</td>
</tr>
{% endfor %}
@ -101,4 +104,31 @@
</div>
</div>
{% endif %}
<script>
$(function() {
$(document).on('click', '.togglable', function(event) {
event.preventDefault();
var $toggle = $(this);
var $tr = $toggle.parents('tr');
var $td = $toggle.parents('td.booking-username');
if ($('.extra-user-block', $td).length == 0) {
$.ajax({
url: $td.data('extra-user-block-url')
}).done(function(html) {
$tr.toggleClass('toggled').toggleClass('untoggled');
$td.append('<div class="extra-user-block">' + html + '</div>');
});
} else {
if ($tr.hasClass('toggled')) {
$tr.toggleClass('toggled').toggleClass('untoggled');
$('.extra-user-block', $td).hide();
} else {
$tr.toggleClass('toggled').toggleClass('untoggled');
$('.extra-user-block', $td).show();
}
}
});
});
</script>
{% endblock %}

View File

@ -1,6 +1,9 @@
{% 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-username main-list" {% if agenda.booking_extra_user_block_template %}data-extra-user-block-url="{% if booking.kind == "subscription" %}{% url 'chrono-manager-subscription-extra-user-block' pk=agenda.pk subscription_pk=booking.pk %}{% else %}{% url 'chrono-manager-booking-extra-user-block' pk=agenda.pk booking_pk=booking.pk %}{% endif %}"{% endif %}>
{% if agenda.booking_extra_user_block_template %}<span class="togglable"></span>{% endif %}
{{ booking.get_user_block }}{% if booking.places_count > 1 %} ({{ booking.places_count }} {% trans "places" %}){% endif %}
</td>
<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" %})

View File

@ -114,6 +114,10 @@
<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>
<li>
{% trans "Extra user block template:" %}
<pre>{{ agenda.booking_extra_user_block_template }}</pre>
</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>

View File

@ -424,6 +424,16 @@ urlpatterns = [
views.subscription_absence,
name='chrono-manager-subscription-absence',
),
path(
'agendas/<int:pk>/bookings/<int:booking_pk>/extra-user-block',
views.booking_extra_user_block,
name='chrono-manager-booking-extra-user-block',
),
path(
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/extra-user-block',
views.subscription_extra_user_block,
name='chrono-manager-subscription-extra-user-block',
),
re_path(
r'^agendas/events.csv$',
views.agenda_import_events_sample_csv,

View File

@ -1141,9 +1141,6 @@ class AgendaBookingCheckSettingsView(AgendaEditView):
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
def get_initial(self):
return {'booking_user_block_template': self.agenda.get_booking_user_block_template()}
agenda_booking_check_settings = AgendaBookingCheckSettingsView.as_view()
@ -3642,6 +3639,34 @@ class SubscriptionAbsenceView(ViewableAgendaMixin, SubscriptionCheckMixin, Absen
subscription_absence = SubscriptionAbsenceView.as_view()
class BookingExtraUserBlock(ViewableAgendaMixin, View):
def get(self, request, *args, **kwargs):
booking = get_object_or_404(
Booking,
pk=kwargs['booking_pk'],
event__agenda=self.agenda,
event__cancelled=False,
primary_booking__isnull=True,
)
return HttpResponse(booking.get_extra_user_block(request))
booking_extra_user_block = BookingExtraUserBlock.as_view()
class SubscriptionExtraUserBlock(ViewableAgendaMixin, View):
def get(self, request, *args, **kwargs):
subscription = get_object_or_404(
Subscription,
agenda=self.agenda,
pk=kwargs['subscription_pk'],
)
return HttpResponse(subscription.get_extra_user_block(request))
subscription_extra_user_block = SubscriptionExtraUserBlock.as_view()
class EventCancelView(ViewableAgendaMixin, UpdateView):
template_name = 'chrono/manager_confirm_event_cancellation.html'
model = Event

View File

@ -697,6 +697,24 @@ def test_options_agenda_booking_check_options(app, admin_user):
agenda.refresh_from_db()
assert agenda.enable_check_for_future_events is True
# check extra user block
assert agenda.booking_extra_user_block_template == ''
resp = app.get(url)
resp.form['booking_extra_user_block_template'] = '{{ booking.user_name }} Foo Bar'
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.booking_extra_user_block_template == '{{ booking.user_name }} Foo Bar'
invalid_templates = [
'{{ syntax error }}',
'{{ booking.user_name|invalidfilter }}',
]
for template in invalid_templates:
resp = app.get(url)
resp.form['booking_extra_user_block_template'] = template
resp = resp.form.submit()
assert 'syntax error' in resp.text
# check kind
agenda.kind = 'meetings'
agenda.save()

View File

@ -2792,6 +2792,77 @@ def test_event_check_subscription(check_types, app, admin_user):
)
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_event_check_extra_user_block(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,
)
subscription = Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='41',
date_start=now() - datetime.timedelta(days=1),
date_end=now(),
)
booking = Booking.objects.create(
event=event, user_first_name='User', user_last_name='42', user_external_id='user:2'
)
cancelled_booking = Booking.objects.create(
event=event, user_first_name='User', user_last_name='43', user_external_id='user:3'
)
cancelled_booking.cancel()
waiting_booking = Booking.objects.create(
event=event,
user_first_name='User',
user_last_name='44',
user_external_id='user:4',
in_waiting_list=True,
)
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert '/manage/agendas/%s/subscriptions/%s/extra-user-block' % (agenda.pk, subscription.pk) not in resp
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, booking.pk) not in resp
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, cancelled_booking.pk) not in resp
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, waiting_booking.pk) not in resp
agenda.booking_extra_user_block_template = '{{ booking.user_name }} Foo Bar'
agenda.save()
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert '/manage/agendas/%s/subscriptions/%s/extra-user-block' % (agenda.pk, subscription.pk) in resp
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, booking.pk) in resp
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, cancelled_booking.pk) in resp
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, waiting_booking.pk) in resp
Subscription.objects.create(
agenda=agenda,
user_external_id='user:2',
user_first_name='Subscription',
user_last_name='42',
date_start=now() - datetime.timedelta(days=1),
date_end=now(),
)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
# booking url, not subscription url
assert '/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, booking.pk) in resp
resp = app.get('/manage/agendas/%s/subscriptions/%s/extra-user-block' % (agenda.pk, subscription.pk))
assert resp.text == 'Subscription 41 Foo Bar'
resp = app.get('/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, booking.pk))
assert resp.text == 'User 42 Foo Bar'
resp = app.get('/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, cancelled_booking.pk))
assert resp.text == 'User 43 Foo Bar'
resp = app.get('/manage/agendas/%s/bookings/%s/extra-user-block' % (agenda.pk, waiting_booking.pk))
assert resp.text == 'User 44 Foo Bar'
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_event_check_subscription_ajax(check_types, app, admin_user):
check_types.return_value = [
@ -3049,13 +3120,13 @@ def test_event_check_primary_booking(app, admin_user):
assert 'Bookings (4/10)' in resp.text
user_bookings = resp.pyquery.find('td.booking-username.main-list')
assert len(user_bookings) == 2
assert user_bookings[0].text == 'User 42 (3 places)'
assert user_bookings[1].text == 'John Doe'
assert user_bookings[0].text.replace('\n', '').strip() == 'User 42 (3 places)'
assert user_bookings[1].text.replace('\n', '').strip() == 'John Doe'
assert 'Waiting List (2/5)' in resp.text
user_bookings = resp.pyquery.find('td.booking-username.waiting')
assert len(user_bookings) == 1
assert user_bookings[0].text == 'Jane Doe (2 places)'
assert user_bookings[0].text.replace('\n', '').strip() == 'Jane Doe (2 places)'
def test_duplicate_event(app, admin_user):