manager: add date time period support in settings (#70185)
This commit is contained in:
parent
d0a8534fcd
commit
aa0549f5b6
|
@ -795,7 +795,7 @@ class Agenda(models.Model):
|
|||
end_datetime__gt=min_start,
|
||||
)
|
||||
|
||||
def prefetch_desks_and_exceptions(self, with_sources=False):
|
||||
def prefetch_desks_and_exceptions(self, with_sources=False, min_date=None):
|
||||
if self.kind == 'meetings':
|
||||
desks = self.desk_set.all()
|
||||
elif self.kind == 'virtual':
|
||||
|
@ -807,7 +807,15 @@ class Agenda(models.Model):
|
|||
else:
|
||||
raise ValueError('does not work with kind %r' % self.kind)
|
||||
|
||||
self.prefetched_desks = desks.prefetch_related('timeperiod_set', 'unavailability_calendars')
|
||||
if min_date:
|
||||
past_date_time_periods = TimePeriod.objects.filter(desk=OuterRef('pk'), date__lt=min_date)
|
||||
desks = desks.annotate(has_past_date_time_periods=Exists(past_date_time_periods))
|
||||
|
||||
time_period_queryset = TimePeriod.objects.filter(Q(date__isnull=True) | Q(date__gte=min_date))
|
||||
|
||||
self.prefetched_desks = desks.prefetch_related(
|
||||
'unavailability_calendars', Prefetch('timeperiod_set', queryset=time_period_queryset)
|
||||
)
|
||||
if with_sources:
|
||||
self.prefetched_desks = self.prefetched_desks.prefetch_related('timeperiodexceptionsource_set')
|
||||
unavailability_calendar_ids = UnavailabilityCalendar.objects.filter(
|
||||
|
|
|
@ -842,13 +842,19 @@ class TimePeriodFormBase(forms.Form):
|
|||
widget=forms.CheckboxSelectMultiple(),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'date' in self.fields:
|
||||
del self.fields['repeat']
|
||||
del self.fields['weekday_indexes']
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
if cleaned_data['end_time'] <= cleaned_data['start_time']:
|
||||
raise ValidationError(_('End time must come after start time.'))
|
||||
|
||||
if cleaned_data['repeat'] == 'every-week':
|
||||
if cleaned_data.get('repeat') == 'every-week':
|
||||
cleaned_data['weekday_indexes'] = None
|
||||
|
||||
return cleaned_data
|
||||
|
@ -868,6 +874,7 @@ class TimePeriodForm(TimePeriodFormBase, forms.ModelForm):
|
|||
class Meta:
|
||||
model = TimePeriod
|
||||
widgets = {
|
||||
'date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'start_time': widgets.TimeWidget(),
|
||||
'end_time': widgets.TimeWidget(),
|
||||
}
|
||||
|
@ -878,6 +885,7 @@ class TimePeriodForm(TimePeriodFormBase, forms.ModelForm):
|
|||
self.old_weekday = self.instance.weekday
|
||||
self.old_start_time = self.instance.start_time
|
||||
self.old_end_time = self.instance.end_time
|
||||
self.old_date = self.instance.date
|
||||
|
||||
if self.instance.weekday_indexes:
|
||||
self.fields['repeat'].initial = 'custom'
|
||||
|
@ -893,17 +901,26 @@ class TimePeriodForm(TimePeriodFormBase, forms.ModelForm):
|
|||
|
||||
for desk in self.instance.desk.agenda.desk_set.exclude(pk=self.instance.desk.pk):
|
||||
timeperiod = desk.timeperiod_set.filter(
|
||||
weekday=self.old_weekday, start_time=self.old_start_time, end_time=self.old_end_time
|
||||
weekday=self.old_weekday,
|
||||
start_time=self.old_start_time,
|
||||
end_time=self.old_end_time,
|
||||
date=self.old_date,
|
||||
).first()
|
||||
if timeperiod is not None:
|
||||
timeperiod.weekday = self.instance.weekday
|
||||
timeperiod.start_time = self.instance.start_time
|
||||
timeperiod.end_time = self.instance.end_time
|
||||
timeperiod.date = self.instance.date
|
||||
timeperiod.save()
|
||||
|
||||
return self.instance
|
||||
|
||||
|
||||
class DateTimePeriodForm(TimePeriodForm):
|
||||
class Meta(TimePeriodForm.Meta):
|
||||
fields = ['date', 'start_time', 'end_time']
|
||||
|
||||
|
||||
class NewDeskForm(forms.ModelForm):
|
||||
copy_from = forms.ModelChoiceField(
|
||||
label=_('Copy settings of desk'),
|
||||
|
|
|
@ -78,7 +78,7 @@ h2 span.identifier {
|
|||
padding-right: 1ex;
|
||||
}
|
||||
|
||||
a.timeperiod-exception-all {
|
||||
a.timeperiod-list-all {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{% extends "chrono/manager_agenda_view.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block extrascripts %}
|
||||
{{ block.super }}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
{% if object.id %}
|
||||
<a href="">{{object}}</a>
|
||||
{% else %}
|
||||
<a href="">{% trans "Unique period" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Unique period" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,31 @@
|
|||
{% extends "chrono/manager_agenda_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extrascripts %}
|
||||
{{ block.super }}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href=".">{% trans "Unique periods" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Unique periods" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="timeperiod">
|
||||
<ul class="objects-list single-links">
|
||||
{% for time_period in object_list %}
|
||||
<li>
|
||||
<a rel="popup" href="{% url 'chrono-manager-time-period-edit' pk=time_period.id %}">{{ time_period }}</a>
|
||||
<a rel="popup" class="delete" href="{% url 'chrono-manager-time-period-delete' pk=time_period.id %}">{% trans "remove" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% include "gadjo/pagination.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -93,6 +93,7 @@
|
|||
{% if not object.desk_simple_management or object.desk_simple_management and forloop.counter == 1 %}
|
||||
<div class="timeperiod">
|
||||
{% url 'chrono-manager-agenda-add-time-period' agenda_pk=object.pk pk=desk.pk as add_time_period_url %}
|
||||
{% url 'chrono-manager-agenda-add-date-time-period' agenda_pk=object.pk pk=desk.pk as add_date_time_period_url %}
|
||||
<ul class="objects-list single-links">
|
||||
{% if not object.desk_simple_management and object.prefetched_desks|length > 1 %}
|
||||
<li>
|
||||
|
@ -108,7 +109,11 @@
|
|||
<a rel="popup" class="delete" href="{% url 'chrono-manager-time-period-delete' pk=time_period.id %}">{% trans "remove" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if desk.has_past_date_time_periods %}
|
||||
<li><a class="timeperiod-list-all desk-{{ desk.pk }}" data-selector="div.timeperiod" href="{% url 'chrono-manager-date-time-period-list' pk=desk.id %}">({% trans 'see all unique periods' %})</a></li>
|
||||
{% endif %}
|
||||
<li><a class="add" rel="popup" href="{{add_time_period_url}}">{% trans 'Add repeating periods' %}</a></li>
|
||||
<li><a class="add" rel="popup" href="{{add_date_time_period_url}}">{% trans 'Add a unique period' %}</a></li>
|
||||
{% url 'chrono-manager-agenda-add-time-period-exception' agenda_pk=object.pk pk=desk.pk as add_time_period_exception_url %}
|
||||
<li>
|
||||
<a><strong>{% trans 'Exceptions' %}</strong></a>
|
||||
|
@ -123,7 +128,7 @@
|
|||
</li>
|
||||
{% endfor %}
|
||||
{% if not desk.are_all_exceptions_displayed %}
|
||||
<li><a class="timeperiod-exception-all desk-{{ desk.pk }}" rel="popup" data-selector="div.timeperiod" href="{% url 'chrono-manager-time-period-exception-extract-list' pk=desk.id %}">({% trans 'see all exceptions' %})</a></li>
|
||||
<li><a class="timeperiod-list-all desk-{{ desk.pk }}" rel="popup" data-selector="div.timeperiod" href="{% url 'chrono-manager-time-period-exception-extract-list' pk=desk.id %}">({% trans 'see all exceptions' %})</a></li>
|
||||
{% endif %}
|
||||
<li><a class="add" rel="popup" href="{{add_time_period_exception_url}}">{% trans 'Add a time period exception' %}</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -301,6 +301,16 @@ urlpatterns = [
|
|||
views.time_period_delete,
|
||||
name='chrono-manager-time-period-delete',
|
||||
),
|
||||
path(
|
||||
'agendas/<int:agenda_pk>/desk/<int:pk>/add-date-time-period',
|
||||
views.agenda_add_date_time_period,
|
||||
name='chrono-manager-agenda-add-date-time-period',
|
||||
),
|
||||
path(
|
||||
'timeperiods/desk/<int:pk>/date-time-period-list',
|
||||
views.agenda_date_time_period_list,
|
||||
name='chrono-manager-date-time-period-list',
|
||||
),
|
||||
path('agendas/<int:pk>/add-desk', views.agenda_add_desk, name='chrono-manager-agenda-add-desk'),
|
||||
path('desks/<int:pk>/edit', views.desk_edit, name='chrono-manager-desk-edit'),
|
||||
path('desks/<int:pk>/delete', views.desk_delete, name='chrono-manager-desk-delete'),
|
||||
|
|
|
@ -110,6 +110,7 @@ from .forms import (
|
|||
BookingCheckFilterSet,
|
||||
BookingCheckPresenceForm,
|
||||
CustomFieldFormSet,
|
||||
DateTimePeriodForm,
|
||||
DeskExceptionsImportForm,
|
||||
DeskForm,
|
||||
EventCancelForm,
|
||||
|
@ -1832,7 +1833,7 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if self.agenda.kind == 'meetings':
|
||||
self.agenda.prefetch_desks_and_exceptions(with_sources=True)
|
||||
self.agenda.prefetch_desks_and_exceptions(with_sources=True, min_date=now())
|
||||
return self.agenda
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -2859,11 +2860,21 @@ virtual_agenda_add_time_period = VirtualAgendaAddTimePeriodView.as_view()
|
|||
|
||||
|
||||
class TimePeriodEditView(ManagedTimePeriodMixin, UpdateView):
|
||||
template_name = 'chrono/manager_time_period_form.html'
|
||||
model = TimePeriod
|
||||
form_class = TimePeriodForm
|
||||
tab_anchor = 'time-periods'
|
||||
|
||||
def get_form_class(self):
|
||||
if self.object.weekday is not None:
|
||||
return TimePeriodForm
|
||||
else:
|
||||
return DateTimePeriodForm
|
||||
|
||||
def get_template_names(self):
|
||||
if self.object.weekday is not None:
|
||||
return ['chrono/manager_time_period_form.html']
|
||||
else:
|
||||
return ['chrono/manager_date_time_period_form.html']
|
||||
|
||||
|
||||
time_period_edit = TimePeriodEditView.as_view()
|
||||
|
||||
|
@ -2885,7 +2896,10 @@ class TimePeriodDeleteView(ManagedTimePeriodMixin, DeleteView):
|
|||
|
||||
for desk in time_period.desk.agenda.desk_set.exclude(pk=time_period.desk.pk):
|
||||
tp = desk.timeperiod_set.filter(
|
||||
weekday=time_period.weekday, start_time=time_period.start_time, end_time=time_period.end_time
|
||||
weekday=time_period.weekday,
|
||||
start_time=time_period.start_time,
|
||||
end_time=time_period.end_time,
|
||||
date=time_period.date,
|
||||
).first()
|
||||
if tp is not None:
|
||||
tp.delete()
|
||||
|
@ -2896,6 +2910,43 @@ class TimePeriodDeleteView(ManagedTimePeriodMixin, DeleteView):
|
|||
time_period_delete = TimePeriodDeleteView.as_view()
|
||||
|
||||
|
||||
class AgendaAddDateTimePeriodView(ManagedDeskMixin, FormView):
|
||||
template_name = 'chrono/manager_date_time_period_form.html'
|
||||
model = TimePeriod
|
||||
form_class = DateTimePeriodForm
|
||||
tab_anchor = 'time-periods'
|
||||
|
||||
def form_valid(self, form):
|
||||
create_kwargs = {
|
||||
'date': form.cleaned_data['date'],
|
||||
'start_time': form.cleaned_data['start_time'],
|
||||
'end_time': form.cleaned_data['end_time'],
|
||||
}
|
||||
|
||||
if self.desk.agenda.desk_simple_management:
|
||||
for desk in self.desk.agenda.desk_set.all():
|
||||
TimePeriod.objects.create(desk=desk, **create_kwargs)
|
||||
else:
|
||||
TimePeriod.objects.create(desk=self.desk, **create_kwargs)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
agenda_add_date_time_period = AgendaAddDateTimePeriodView.as_view()
|
||||
|
||||
|
||||
class AgendaDateTimePeriodListView(ManagedDeskMixin, ListView):
|
||||
template_name = 'chrono/manager_date_time_period_list.html'
|
||||
model = TimePeriod
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(desk=self.desk, date__isnull=False).order_by('-date')
|
||||
|
||||
|
||||
agenda_date_time_period_list = AgendaDateTimePeriodListView.as_view()
|
||||
|
||||
|
||||
class AgendaAddDesk(ManagedAgendaMixin, CreateView):
|
||||
template_name = 'chrono/manager_desk_form.html'
|
||||
model = Desk
|
||||
|
|
|
@ -238,3 +238,150 @@ def test_meetings_agenda_delete_time_period_desk_simple_management(app, admin_us
|
|||
resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
|
||||
resp.form.submit()
|
||||
assert TimePeriod.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-10-24 10:00')
|
||||
def test_meetings_agenda_date_time_period(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
|
||||
MeetingType.objects.create(agenda=agenda, label='Blah')
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('Add a unique period', index=0)
|
||||
assert 'repeat' not in resp.form.fields
|
||||
assert 'weekday' not in resp.form.fields
|
||||
resp.form['date'] = '2022-10-24'
|
||||
resp.form['start_time'] = '10:00'
|
||||
resp.form['end_time'] = '17:00'
|
||||
resp = resp.form.submit()
|
||||
assert TimePeriod.objects.get(desk=desk).date == datetime.date(2022, 10, 24)
|
||||
assert TimePeriod.objects.get(desk=desk).start_time.hour == 10
|
||||
assert TimePeriod.objects.get(desk=desk).start_time.minute == 0
|
||||
assert TimePeriod.objects.get(desk=desk).end_time.hour == 17
|
||||
assert TimePeriod.objects.get(desk=desk).end_time.minute == 0
|
||||
assert desk2.timeperiod_set.exists() is False
|
||||
resp = resp.follow()
|
||||
|
||||
# invert start and end
|
||||
resp = resp.click('Add a unique period', index=0)
|
||||
resp.form['date'] = '2022-10-24'
|
||||
resp.form['start_time'] = '13:00'
|
||||
resp.form['end_time'] = '10:00'
|
||||
resp = resp.form.submit()
|
||||
assert 'End time must come after start time.' in resp.text
|
||||
|
||||
# edit
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('Monday 24 October 2022 / 10 a.m. → 5 p.m.', index=0)
|
||||
assert 'Unique period' in resp.text
|
||||
resp.form['date'] = '2022-10-25'
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Tuesday 25' in resp.text
|
||||
|
||||
# delete
|
||||
resp = resp.click('remove', href='timeperiods')
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Tuesday 25' not in resp.text
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-10-24 10:00')
|
||||
def test_meetings_agenda_date_time_period_desk_simple_management(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
|
||||
assert agenda.is_available_for_simple_management() is True
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/desk/%s/add-date-time-period' % (agenda.pk, desk.pk))
|
||||
resp.form['date'] = '2022-10-24'
|
||||
resp.form['start_time'] = '10:00'
|
||||
resp.form['end_time'] = '13:00'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert TimePeriod.objects.filter(desk=desk).count() == 1
|
||||
assert TimePeriod.objects.filter(desk=desk2).count() == 1
|
||||
|
||||
# edit
|
||||
resp = resp.click('Monday 24')
|
||||
resp.form['date'] = '2022-10-25'
|
||||
resp.form['start_time'] = '11:00'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert TimePeriod.objects.filter(desk=desk, date__day=25, start_time__hour=11).count() == 1
|
||||
assert TimePeriod.objects.filter(desk=desk2, date__day=25, start_time__hour=11).count() == 1
|
||||
|
||||
# delete
|
||||
resp = resp.click('remove', href='timeperiods')
|
||||
resp = resp.form.submit().follow()
|
||||
assert TimePeriod.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-10-23')
|
||||
def test_meetings_agenda_date_time_period_display(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
TimePeriod.objects.create(
|
||||
desk=desk, weekday=6, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0)
|
||||
) # repeating period on Sunday
|
||||
TimePeriod.objects.create(
|
||||
desk=desk,
|
||||
date=datetime.date(2022, 10, 24),
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(12, 0),
|
||||
) # unique period on next Monday
|
||||
TimePeriod.objects.create(
|
||||
desk=desk,
|
||||
date=datetime.date(2022, 10, 25),
|
||||
start_time=datetime.time(8, 0),
|
||||
end_time=datetime.time(10, 0),
|
||||
) # unique period on next Tuesday
|
||||
TimePeriod.objects.create(
|
||||
desk=desk,
|
||||
date=datetime.date(2022, 10, 17),
|
||||
start_time=datetime.time(8, 0),
|
||||
end_time=datetime.time(10, 0),
|
||||
) # unique period on past Monday
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert 'Sunday / 2 p.m. → 4 p.m.' in resp.text
|
||||
assert 'Monday 24 October 2022 / 10 a.m. → noon' in resp.text
|
||||
assert 'Tuesday 25 October 2022 / 8 a.m. → 10 a.m.' in resp.text
|
||||
|
||||
# past unique periods are not displayed
|
||||
assert '17 October' not in resp.text
|
||||
|
||||
# unique periods are displayed after repeating periods
|
||||
assert resp.text.index('Sunday') < resp.text.index('Monday') < resp.text.index('Tuesday')
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-10-23')
|
||||
def test_meetings_agenda_date_time_period_list(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
TimePeriod.objects.create(
|
||||
desk=desk,
|
||||
date=datetime.date(2022, 10, 24),
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(12, 0),
|
||||
) # unique period on next Monday
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert 'Monday 24 October 2022 / 10 a.m. → noon' in resp.text
|
||||
assert not 'see all unique periods' in resp.text
|
||||
|
||||
TimePeriod.objects.create(
|
||||
desk=desk,
|
||||
date=datetime.date(2022, 10, 17),
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(12, 0),
|
||||
) # unique period on past Monday
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert '17 October' not in resp.text
|
||||
|
||||
resp = resp.click('see all unique periods')
|
||||
assert 'Monday 24 October 2022 / 10 a.m. → noon' in resp.text
|
||||
assert 'Monday 17 October 2022 / 10 a.m. → noon' in resp.text
|
||||
assert resp.text.index('24 October') < resp.text.index('17 October')
|
||||
|
|
Loading…
Reference in New Issue