add support for unavailability calendars (#46555)

This commit is contained in:
Emmanuel Cazenave 2020-09-30 17:53:42 +02:00 committed by Frédéric Péters
parent e934c54109
commit 7320dcfbe6
17 changed files with 1124 additions and 19 deletions

View File

@ -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'
),
),
]

View File

@ -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'))

View File

@ -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():

View File

@ -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']

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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,

View File

@ -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(

View File

@ -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

View File

@ -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'

View File

@ -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)