add support for unavailability calendars (#46555)
This commit is contained in:
parent
e934c54109
commit
7320dcfbe6
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2020-10-05 12:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
('agendas', '0064_booking_form_url'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UnavailabilityCalendar',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('label', models.CharField(max_length=150, verbose_name='Label')),
|
||||
('slug', models.SlugField(max_length=160, unique=True, verbose_name='Identifier')),
|
||||
('desks', models.ManyToManyField(related_name='unavailability_calendars', to='agendas.Desk')),
|
||||
(
|
||||
'edit_role',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='+',
|
||||
to='auth.Group',
|
||||
verbose_name='Edit Role',
|
||||
),
|
||||
),
|
||||
(
|
||||
'view_role',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='+',
|
||||
to='auth.Group',
|
||||
verbose_name='View Role',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={'ordering': ['label'],},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='timeperiodexception',
|
||||
name='desk',
|
||||
field=models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to='agendas.Desk'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='timeperiodexception',
|
||||
name='unavailability_calendar',
|
||||
field=models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to='agendas.UnavailabilityCalendar'
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1486,8 +1486,63 @@ class TimePeriodExceptionSource(models.Model):
|
|||
self.save()
|
||||
|
||||
|
||||
class UnavailabilityCalendar(models.Model):
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
desks = models.ManyToManyField(Desk, related_name='unavailability_calendars')
|
||||
edit_role = models.ForeignKey(
|
||||
Group,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
related_name='+',
|
||||
verbose_name=_('Edit Role'),
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
view_role = models.ForeignKey(
|
||||
Group,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
related_name='+',
|
||||
verbose_name=_('View Role'),
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['label']
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
@property
|
||||
def base_slug(self):
|
||||
return slugify(self.label)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = generate_slug(self)
|
||||
super(UnavailabilityCalendar, self).save(*args, **kwargs)
|
||||
|
||||
def can_be_managed(self, user):
|
||||
if user.is_staff:
|
||||
return True
|
||||
group_ids = [x.id for x in user.groups.all()]
|
||||
return bool(self.edit_role_id in group_ids)
|
||||
|
||||
def can_be_viewed(self, user):
|
||||
if self.can_be_managed(user):
|
||||
return True
|
||||
group_ids = [x.id for x in user.groups.all()]
|
||||
return bool(self.view_role_id in group_ids)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('chrono-manager-unavailability-calendar-view', kwargs={'pk': self.id})
|
||||
|
||||
|
||||
class TimePeriodException(models.Model):
|
||||
desk = models.ForeignKey(Desk, on_delete=models.CASCADE)
|
||||
desk = models.ForeignKey(Desk, on_delete=models.CASCADE, null=True)
|
||||
unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE, null=True)
|
||||
source = models.ForeignKey(TimePeriodExceptionSource, on_delete=models.CASCADE, null=True)
|
||||
label = models.CharField(_('Optional Label'), max_length=150, blank=True, null=True)
|
||||
start_datetime = models.DateTimeField(_('Exception start time'))
|
||||
|
|
|
@ -141,6 +141,18 @@ def get_all_slots(base_agenda, meeting_type, resources=None, unique=False):
|
|||
key=lambda time_period: time_period.desk,
|
||||
)
|
||||
}
|
||||
|
||||
# add exceptions from unavailability calendar
|
||||
for time_period_exception in TimePeriodException.objects.filter(
|
||||
unavailability_calendar__desks__agenda__in=agendas
|
||||
).order_by('start_datetime', 'end_datetime'):
|
||||
for desk in time_period_exception.unavailability_calendar.desks.all():
|
||||
if desk not in desks_exceptions:
|
||||
desks_exceptions[desk] = IntervalSet()
|
||||
desks_exceptions[desk].add(
|
||||
time_period_exception.start_datetime, time_period_exception.end_datetime
|
||||
)
|
||||
|
||||
# compute reduced min/max_datetime windows by desks based on exceptions
|
||||
desk_min_max_datetime = {}
|
||||
for desk, desk_exception in desks_exceptions.items():
|
||||
|
|
|
@ -44,6 +44,7 @@ from chrono.agendas.models import (
|
|||
AgendaNotificationsSettings,
|
||||
AgendaReminderSettings,
|
||||
WEEKDAYS_LIST,
|
||||
UnavailabilityCalendar,
|
||||
)
|
||||
|
||||
from . import widgets
|
||||
|
@ -89,6 +90,25 @@ class AgendaEditForm(AgendaAddForm):
|
|||
del self.fields['booking_form_url']
|
||||
|
||||
|
||||
class UnavailabilityCalendarAddForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UnavailabilityCalendar
|
||||
fields = ['label', 'edit_role', 'view_role']
|
||||
|
||||
edit_role = forms.ModelChoiceField(
|
||||
label=_('Edit Role'), required=False, queryset=Group.objects.all().order_by('name')
|
||||
)
|
||||
view_role = forms.ModelChoiceField(
|
||||
label=_('View Role'), required=False, queryset=Group.objects.all().order_by('name')
|
||||
)
|
||||
|
||||
|
||||
class UnavailabilityCalendarEditForm(UnavailabilityCalendarAddForm):
|
||||
class Meta:
|
||||
model = UnavailabilityCalendar
|
||||
fields = ['label', 'slug', 'edit_role', 'view_role']
|
||||
|
||||
|
||||
class ResourceAddForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Resource
|
||||
|
@ -272,6 +292,8 @@ class TimePeriodExceptionForm(forms.ModelForm):
|
|||
super().__init__(*args, **kwargs)
|
||||
if self.instance.pk is not None:
|
||||
del self.fields['all_desks']
|
||||
elif self.instance.unavailability_calendar:
|
||||
del self.fields['all_desks']
|
||||
elif self.instance.desk_id and self.instance.desk.agenda.desk_set.count() == 1:
|
||||
del self.fields['all_desks']
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "chrono/manager_agenda_view.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="">{% trans "Add unavailability calendar" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Add unavailability calendar" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -3,14 +3,17 @@
|
|||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Agendas' %}</h2>
|
||||
{% if user.is_staff %}
|
||||
<span class="actions">
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'chrono-manager-category-list' %}">{% trans 'Categories' %}</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-list' %}">{% trans 'Unavailability calendars' %}</a>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'chrono-manager-resource-list' %}">{% trans 'Resources' %}</a>
|
||||
<a rel="popup" href="{% url 'chrono-manager-agendas-import' %}">{% trans 'Import' %}</a>
|
||||
<a rel="popup" href="{% url 'chrono-manager-agenda-add' %}">{% trans 'New' %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% if exception_sources %}
|
||||
{% if exception_sources or unavailability_calendars %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for object in exception_sources %}
|
||||
<li>
|
||||
|
@ -30,6 +30,12 @@
|
|||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% for unavailability_calendar in unavailability_calendars %}
|
||||
<li>
|
||||
<a {% if not unavailability_calendar.enabled %}class="disabled"{% endif %} title="{{ unavailability_calendar }}" href="{{ unavailability_calendar.get_absolute_url }}">{{ unavailability_calendar|truncatechars:50 }}</a>
|
||||
<a class="link-action-text" href="{% url 'chrono-manager-unavailability-calendar-toggle-view' desk.pk unavailability_calendar.pk %}">({{ unavailability_calendar.enabled|yesno:_("disable,enable") }})</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
{% endfor %}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=desk.agenda.id %}">{% trans 'Cancel' %}</a>
|
||||
<a class="cancel" href="{{ cancel_url }}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_list.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page-title-extra-label %}
|
||||
- {{ unavailability_calendar.label }}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-view' pk=unavailability_calendar.pk %}">{{ unavailability_calendar.label }}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
{% block appbar-title %}
|
||||
<h2>{{ unavailability_calendar }}</h2>
|
||||
{% endblock %}
|
||||
{% if user_can_manage %}
|
||||
<span class="actions">
|
||||
{% block appbar-extras %}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-settings' pk=unavailability_calendar.pk %}">{% trans 'Settings' %}</a>
|
||||
{% endblock %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="section">
|
||||
<h3>{% trans 'Used in meetings agendas' %}</h3>
|
||||
<div>
|
||||
{% if agendas %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for agenda in agendas %}
|
||||
<li>
|
||||
<a href="{% url 'chrono-manager-agenda-view' pk=agenda.pk %}">
|
||||
{{ agenda.label }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans %}
|
||||
This unavailability calendar is not used yet.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "chrono/manager_home.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
{% if object.id %}
|
||||
<h2>{% trans "Edit Unavailability Calendar" %}</h2>
|
||||
{% else %}
|
||||
<h2>{% trans "New Unavailability Calendar" %}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-unavailability-calendar-list' %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
|||
{% extends "chrono/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-list' %}">{% trans "Unavailability Calendars" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Unavailability Calendars' %}</h2>
|
||||
{% if user.is_staff %}
|
||||
<span class="actions">
|
||||
<a rel="popup" href="{% url 'chrono-manager-unavailability-calendar-add' %}">{% trans 'New' %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% if object_list %}
|
||||
<div>
|
||||
<ul class="objects-list single-links">
|
||||
{% for object in object_list %}
|
||||
<li>
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-view' pk=object.pk %}">{{ object.label }} ({{ object.slug }})</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans %}
|
||||
This site doesn't have any unavailability calendar yet. Click on the "New" button in the top
|
||||
right of the page to add a first one.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,52 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_detail.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href=".">{% trans "Settings" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Settings" %}
|
||||
</h2>
|
||||
<span class="actions">
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
{% block agenda-extra-management-actions %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-unavailability-calendar-add-unavailability' pk=unavailability_calendar.id %}">{% trans 'Add Unavailability' %}</a>
|
||||
{% endblock %}
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a rel="popup" href="{% url 'chrono-manager-unavailability-calendar-edit' pk=unavailability_calendar.id %}">{% trans 'Options' %}</a></li>
|
||||
{% if user.is_staff %}
|
||||
<li><a rel="popup" href="{% url 'chrono-manager-unavailability-calendar-delete' pk=unavailability_calendar.id %}">{% trans 'Delete' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h3>{% trans 'Unavailabilities' %}</h3>
|
||||
<div>
|
||||
{% block agenda-settings %}
|
||||
{% if unavailability_calendar.timeperiodexception_set.count %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for time_period_exception in unavailability_calendar.timeperiodexception_set.all %}
|
||||
<li><a rel="popup" href="{% url 'chrono-manager-time-period-exception-edit' pk=time_period_exception.id %}">{{ time_period_exception }}</a>
|
||||
<a rel="popup" class="delete" href="{% url 'chrono-manager-time-period-exception-delete' pk=time_period_exception.id %}">{% trans "remove" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans %}
|
||||
There is no unavailabilities yet. Click on the "Add Unavailabilty" button in
|
||||
the top right of the page to add a first one.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -20,6 +20,41 @@ from . import views
|
|||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.homepage, name='chrono-manager-homepage'),
|
||||
url(
|
||||
r'^unavailability-calendars/$',
|
||||
views.unavailability_calendar_list,
|
||||
name='chrono-manager-unavailability-calendar-list',
|
||||
),
|
||||
url(
|
||||
r'^unavailability-calendar/add/$',
|
||||
views.unavailability_calendar_add,
|
||||
name='chrono-manager-unavailability-calendar-add',
|
||||
),
|
||||
url(
|
||||
r'^unavailability-calendar/(?P<pk>\d+)/$',
|
||||
views.unavailability_calendar_view,
|
||||
name='chrono-manager-unavailability-calendar-view',
|
||||
),
|
||||
url(
|
||||
r'^unavailability-calendar/(?P<pk>\d+)/edit/$',
|
||||
views.unavailability_calendar_edit,
|
||||
name='chrono-manager-unavailability-calendar-edit',
|
||||
),
|
||||
url(
|
||||
r'^unavailability-calendar/(?P<pk>\d+)/delete/$',
|
||||
views.unavailability_calendar_delete,
|
||||
name='chrono-manager-unavailability-calendar-delete',
|
||||
),
|
||||
url(
|
||||
r'^unavailability-calendar/(?P<pk>\d+)/settings$',
|
||||
views.unavailability_calendar_settings,
|
||||
name='chrono-manager-unavailability-calendar-settings',
|
||||
),
|
||||
url(
|
||||
r'^unavailability-calendar/(?P<pk>\d+)/add-unavailability$',
|
||||
views.unavailability_calendar_add_unavailability,
|
||||
name='chrono-manager-unavailability-calendar-add-unavailability',
|
||||
),
|
||||
url(r'^resources/$', views.resource_list, name='chrono-manager-resource-list'),
|
||||
url(r'^resource/add/$', views.resource_add, name='chrono-manager-resource-add'),
|
||||
url(r'^resource/(?P<pk>\d+)/$', views.resource_view, name='chrono-manager-resource-view'),
|
||||
|
@ -158,6 +193,11 @@ urlpatterns = [
|
|||
url(r'^agendas/(?P<pk>\d+)/add-desk$', views.agenda_add_desk, name='chrono-manager-agenda-add-desk'),
|
||||
url(r'^desks/(?P<pk>\d+)/edit$', views.desk_edit, name='chrono-manager-desk-edit'),
|
||||
url(r'^desks/(?P<pk>\d+)/delete$', views.desk_delete, name='chrono-manager-desk-delete'),
|
||||
url(
|
||||
r'^desk/(?P<pk>\d+)/unavailability-calendar/(?P<unavailability_calendar_pk>\d+)/toogle/$',
|
||||
views.unavailability_calendar_toggle_view,
|
||||
name='chrono-manager-unavailability-calendar-toggle-view',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<agenda_pk>\d+)/desk/(?P<pk>\d+)/add-time-period-exception$',
|
||||
views.agenda_add_time_period_exception,
|
||||
|
|
|
@ -23,7 +23,7 @@ import uuid
|
|||
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, Value, BooleanField
|
||||
from django.db.models import Min, Max
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
@ -65,6 +65,7 @@ from chrono.agendas.models import (
|
|||
EventCancellationReport,
|
||||
AgendaNotificationsSettings,
|
||||
AgendaReminderSettings,
|
||||
UnavailabilityCalendar,
|
||||
)
|
||||
|
||||
from .forms import (
|
||||
|
@ -94,6 +95,8 @@ from .forms import (
|
|||
EventCancelForm,
|
||||
AgendaNotificationsForm,
|
||||
AgendaReminderForm,
|
||||
UnavailabilityCalendarAddForm,
|
||||
UnavailabilityCalendarEditForm,
|
||||
)
|
||||
from .utils import import_site
|
||||
|
||||
|
@ -1237,6 +1240,40 @@ class ManagedTimePeriodMixin(object):
|
|||
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
|
||||
|
||||
|
||||
class ManagedTimePeriodExceptionMixin(object):
|
||||
|
||||
desk = None
|
||||
unavailability_calendar = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
object_ = self.get_object()
|
||||
if object_.desk:
|
||||
self.desk = self.get_object().desk
|
||||
if not self.desk.agenda.can_be_managed(request.user):
|
||||
raise PermissionDenied()
|
||||
elif object_.unavailability_calendar:
|
||||
self.unavailability_calendar = object_.unavailability_calendar
|
||||
if not self.unavailability_calendar.can_be_managed(request.user):
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.desk:
|
||||
context['desk'] = self.object.desk
|
||||
context['agenda'] = self.object.desk.agenda
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
if self.desk:
|
||||
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
|
||||
elif self.unavailability_calendar:
|
||||
return reverse(
|
||||
'chrono-manager-unavailability-calendar-settings',
|
||||
kwargs={'pk': self.unavailability_calendar.pk},
|
||||
)
|
||||
|
||||
|
||||
class AgendaSettings(ManagedAgendaMixin, DetailView):
|
||||
model = Agenda
|
||||
|
||||
|
@ -1258,6 +1295,7 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|||
]
|
||||
if self.agenda.kind == 'meetings':
|
||||
context['has_resources'] = Resource.objects.exists()
|
||||
context['has_unavailability_calendars'] = UnavailabilityCalendar.objects.exists()
|
||||
return context
|
||||
|
||||
def get_events(self):
|
||||
|
@ -1538,6 +1576,35 @@ class AgendaResourceDeleteView(ManagedAgendaMixin, DeleteView):
|
|||
agenda_delete_resource = AgendaResourceDeleteView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarToggleView(ManagedDeskMixin, DetailView):
|
||||
model = UnavailabilityCalendar
|
||||
pk_url_kwarg = 'unavailability_calendar_pk'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
unavailability_calendar = self.get_object()
|
||||
try:
|
||||
self.desk.unavailability_calendars.get(pk=unavailability_calendar.pk)
|
||||
self.desk.unavailability_calendars.remove(unavailability_calendar)
|
||||
message = _(
|
||||
'Unavailability calendar %(unavailability_calendar)s has been disabled on desk %(desk)s.'
|
||||
)
|
||||
|
||||
except UnavailabilityCalendar.DoesNotExist:
|
||||
self.desk.unavailability_calendars.add(unavailability_calendar)
|
||||
message = _(
|
||||
'Unavailability calendar %(unavailability_calendar)s has been enabled on desk %(desk)s.'
|
||||
)
|
||||
messages.info(
|
||||
self.request, message % {'unavailability_calendar': unavailability_calendar, 'desk': self.desk}
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id})
|
||||
)
|
||||
|
||||
|
||||
unavailability_calendar_toggle_view = UnavailabilityCalendarToggleView.as_view()
|
||||
|
||||
|
||||
class AgendaAddMeetingTypeView(ManagedAgendaMixin, CreateView):
|
||||
template_name = 'chrono/manager_meeting_type_form.html'
|
||||
model = MeetingType
|
||||
|
@ -1745,6 +1812,11 @@ class AgendaAddTimePeriodExceptionView(ManagedDeskMixin, CreateView):
|
|||
model = TimePeriodException
|
||||
form_class = TimePeriodExceptionForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.pk})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
result = super().form_valid(form)
|
||||
exceptions = [self.object]
|
||||
|
@ -1775,11 +1847,24 @@ class AgendaAddTimePeriodExceptionView(ManagedDeskMixin, CreateView):
|
|||
agenda_add_time_period_exception = AgendaAddTimePeriodExceptionView.as_view()
|
||||
|
||||
|
||||
class TimePeriodExceptionEditView(ManagedDeskSubobjectMixin, UpdateView):
|
||||
class TimePeriodExceptionEditView(ManagedTimePeriodExceptionMixin, UpdateView):
|
||||
template_name = 'chrono/manager_time_period_exception_form.html'
|
||||
model = TimePeriodException
|
||||
form_class = TimePeriodExceptionForm
|
||||
|
||||
def get_context_data(self):
|
||||
context = super().get_context_data()
|
||||
if self.desk:
|
||||
context['cancel_url'] = reverse(
|
||||
'chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.pk}
|
||||
)
|
||||
elif self.unavailability_calendar:
|
||||
context['cancel_url'] = reverse(
|
||||
'chrono-manager-unavailability-calendar-settings',
|
||||
kwargs={'pk': self.unavailability_calendar.pk},
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
time_period_exception_edit = TimePeriodExceptionEditView.as_view()
|
||||
|
||||
|
@ -1812,18 +1897,19 @@ class TimePeriodExceptionExtractListView(TimePeriodExceptionListView):
|
|||
time_period_exception_extract_list = TimePeriodExceptionExtractListView.as_view()
|
||||
|
||||
|
||||
class TimePeriodExceptionDeleteView(ManagedDeskSubobjectMixin, DeleteView):
|
||||
class TimePeriodExceptionDeleteView(ManagedTimePeriodExceptionMixin, DeleteView):
|
||||
template_name = 'chrono/manager_confirm_exception_delete.html'
|
||||
model = TimePeriodException
|
||||
|
||||
def get_success_url(self):
|
||||
referer = self.request.META.get('HTTP_REFERER')
|
||||
success_url = reverse('chrono-manager-time-period-exception-list', kwargs={'pk': self.desk.pk})
|
||||
if success_url in referer:
|
||||
return success_url
|
||||
if self.desk:
|
||||
referer = self.request.META.get('HTTP_REFERER')
|
||||
success_url = reverse('chrono-manager-time-period-exception-list', kwargs={'pk': self.desk.pk})
|
||||
if success_url in referer:
|
||||
return success_url
|
||||
|
||||
success_url = super(TimePeriodExceptionDeleteView, self).get_success_url()
|
||||
if 'from_popup' in self.request.GET:
|
||||
if self.desk and 'from_popup' in self.request.GET:
|
||||
success_url = '{}?display_exceptions={}'.format(success_url, self.desk.pk)
|
||||
return success_url
|
||||
|
||||
|
@ -1841,7 +1927,18 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DeskImportTimePeriodExceptionsView, self).get_context_data(**kwargs)
|
||||
context['exception_sources'] = self.get_object().timeperiodexceptionsource_set.all()
|
||||
desk = self.get_object()
|
||||
context['exception_sources'] = desk.timeperiodexceptionsource_set.all()
|
||||
context['desk'] = desk
|
||||
context['unavailability_calendars'] = (
|
||||
UnavailabilityCalendar.objects.filter(desks=desk)
|
||||
.annotate(enabled=Value(True, BooleanField()))
|
||||
.union(
|
||||
UnavailabilityCalendar.objects.exclude(desks=desk).annotate(
|
||||
enabled=Value(False, BooleanField())
|
||||
)
|
||||
)
|
||||
)
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
|
@ -2082,6 +2179,146 @@ class TimePeriodExceptionSourceToggleView(ManagedDeskSubobjectMixin, DetailView)
|
|||
time_period_exception_source_toggle = TimePeriodExceptionSourceToggleView.as_view()
|
||||
|
||||
|
||||
class ViewableUnavailabilityCalendarMixin(object):
|
||||
unavailability_calendar = None
|
||||
|
||||
def set_unavailability_calendar(self, **kwargs):
|
||||
self.unavailability_calendar = get_object_or_404(UnavailabilityCalendar, id=kwargs.get('pk'))
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.set_unavailability_calendar(**kwargs)
|
||||
if not self.check_permissions(request.user):
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def check_permissions(self, user):
|
||||
return self.unavailability_calendar.can_be_viewed(user)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['unavailability_calendar'] = self.unavailability_calendar
|
||||
context['user_can_manage'] = self.unavailability_calendar.can_be_managed(self.request.user)
|
||||
return context
|
||||
|
||||
|
||||
class ManagedUnavailabilityCalendarMixin(ViewableUnavailabilityCalendarMixin):
|
||||
def check_permissions(self, user):
|
||||
return self.unavailability_calendar.can_be_managed(user)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(
|
||||
'chrono-manager-unavailability-calendar-settings', kwargs={'pk': self.unavailability_calendar.id}
|
||||
)
|
||||
|
||||
|
||||
class UnavailabilityCalendarListView(ListView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_list.html'
|
||||
model = UnavailabilityCalendar
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if not self.request.user.is_staff:
|
||||
group_ids = [x.id for x in self.request.user.groups.all()]
|
||||
queryset = queryset.filter(Q(view_role_id__in=group_ids) | Q(edit_role_id__in=group_ids))
|
||||
return queryset.order_by('label')
|
||||
|
||||
|
||||
unavailability_calendar_list = UnavailabilityCalendarListView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarAddView(CreateView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_form.html'
|
||||
model = UnavailabilityCalendar
|
||||
form_class = UnavailabilityCalendarAddForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('chrono-manager-unavailability-calendar-view', kwargs={'pk': self.object.id})
|
||||
|
||||
|
||||
unavailability_calendar_add = UnavailabilityCalendarAddView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarDetailView(ViewableUnavailabilityCalendarMixin, DetailView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_detail.html'
|
||||
model = UnavailabilityCalendar
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['agendas'] = Agenda.objects.filter(
|
||||
desk__unavailability_calendars__pk=self.unavailability_calendar.pk
|
||||
).distinct()
|
||||
return context
|
||||
|
||||
|
||||
unavailability_calendar_view = UnavailabilityCalendarDetailView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarEditView(ManagedUnavailabilityCalendarMixin, UpdateView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_form.html'
|
||||
model = UnavailabilityCalendar
|
||||
form_class = UnavailabilityCalendarEditForm
|
||||
|
||||
|
||||
unavailability_calendar_edit = UnavailabilityCalendarEditView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarDeleteView(DeleteView):
|
||||
template_name = 'chrono/manager_confirm_delete.html'
|
||||
model = UnavailabilityCalendar
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('chrono-manager-unavailability-calendar-list')
|
||||
|
||||
|
||||
unavailability_calendar_delete = UnavailabilityCalendarDeleteView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarSettings(ManagedUnavailabilityCalendarMixin, DetailView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_settings.html'
|
||||
model = UnavailabilityCalendar
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UnavailabilityCalendarSettings, self).get_context_data(**kwargs)
|
||||
context['unavailability_calendar'] = self.object
|
||||
return context
|
||||
|
||||
|
||||
unavailability_calendar_settings = UnavailabilityCalendarSettings.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarAddUnavailabilityView(ManagedUnavailabilityCalendarMixin, CreateView):
|
||||
template_name = 'chrono/manager_time_period_exception_form.html'
|
||||
form_class = TimePeriodExceptionForm
|
||||
model = TimePeriodException
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
if not kwargs.get('instance'):
|
||||
kwargs['instance'] = self.model()
|
||||
kwargs['instance'].unavailability_calendar = self.unavailability_calendar
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UnavailabilityCalendarAddUnavailabilityView, self).get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse(
|
||||
'chrono-manager-unavailability-calendar-settings', kwargs={'pk': self.unavailability_calendar.pk,}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
unavailability_calendar_add_unavailability = UnavailabilityCalendarAddUnavailabilityView.as_view()
|
||||
|
||||
|
||||
def menu_json(request):
|
||||
label = _('Agendas')
|
||||
json_str = json.dumps(
|
||||
|
|
|
@ -22,7 +22,7 @@ from django.contrib.auth.decorators import user_passes_test
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
|
||||
from .agendas.models import Agenda
|
||||
from .agendas.models import Agenda, UnavailabilityCalendar
|
||||
|
||||
if django.VERSION < (2, 0, 0):
|
||||
from django.urls.resolvers import RegexURLPattern as URLPattern
|
||||
|
@ -55,7 +55,12 @@ def manager_required(function=None, login_url=None):
|
|||
if user and not user.is_anonymous:
|
||||
# /manage/ is open to anyone authorized to view or edit an agenda.
|
||||
group_ids = [x.id for x in user.groups.all()]
|
||||
if Agenda.objects.filter(Q(view_role_id__in=group_ids) | Q(edit_role_id__in=group_ids)).exists():
|
||||
if (
|
||||
Agenda.objects.filter(Q(view_role_id__in=group_ids) | Q(edit_role_id__in=group_ids)).exists()
|
||||
or UnavailabilityCalendar.objects.filter(
|
||||
Q(view_role_id__in=group_ids) | Q(edit_role_id__in=group_ids)
|
||||
).exists()
|
||||
):
|
||||
return True
|
||||
raise PermissionDenied()
|
||||
# As the last resort, show the login form
|
||||
|
|
|
@ -21,6 +21,7 @@ from chrono.agendas.models import (
|
|||
Resource,
|
||||
TimePeriod,
|
||||
TimePeriodException,
|
||||
UnavailabilityCalendar,
|
||||
VirtualMember,
|
||||
)
|
||||
import chrono.api.views
|
||||
|
@ -643,7 +644,7 @@ def test_datetimes_api_meetings_agenda_with_resources(app):
|
|||
)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(api_url)
|
||||
assert len(ctx.captured_queries) == 9
|
||||
assert len(ctx.captured_queries) == 10
|
||||
assert len(resp.json['data']) == 32
|
||||
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
||||
'%s 09:00:00' % tomorrow_str,
|
||||
|
@ -3802,7 +3803,7 @@ def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, time_zone, moc
|
|||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(api_url)
|
||||
assert len(resp.json['data']) == 12
|
||||
assert len(ctx.captured_queries) == 11
|
||||
assert len(ctx.captured_queries) == 12
|
||||
|
||||
# simulate booking
|
||||
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
||||
|
@ -4126,3 +4127,129 @@ def test_duration_on_booking_api_fillslots_response(app, user):
|
|||
ics = app.get(resp.json['api']['ics_url']).text
|
||||
assert 'DTSTART:20170519T231200Z' in ics
|
||||
assert 'DTEND:20170520T004200Z' in ics
|
||||
|
||||
|
||||
def test_unavailabilitycalendar_meetings_datetimes(app, user, meetings_agenda):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
meeting_type = meetings_agenda.meetingtype_set.first()
|
||||
desk = meetings_agenda.desk_set.first()
|
||||
datetimes_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug)
|
||||
resp = app.get(datetimes_url)
|
||||
assert len(resp.json['data']) == 144
|
||||
|
||||
# create an unvalailability calendar
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='foo holydays')
|
||||
TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
|
||||
)
|
||||
unavailability_calendar.desks.add(desk)
|
||||
|
||||
# 2 slots are gone
|
||||
resp2 = app.get(datetimes_url)
|
||||
assert len(resp.json['data']) == len(resp2.json['data']) + 2
|
||||
|
||||
# add a standard desk exception
|
||||
desk = meetings_agenda.desk_set.first()
|
||||
TimePeriodException.objects.create(
|
||||
desk=desk,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
||||
)
|
||||
# 4 slots are gone
|
||||
resp3 = app.get(datetimes_url)
|
||||
assert len(resp.json['data']) == len(resp3.json['data']) + 4
|
||||
|
||||
|
||||
def test_unavailabilitycalendar_on_virtual_datetimes(app, user, mock_now):
|
||||
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
|
||||
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
||||
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
||||
TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
|
||||
)
|
||||
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
||||
|
||||
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
|
||||
resp = app.get(api_url)
|
||||
# 8 slots
|
||||
data = resp.json['data']
|
||||
assert len(data) == 8
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
||||
assert data[2]['datetime'] == '2017-05-22 11:00:00'
|
||||
|
||||
# exclude one hour the first day through an unvalailability calendar on the foo agenda
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='foo holydays')
|
||||
TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
||||
)
|
||||
unavailability_calendar.desks.add(foo_desk_1)
|
||||
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 6
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
||||
# no more slots the 22 thanks to the unavailability calendar
|
||||
assert data[2]['datetime'] == '2017-05-23 10:00:00'
|
||||
|
||||
# exclude the second day
|
||||
TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 18, 0)),
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 2
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
||||
|
||||
# add a second real agenda
|
||||
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
||||
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
|
||||
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
|
||||
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
|
||||
TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_2,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_2,
|
||||
)
|
||||
|
||||
# bar_agenda has the same time periods than foo_agenda, but no unavailability calendar
|
||||
# so we are back at the start : 8 slots
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 8
|
||||
|
||||
# exclude one hour the second day through another unvalailability calendar on the bar agenda
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='bar holydays')
|
||||
TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 23, 11, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 12, 0)),
|
||||
)
|
||||
unavailability_calendar.desks.add(bar_desk_1, bar_desk_2)
|
||||
|
||||
# 2 slots are gone
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 6
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[-1]['datetime'] == '2017-05-23 10:30:00'
|
||||
|
|
|
@ -35,6 +35,7 @@ from chrono.agendas.models import (
|
|||
TimePeriodExceptionSource,
|
||||
VirtualMember,
|
||||
AgendaReminderSettings,
|
||||
UnavailabilityCalendar,
|
||||
)
|
||||
from chrono.manager.forms import TimePeriodExceptionForm
|
||||
from chrono.utils.signature import check_query
|
||||
|
@ -1000,7 +1001,7 @@ def test_options_meetings_agenda_num_queries(app, admin_user):
|
|||
app = login(app)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert len(ctx.captured_queries) == 9
|
||||
assert len(ctx.captured_queries) == 10
|
||||
|
||||
|
||||
def test_agenda_resources(app, admin_user):
|
||||
|
@ -4376,3 +4377,346 @@ def test_manager_reminders_preview(app, admin_user):
|
|||
'Reminder: you have a booking for event "{{ event_label }}", on 02/06 at 2:30 p.m.. Take ID card.'
|
||||
in resp.text
|
||||
)
|
||||
|
||||
|
||||
def test_no_unavailability_calendar(app, admin_user):
|
||||
app = login(app)
|
||||
|
||||
# empty unavailability calendars list
|
||||
resp = app.get('/manage/')
|
||||
resp = resp.click('Unavailability calendars')
|
||||
assert "This site doesn't have any unavailability calendar yet" in resp.text
|
||||
|
||||
# on the agenda settings page, no unavailability calendar reference
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert 'unavailability calendar' not in resp.text
|
||||
|
||||
|
||||
def test_add_unavailability_calendar(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/')
|
||||
resp = resp.click('Unavailability calendars')
|
||||
resp = resp.click('New')
|
||||
resp.form['label'] = 'Foo bar'
|
||||
resp = resp.form.submit()
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/unavailability-calendar/%s/' % unavailability_calendar.pk)
|
||||
assert unavailability_calendar.label == 'Foo bar'
|
||||
assert unavailability_calendar.slug == 'foo-bar'
|
||||
resp = resp.follow()
|
||||
assert 'This unavailability calendar is not used yet.' in resp.text
|
||||
resp = app.get('/manage/unavailability-calendars/')
|
||||
assert 'Foo bar' in resp.text
|
||||
assert 'foo-bar' in resp.text
|
||||
|
||||
|
||||
def test_used_unavailability_calendar(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
||||
agenda = Agenda.objects.create(label='Foo', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='desk')
|
||||
|
||||
app = login(app)
|
||||
url = '/manage/unavailability-calendar/%s/' % unavailability_calendar.pk
|
||||
resp = app.get(url)
|
||||
assert 'This unavailability calendar is not used yet.' in resp.text
|
||||
assert 'Foo' not in resp.text
|
||||
|
||||
desk.unavailability_calendars.add(unavailability_calendar)
|
||||
resp = app.get(url)
|
||||
assert 'This unavailability calendar is not used yet.' not in resp.text
|
||||
assert 'Foo' in resp.text
|
||||
|
||||
|
||||
def test_edit_unavailability_calendar(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Foo')
|
||||
app = login(app)
|
||||
settings_url = '/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk
|
||||
resp = app.get(settings_url)
|
||||
resp = resp.click('Options')
|
||||
resp.form['label'] = 'Bar'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(settings_url)
|
||||
resp = resp.follow()
|
||||
assert 'Bar' in resp.text
|
||||
unavailability_calendar.refresh_from_db()
|
||||
assert unavailability_calendar.label == 'Bar'
|
||||
|
||||
|
||||
def test_delete_unavailability_calendar(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Foo')
|
||||
app = login(app)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
resp = resp.click('Delete')
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/unavailability-calendars/')
|
||||
resp = resp.follow()
|
||||
assert 'Foo' not in resp.text
|
||||
assert UnavailabilityCalendar.objects.count() == 0
|
||||
|
||||
|
||||
def test_unavailability_calendar_add_time_period_exeptions(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Foo')
|
||||
app = login(app)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/' % unavailability_calendar.pk)
|
||||
resp = resp.click('Settings')
|
||||
assert 'There is no unavailabilities yet' in resp.text
|
||||
resp = resp.click('Add Unavailability')
|
||||
assert 'all_desks' not in resp.form.fields
|
||||
today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
tomorrow = make_aware(today + datetime.timedelta(days=1))
|
||||
resp.form['label'] = 'Exception 1'
|
||||
resp.form['start_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
|
||||
resp.form['start_datetime_1'] = '08:00'
|
||||
resp.form['end_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
|
||||
resp.form['end_datetime_1'] = '16:00'
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Exception 1' in resp.text
|
||||
|
||||
time_period_exception = TimePeriodException.objects.first()
|
||||
assert time_period_exception.unavailability_calendar == unavailability_calendar
|
||||
assert time_period_exception.desk is None
|
||||
assert time_period_exception.label == 'Exception 1'
|
||||
|
||||
|
||||
def test_unavailability_calendar_edit_time_period_exeptions(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Foo')
|
||||
time_period_exception = TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
label='Exception 1',
|
||||
start_datetime=now() - datetime.timedelta(days=2),
|
||||
end_datetime=now() - datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
assert 'Exception 1' in resp.text
|
||||
resp = resp.click(href='/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
|
||||
assert 'all_desks' not in resp.form.fields
|
||||
resp.form['label'] = 'Exception foo'
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Exception foo' in resp.text
|
||||
time_period_exception.refresh_from_db()
|
||||
assert 'Exception foo' == time_period_exception.label
|
||||
|
||||
|
||||
def test_unavailability_calendar_delete_time_period_exeptions(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Foo')
|
||||
time_period_exception = TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
label='Exception 1',
|
||||
start_datetime=now() - datetime.timedelta(days=2),
|
||||
end_datetime=now() - datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
assert 'Exception 1' in resp.text
|
||||
resp = resp.click(href='/manage/time-period-exceptions/%s/delete' % time_period_exception.pk)
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Exception foo' not in resp.text
|
||||
assert unavailability_calendar.timeperiodexception_set.count() == 0
|
||||
|
||||
|
||||
def test_activate_unavailability_calendar_in_desk(app, admin_user):
|
||||
app = login(app)
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='desk')
|
||||
exceptions_url = '/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk
|
||||
resp = app.get(exceptions_url)
|
||||
assert 'calendar' in resp.text
|
||||
assert 'enable' in resp.text
|
||||
resp = resp.click(
|
||||
href='/manage/desk/%s/unavailability-calendar/%s/toogle/' % (desk.pk, unavailability_calendar.pk)
|
||||
)
|
||||
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = app.get(exceptions_url)
|
||||
assert 'calendar' in resp.text
|
||||
assert 'disable' in resp.text
|
||||
assert desk.unavailability_calendars.get(pk=unavailability_calendar.pk)
|
||||
|
||||
|
||||
def test_deactivate_unavailability_calendar_in_desk(app, admin_user):
|
||||
app = login(app)
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='desk')
|
||||
desk.unavailability_calendars.add(unavailability_calendar)
|
||||
exceptions_url = '/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk
|
||||
resp = app.get(exceptions_url)
|
||||
assert 'calendar' in resp.text
|
||||
assert 'disable' in resp.text
|
||||
resp = resp.click(
|
||||
href='/manage/desk/%s/unavailability-calendar/%s/toogle/' % (desk.pk, unavailability_calendar.pk)
|
||||
)
|
||||
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = app.get(exceptions_url)
|
||||
assert 'calendar' in resp.text
|
||||
assert 'enable' in resp.text
|
||||
assert desk.unavailability_calendars.count() == 0
|
||||
|
||||
|
||||
def test_unavailability_calendar_homepage_permission(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
resp = app.get('/manage/', status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
resp = app.get('/manage/')
|
||||
resp = resp.click('Unavailability calendars')
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
resp = app.get('/manage/')
|
||||
resp = resp.click('Unavailability calendars')
|
||||
|
||||
|
||||
def test_unavailability_calendar_list_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
app.get('/manage/unavailability-calendars/', status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
resp = app.get('/manage/unavailability-calendars/')
|
||||
assert 'Calendar 1' in resp.text
|
||||
assert 'New' not in resp.text
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
assert 'Calendar 1' in resp.text
|
||||
assert 'New' not in resp.text
|
||||
|
||||
|
||||
def test_unavailability_calendar_add_permissions(app, manager_user):
|
||||
app = login(app, username='manager', password='manager')
|
||||
url = '/manage/unavailability-calendar/add/'
|
||||
app.get(url, status=403)
|
||||
|
||||
|
||||
def test_unavailability_calendar_detail_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
detail_url = '/manage/unavailability-calendar/%s/' % unavailability_calendar.pk
|
||||
resp = app.get(detail_url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
resp = app.get(detail_url)
|
||||
assert 'Settings' not in resp.text
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
resp = app.get(detail_url)
|
||||
assert 'Settings' in resp.text
|
||||
|
||||
|
||||
def test_unavailability_calendar_edit_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
url = '/manage/unavailability-calendar/%s/edit/' % unavailability_calendar.pk
|
||||
app.get(url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
app.get(url, status=403)
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(url)
|
||||
|
||||
|
||||
def test_unavailability_calendar_delete_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
url = '/manage/unavailability-calendar/%s/delete/' % unavailability_calendar.pk
|
||||
app.get(url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
app.get(url, status=403)
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(url, status=403)
|
||||
|
||||
|
||||
def test_unavailability_calendar_settings_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
settings_url = '/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk
|
||||
app.get(settings_url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
app.get(settings_url, status=403)
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(settings_url)
|
||||
|
||||
|
||||
def test_unavailability_calendar_add_unavailability_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
app = login(app, username='manager', password='manager')
|
||||
url = '/manage/unavailability-calendar/%s/add-unavailability' % unavailability_calendar.pk
|
||||
app.get(url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
app.get(url, status=403)
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(url)
|
||||
|
||||
|
||||
def test_unavailability_calendar_edit_unavailability_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
time_period_exception = TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=now() - datetime.timedelta(days=2),
|
||||
end_datetime=now() - datetime.timedelta(days=1),
|
||||
)
|
||||
app = login(app, username='manager', password='manager')
|
||||
url = '/manage/time-period-exceptions/%s/edit' % time_period_exception.pk
|
||||
app.get(url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
app.get(url, status=403)
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(url)
|
||||
|
||||
|
||||
def test_unavailability_calendar_delete_unavailability_permissions(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
time_period_exception = TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=now() - datetime.timedelta(days=2),
|
||||
end_datetime=now() - datetime.timedelta(days=1),
|
||||
)
|
||||
app = login(app, username='manager', password='manager')
|
||||
url = '/manage/time-period-exceptions/%s/delete' % time_period_exception.pk
|
||||
app.get(url, status=403)
|
||||
group = manager_user.groups.all()[0]
|
||||
unavailability_calendar.view_role = group
|
||||
unavailability_calendar.edit_role = None
|
||||
unavailability_calendar.save()
|
||||
app.get(url, status=403)
|
||||
unavailability_calendar.view_role = None
|
||||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(url)
|
||||
|
|
Loading…
Reference in New Issue