manager: inspect views (#87751)

This commit is contained in:
Lauréline Guérin 2024-03-07 15:23:38 +01:00
parent ecf0ffd96e
commit bd06f2b82f
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
20 changed files with 974 additions and 27 deletions

View File

@ -10,6 +10,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='agenda',
name='desk_simple_management',
field=models.BooleanField(default=False),
field=models.BooleanField(default=False, verbose_name='Global desk management'),
),
]

View File

@ -62,13 +62,14 @@ from django.template import (
VariableDoesNotExist,
engines,
)
from django.template.defaultfilters import yesno
from django.urls import reverse
from django.utils import functional
from django.utils.dates import WEEKDAYS
from django.utils.encoding import force_str
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.html import escape, linebreaks
from django.utils.module_loading import import_string
from django.utils.safestring import mark_safe
from django.utils.text import slugify
@ -177,12 +178,35 @@ def booking_template_validator(value):
pass
class WithInspectMixin:
def get_inspect_fields(self, keys=None):
keys = keys or self.get_inspect_keys()
for key in keys:
field = self._meta.get_field(key)
get_value_method = 'get_%s_inspect_value' % key
get_display_method = 'get_%s_display' % key
if hasattr(self, get_value_method):
value = getattr(self, get_value_method)()
elif hasattr(self, get_display_method):
value = getattr(self, get_display_method)()
else:
value = getattr(self, key)
if value in [None, '']:
continue
if isinstance(value, bool):
value = yesno(value)
if isinstance(field, models.TextField):
value = mark_safe(linebreaks(value))
yield (field.verbose_name, value)
TimeSlot = collections.namedtuple(
'TimeSlot', ['start_datetime', 'end_datetime', 'full', 'desk', 'booked_for_external_user']
)
class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
# pylint: disable=too-many-public-methods
class Agenda(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
# mark temporarily restored snapshots
snapshot = models.ForeignKey(
AgendaSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
@ -250,7 +274,7 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
booking_form_url = models.CharField(
_('Booking form URL'), max_length=200, blank=True, validators=[django_template_validator]
)
desk_simple_management = models.BooleanField(default=False)
desk_simple_management = models.BooleanField(_('Global desk management'), default=False)
mark_event_checked_auto = models.BooleanField(
_('Automatically mark event as checked when all bookings have been checked'), default=False
)
@ -625,6 +649,62 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
return created, agenda
def get_inspect_keys(self):
keys = ['label', 'slug', 'kind', 'category', 'anonymize_delay', 'default_view']
if self.kind == 'events':
keys += ['booking_form_url', 'events_type']
elif self.kind == 'meetings':
keys += ['desk_simple_management']
return keys
def get_permissions_inspect_fields(self):
yield from self.get_inspect_fields(keys=['edit_role', 'view_role'])
def get_display_inspect_fields(self):
keys = []
if self.kind == 'events':
keys += ['event_display_template']
keys += [
'booking_user_block_template',
]
yield from self.get_inspect_fields(keys=keys)
def get_booking_check_inspect_fields(self):
keys = [
'booking_check_filters',
'mark_event_checked_auto',
'disable_check_update',
'enable_check_for_future_events',
'booking_extra_user_block_template',
]
yield from self.get_inspect_fields(keys=keys)
def get_invoicing_inspect_fields(self):
keys = ['invoicing_unit', 'invoicing_tolerance']
yield from self.get_inspect_fields(keys=keys)
def get_notifications_inspect_fields(self):
if hasattr(self, 'notifications_settings'):
yield from self.notifications_settings.get_inspect_fields()
return []
def get_reminder_inspect_fields(self):
if hasattr(self, 'reminder_settings'):
yield from self.reminder_settings.get_inspect_fields()
return []
def get_booking_delays_inspect_fields(self):
keys = [
'minimal_booking_delay',
'minimal_booking_delay_in_working_days',
'maximal_booking_delay',
'minimal_booking_time',
]
yield from self.get_inspect_fields(keys=keys)
def get_kind_inspect_value(self):
return self.get_real_kind_display()
def duplicate(self, label=None):
# clone current agenda
new_agenda = copy.deepcopy(self)
@ -1761,7 +1841,7 @@ WEEK_CHOICES = [
]
class TimePeriod(models.Model):
class TimePeriod(WithInspectMixin, models.Model):
weekday = models.IntegerField(_('Week day'), choices=WEEKDAYS_LIST, null=True)
weekday_indexes = ArrayField(
models.IntegerField(choices=WEEK_CHOICES),
@ -1828,6 +1908,15 @@ class TimePeriod(models.Model):
'end_time': self.end_time.strftime('%H:%M'),
}
def get_inspect_keys(self):
return [
'weekday',
'weekday_indexes',
'date',
'start_time',
'end_time',
]
def duplicate(self, desk_target=None, agenda_target=None):
# clone current period
new_period = copy.deepcopy(self)
@ -2035,7 +2124,7 @@ class SharedTimePeriod:
start_datetime += datetime.timedelta(days=7)
class MeetingType(models.Model):
class MeetingType(WithInspectMixin, models.Model):
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160)
@ -2072,6 +2161,13 @@ class MeetingType(models.Model):
'duration': self.duration,
}
def get_inspect_keys(self):
return [
'label',
'slug',
'duration',
]
def duplicate(self, agenda_target=None):
new_meeting_type = copy.deepcopy(self)
new_meeting_type.pk = None
@ -2093,7 +2189,7 @@ class MeetingType(models.Model):
)
class Event(models.Model):
class Event(WithInspectMixin, models.Model):
id = models.BigAutoField(primary_key=True)
INTERVAL_CHOICES = [
(1, _('Every week')),
@ -2661,6 +2757,23 @@ class Event(models.Model):
'duration': self.duration,
}
def get_inspect_keys(self):
return [
'label',
'slug',
'description',
'start_datetime',
'duration',
'recurrence_days',
'recurrence_week_interval',
'recurrence_end_date',
'publication_datetime',
'places',
'waiting_list_places',
'url',
'pricing',
]
def duplicate(self, agenda_target=None, primary_event=None, label=None, start_datetime=None):
new_event = copy.deepcopy(self)
new_event.pk = None
@ -2845,7 +2958,7 @@ class Event(models.Model):
return custom_fields
class EventsType(WithSnapshotMixin, WithApplicationMixin, models.Model):
class EventsType(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
# mark temporarily restored snapshots
snapshot = models.ForeignKey(
EventsTypeSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
@ -2918,6 +3031,9 @@ class EventsType(WithSnapshotMixin, WithApplicationMixin, models.Model):
'custom_fields': self.custom_fields,
}
def get_inspect_keys(self):
return ['label', 'slug']
class BookingColor(models.Model):
COLOR_COUNT = 8
@ -3312,7 +3428,7 @@ class BookingCheck(models.Model):
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])
class Desk(models.Model):
class Desk(WithInspectMixin, models.Model):
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160)
@ -3375,6 +3491,12 @@ class Desk(models.Model):
'unavailability_calendars': [{'slug': x.slug} for x in self.unavailability_calendars.all()],
}
def get_inspect_keys(self):
return [
'label',
'slug',
]
def duplicate(self, label=None, agenda_target=None, reset_slug=True):
# clone current desk
new_desk = copy.deepcopy(self)
@ -3476,7 +3598,7 @@ class Desk(models.Model):
).delete() # source was not in settings anymore
class Resource(WithSnapshotMixin, WithApplicationMixin, models.Model):
class Resource(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
# mark temporarily restored snapshots
snapshot = models.ForeignKey(
ResourceSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
@ -3539,8 +3661,11 @@ class Resource(WithSnapshotMixin, WithApplicationMixin, models.Model):
'description': self.description,
}
def get_inspect_keys(self):
return ['label', 'slug', 'description']
class Category(WithSnapshotMixin, WithApplicationMixin, models.Model):
class Category(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
# mark temporarily restored snapshots
snapshot = models.ForeignKey(
CategorySnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
@ -3595,12 +3720,15 @@ class Category(WithSnapshotMixin, WithApplicationMixin, models.Model):
'slug': self.slug,
}
def get_inspect_keys(self):
return ['label', 'slug']
def ics_directory_path(instance, filename):
return f'ics/{str(uuid.uuid4())}/{filename}'
class TimePeriodExceptionSource(models.Model):
class TimePeriodExceptionSource(WithInspectMixin, models.Model):
desk = models.ForeignKey(Desk, on_delete=models.CASCADE, null=True)
unavailability_calendar = models.ForeignKey('UnavailabilityCalendar', on_delete=models.CASCADE, null=True)
ics_filename = models.CharField(null=True, max_length=256)
@ -3871,8 +3999,18 @@ class TimePeriodExceptionSource(models.Model):
'enabled': self.enabled,
}
def get_inspect_keys(self):
return [
'ics_filename',
'ics_file',
'ics_url',
'settings_slug',
'settings_label',
'enabled',
]
class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, models.Model):
class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
# mark temporarily restored snapshots
snapshot = models.ForeignKey(
UnavailabilityCalendarSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
@ -3980,6 +4118,12 @@ class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, models.Mod
return created, unavailability_calendar
def get_inspect_keys(self):
return ['label', 'slug']
def get_permissions_inspect_fields(self):
yield from self.get_inspect_fields(keys=['edit_role', 'view_role'])
class TimePeriodExceptionGroup(models.Model):
unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE)
@ -3994,7 +4138,7 @@ class TimePeriodExceptionGroup(models.Model):
return self.label
class TimePeriodException(models.Model):
class TimePeriodException(WithInspectMixin, models.Model):
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)
@ -4108,6 +4252,13 @@ class TimePeriodException(models.Model):
'update_datetime': export_datetime(self.update_datetime),
}
def get_inspect_keys(self):
return [
'label',
'start_datetime',
'end_datetime',
]
def duplicate(self, desk_target=None, source_target=None):
# clone current exception
new_exception = copy.deepcopy(self)
@ -4199,7 +4350,7 @@ class NotificationType:
return self.settings._meta.get_field(self.name).verbose_name
class AgendaNotificationsSettings(models.Model):
class AgendaNotificationsSettings(WithInspectMixin, models.Model):
EMAIL_FIELD = 'use-email-field'
VIEW_ROLE = 'view-role'
EDIT_ROLE = 'edit-role'
@ -4263,6 +4414,13 @@ class AgendaNotificationsSettings(models.Model):
'cancelled_event_emails': self.cancelled_event_emails,
}
def get_inspect_keys(self):
return [
'almost_full_event',
'full_event',
'cancelled_event',
]
def duplicate(self, agenda_target):
new_settings = copy.deepcopy(self)
new_settings.pk = None
@ -4271,7 +4429,7 @@ class AgendaNotificationsSettings(models.Model):
return new_settings
class AgendaReminderSettings(models.Model):
class AgendaReminderSettings(WithInspectMixin, models.Model):
ONE_DAY_BEFORE = 1
TWO_DAYS_BEFORE = 2
THREE_DAYS_BEFORE = 3
@ -4360,6 +4518,14 @@ class AgendaReminderSettings(models.Model):
'sms_extra_info': self.sms_extra_info,
}
def get_inspect_keys(self):
return [
'days_before_email',
'days_before_sms',
'email_extra_info',
'sms_extra_info',
]
def duplicate(self, agenda_target):
new_settings = copy.deepcopy(self)
new_settings.pk = None

View File

@ -0,0 +1,326 @@
{% extends "chrono/manager_agenda_settings.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans 'Inspect' %}</h2>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-agenda-inspect' pk=agenda.pk %}">{% trans "Inspect" %}</a>
{% endblock %}
{% block content %}
<div class="pk-tabs">
<div class="pk-tabs--tab-list" role="tablist">
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
<button aria-controls="panel-settings" aria-selected="false" id="tab-settings" role="tab" tabindex="-1">{% trans "Settings" %}</button>
<button aria-controls="panel-permissions" aria-selected="false" id="tab-permissions" role="tab" tabindex="-1">{% trans "Permissions" %}</button>
{% if object.kind == 'events' %}
<button aria-controls="panel-events" aria-selected="false" id="tab-events" role="tab" tabindex="-1">{% trans "Events" %}</button>
<button aria-controls="panel-exceptions" aria-selected="false" id="tab-exceptions" role="tab" tabindex="-1">{% trans "Recurrence exceptions" %}</button>
{% elif object.kind == 'meetings' %}
<button aria-controls="panel-meeting-types" aria-selected="false" id="tab-meeting-types" role="tab" tabindex="-1">{% trans "Meeting Types" %}</button>
<button aria-controls="panel-desks" aria-selected="false" id="tab-desks" role="tab" tabindex="-1">{% trans "Desks" %}</button>
<button aria-controls="panel-resources" aria-selected="false" id="tab-resources" role="tab" tabindex="-1">{% trans "Resources" %}</button>
{% elif object.kind == 'virtual' %}
<button aria-controls="panel-agendas" aria-selected="false" id="tab-agendas" role="tab" tabindex="-1">{% trans "Included Agendas" %}</button>
<button aria-controls="panel-time-periods" aria-selected="false" id="tab-time-periods" role="tab" tabindex="-1">{% trans "Exception Periods" %}</button>
{% endif %}
</div>
<div class="pk-tabs--container">
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div aria-labelledby="tab-settings" hidden id="panel-settings" role="tabpanel" tabindex="0">
<div class="section">
{% if object.kind != 'virtual' %}
<h4>{% trans "Display options" %}</h4>
<ul>
{% for label, value in object.get_display_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if object.kind == 'events' %}
<h4>{% trans "Booking check options" %}</h4>
<ul>
{% for label, value in object.get_booking_check_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if object.kind == 'events' %}
{% if agenda.partial_bookings %}
<h4>{% trans "Invoicing options" %}</h4>
<ul>
{% for label, value in object.get_invoicing_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% else %}
<h4>{% trans "Management notifications" %}</h4>
<ul>
{% for label, value in object.get_notifications_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% if object.kind != 'virtual' and not object.partial_bookings %}
<h4>{% trans "Booking reminders" %}</h4>
<ul>
{% for label, value in object.get_reminder_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endif %}
<h4>{% trans "Booking Delays" %}</h4>
<ul>
{% for label, value in object.get_booking_delays_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div aria-labelledby="tab-permissions" hidden id="panel-permissions" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_permissions_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
{% if object.kind == 'events' %}
<div aria-labelledby="tab-events" hidden id="panel-events" role="tabpanel" tabindex="0">
<div class="section">
{% for event in object.event_set.all %}
<h4>{{ event }}</h4>
<ul>
{% for label, value in event.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
<div aria-labelledby="tab-exceptions" hidden id="panel-exceptions" role="tabpanel" tabindex="0">
<div class="section">
{% for desk in object.desk_set.all %}{% if desk.slug == '_exceptions_holder' %}
<h4>{% trans "Unavailability calendars" %}</h4>
<ul>
{% for unavailability_calendar in desk.unavailability_calendars.all %}
<li class="parameter-unavailability-calendar }}">
{{ unavailability_calendar }}
</li>
{% endfor %}
</ul>
<h4>{% trans "Exception sources" %}</h4>
{% for source in desk.timeperiodexceptionsource_set.all %}
<h5>{{ source }}</h5>
<ul>
{% for label, value in source.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
<h4>{% trans "Exceptions" %}</h4>
{% for exception in desk.timeperiodexception_set.all %}
<h5>{{ exception }}</h5>
<ul>
{% for label, value in exception.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
{% endif %}{% endfor %}
</div>
</div>
{% elif object.kind == 'meetings' %}
<div aria-labelledby="tab-meeting-types" hidden id="panel-meeting-types" role="tabpanel" tabindex="0">
<div class="section">
{% for meeting_type in object.meetingtype_set.all %}
<h4>{{ meeting_type }}</h4>
<ul>
{% for label, value in meeting_type.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
<div aria-labelledby="tab-desks" hidden id="panel-desks" role="tabpanel" tabindex="0">
<div class="section">
{% for desk in object.desk_set.all %}
<h4>{{ desk }}</h4>
<ul>
{% for label, value in desk.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
<h5>{% trans "Opening hours" %}</h5>
{% for time_period in desk.timeperiod_set.all %}
<h6>{{ time_period }}</h6>
<ul>
{% for label, value in time_period.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
<h5>{% trans "Unavailability calendars" %}</h5>
<ul>
{% for unavailability_calendar in desk.unavailability_calendars.all %}
<li class="parameter-unavailability-calendar }}">
{{ unavailability_calendar }}
</li>
{% endfor %}
</ul>
<h5>{% trans "Exception sources" %}</h5>
{% for source in desk.timeperiodexceptionsource_set.all %}
<h6>{{ source }}</h6>
<ul>
{% for label, value in source.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
<h5>{% trans "Exceptions" %}</h5>
{% for exception in desk.timeperiodexception_set.all %}
<h6>{{ exception }}</h6>
<ul>
{% for label, value in exception.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
</div>
</div>
<div aria-labelledby="tab-resources" hidden id="panel-resources" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for resource in object.resources.all %}
<li class="parameter-resource }}">
{{ resource }}
</li>
{% endfor %}
</ul>
</div>
</div>
{% elif object.kind == "virtual" %}
<div aria-labelledby="tab-agendas" hidden id="panel-agendas" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for agenda in object.real_agendas.all %}
<li class="parameter-agenda }}">
{{ agenda }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div aria-labelledby="tab-time-periods" hidden id="panel-time-periods" role="tabpanel" tabindex="0">
<div class="section">
{% for time_period in object.excluded_timeperiods.all %}
<h4>{{ time_period }}</h4>
<ul>
{% for label, value in time_period.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block sidebar %}
{% endblock %}

View File

@ -120,10 +120,11 @@
<a class="button button-paragraph" rel="popup" class="action-duplicate" href="{% url 'chrono-manager-agenda-duplicate' pk=object.pk %}">{% trans 'Duplicate' %}</a>
{% endif %}
<h3>{% trans "Navigation" %}</h3>
{% if show_history %}
<h3>{% trans "Navigation" %}</h3>
<a class="button button-paragraph" href="{% url 'chrono-manager-agenda-history' pk=agenda.pk %}">{% trans 'History' %}</a>
{% endif %}
<a class="button button-paragraph" href="{% url 'chrono-manager-agenda-inspect' pk=agenda.pk %}">{% trans 'Inspect' %}</a>
{% block agenda-extra-navigation-actions %}{% endblock %}
{% url 'chrono-manager-homepage' as object_list_url %}

View File

@ -20,9 +20,12 @@
{% else %}
<h2>{% trans "New Category" %}</h2>
{% endif %}
{% if show_history and category %}
{% if category %}
<span class="actions">
<a href="{% url 'chrono-manager-category-history' pk=category.pk %}">{% trans 'History' %}</a>
<a href="{% url 'chrono-manager-category-inspect' pk=category.pk %}">{% trans 'Inspect' %}</a>
{% if show_history %}
<a href="{% url 'chrono-manager-category-history' pk=category.pk %}">{% trans 'History' %}</a>
{% endif %}
</span>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,37 @@
{% extends "chrono/manager_category_form.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans 'Inspect' %}</h2>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-category-inspect' pk=category.pk %}">{% trans "Inspect" %}</a>
{% endblock %}
{% block content %}
<div class="pk-tabs">
<div class="pk-tabs--tab-list" role="tablist">
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
</div>
<div class="pk-tabs--container">
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block sidebar %}
{% endblock %}

View File

@ -8,9 +8,6 @@
{% block agenda-extra-navigation-actions %}
{% with lingo_url=object.get_lingo_url %}{% if lingo_url %}
{% if not show_history %}
<h3>{% trans "Navigation" %}</h3>
{% endif %}
<a class="button button-paragraph" href="{{ lingo_url }}">{% trans 'Pricing' context 'pricing' %}</a>
{% endif %}{% endwith %}
{% endblock %}

View File

@ -20,9 +20,12 @@
{% else %}
<h2>{% trans "New events type" %}</h2>
{% endif %}
{% if show_history and object.pk %}
{% if object.pk %}
<span class="actions">
<a href="{% url 'chrono-manager-events-type-history' pk=object.pk %}">{% trans 'History' %}</a>
<a href="{% url 'chrono-manager-events-type-inspect' pk=object.pk %}">{% trans 'Inspect' %}</a>
{% if show_history %}
<a href="{% url 'chrono-manager-events-type-history' pk=object.pk %}">{% trans 'History' %}</a>
{% endif %}
</span>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,63 @@
{% extends "chrono/manager_events_type_form.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans 'Inspect' %}</h2>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-events-type-inspect' pk=events_type.pk %}">{% trans "Inspect" %}</a>
{% endblock %}
{% block content %}
<div class="pk-tabs">
<div class="pk-tabs--tab-list" role="tablist">
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
<button aria-controls="panel-custom-fields" aria-selected="false" id="tab-custom-fields" role="tab" tabindex="-1">{% trans "Custom fields" %}</button>
</div>
<div class="pk-tabs--container">
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div aria-labelledby="tab-custom-fields" hidden id="panel-custom-fields" role="tabpanel" tabindex="0">
<div class="section">
{% for value in object.get_custom_fields %}
<h4>{{ value.label }}</h4>
<ul>
<li class="parameter-varname">
<span class="parameter">{% trans "Field slug:" %}</span>
{{ value.varname }}
</li>
<li class="parameter-label">
<span class="parameter">{% trans "Field label:" %}</span>
{{ value.label }}
</li>
<li class="parameter-field-type">
<span class="parameter">{% trans "Field type:" %}</span>
{% if value.field_type == 'text' %}{% trans "Text" %}{% endif %}
{% if value.field_type == 'textarea' %}{% trans "Textarea" %}{% endif %}
{% if value.field_type == 'textbool' %}{% trans "Boolean" %}{% endif %}
</li>
</ul>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block sidebar %}
{% endblock %}

View File

@ -63,8 +63,9 @@
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-resource-edit' pk=resource.pk %}">{% trans 'Edit' %}</a>
{% endif %}
<h3>{% trans "Navigation" %}</h3>
<a class="button button-paragraph" href="{% url 'chrono-manager-resource-inspect' pk=resource.pk %}">{% trans 'Inspect' %}</a>
{% if show_history %}
<h3>{% trans "Navigation" %}</h3>
<a class="button button-paragraph" href="{% url 'chrono-manager-resource-history' pk=resource.pk %}">{% trans 'History' %}</a>
{% endif %}

View File

@ -0,0 +1,38 @@
{% extends "chrono/manager_resource_detail.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans 'Inspect' %}</h2>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-resource-inspect' pk=resource.pk %}">{% trans "Inspect" %}</a>
{% endblock %}
{% block content %}
<div class="pk-tabs">
<div class="pk-tabs--tab-list" role="tablist">
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
</div>
<div class="pk-tabs--container">
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block sidebar %}
{% endblock %}

View File

@ -0,0 +1,69 @@
{% extends "chrono/manager_unavailability_calendar_settings.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans 'Inspect' %}</h2>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-unavailability-calendar-inspect' pk=unavailability_calendar.pk %}">{% trans "Inspect" %}</a>
{% endblock %}
{% block content %}
<div class="pk-tabs">
<div class="pk-tabs--tab-list" role="tablist">
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
<button aria-controls="panel-permissions" aria-selected="false" id="tab-permissions" role="tab" tabindex="-1">{% trans "Permissions" %}</button>
<button aria-controls="panel-exceptions" aria-selected="false" id="tab-exceptions" role="tab" tabindex="-1">{% trans "Exceptions" %}</button>
</div>
<div class="pk-tabs--container">
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div aria-labelledby="tab-permissions" hidden id="panel-permissions" role="tabpanel" tabindex="0">
<div class="section">
<ul>
{% for label, value in object.get_permissions_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div aria-labelledby="tab-exceptions" hidden id="panel-exceptions" role="tabpanel" tabindex="0">
<div class="section">
{% for exception in object.timeperiodexception_set.all %}
<h4>{{ exception }}</h4>
<ul>
{% for label, value in exception.get_inspect_fields %}
<li class="parameter-{{ label|slugify }}">
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
{{ value }}
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block sidebar %}
{% endblock %}

View File

@ -54,10 +54,11 @@
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-unavailability-calendar-delete' pk=unavailability_calendar.id %}">{% trans 'Delete' %}</a>
{% endif %}
<h3>{% trans "Navigation" %}</h3>
{% if show_history %}
<h3>{% trans "Navigation" %}</h3>
<a class="button button-paragraph" href="{% url 'chrono-manager-unavailability-calendar-history' pk=unavailability_calendar.pk %}">{% trans 'History' %}</a>
{% endif %}
<a class="button button-paragraph" href="{% url 'chrono-manager-unavailability-calendar-inspect' pk=unavailability_calendar.pk %}">{% trans 'Inspect' %}</a>
{% url 'chrono-manager-unavailability-calendar-list' as object_list_url %}
{% include 'chrono/includes/application_detail_fragment.html' %}

View File

@ -65,6 +65,11 @@ urlpatterns = [
views.unavailability_calendar_import_unavailabilities,
name='chrono-manager-unavailability-calendar-import-unavailabilities',
),
path(
'unavailability-calendar/<int:pk>/inspect/',
views.unavailability_calendar_inspect,
name='chrono-manager-unavailability-calendar-inspect',
),
path(
'unavailability-calendar/<int:pk>/history/',
views.unavailability_calendar_history,
@ -100,6 +105,7 @@ urlpatterns = [
),
path('resource/<int:pk>/edit/', views.resource_edit, name='chrono-manager-resource-edit'),
path('resource/<int:pk>/delete/', views.resource_delete, name='chrono-manager-resource-delete'),
path('resource/<int:pk>/inspect/', views.resource_inspect, name='chrono-manager-resource-inspect'),
path('resource/<int:pk>/history/', views.resource_history, name='chrono-manager-resource-history'),
path(
'resource/<int:pk>/history/compare/',
@ -110,6 +116,7 @@ urlpatterns = [
path('category/add/', views.category_add, name='chrono-manager-category-add'),
path('category/<int:pk>/edit/', views.category_edit, name='chrono-manager-category-edit'),
path('category/<int:pk>/delete/', views.category_delete, name='chrono-manager-category-delete'),
path('category/<int:pk>/inspect/', views.category_inspect, name='chrono-manager-category-inspect'),
path('category/<int:pk>/history/', views.category_history, name='chrono-manager-category-history'),
path(
'category/<int:pk>/history/compare/',
@ -124,6 +131,9 @@ urlpatterns = [
views.events_type_delete,
name='chrono-manager-events-type-delete',
),
path(
'events-type/<int:pk>/inspect/', views.events_type_inspect, name='chrono-manager-events-type-inspect'
),
path(
'events-type/<int:pk>/history/', views.events_type_history, name='chrono-manager-events-type-history'
),
@ -479,6 +489,7 @@ urlpatterns = [
views.agenda_import_events_sample_csv,
name='chrono-manager-sample-events-csv',
),
path('agendas/<int:pk>/inspect/', views.agenda_inspect, name='chrono-manager-agenda-inspect'),
path('agendas/<int:pk>/history/', views.agenda_history, name='chrono-manager-agenda-history'),
path(
'agendas/<int:pk>/history/compare/',

View File

@ -30,7 +30,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, models, transaction
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Max, Min, Q, Value
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Max, Min, Prefetch, Q, Value
from django.db.models.deletion import ProtectedError
from django.db.models.functions import Cast
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect
@ -800,6 +800,19 @@ class ResourceDeleteView(DeleteView):
resource_delete = ResourceDeleteView.as_view()
class ResourceInspectView(DetailView):
template_name = 'chrono/manager_resource_inspect.html'
model = Resource
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
resource_inspect = ResourceInspectView.as_view()
class ResourceHistoryView(InstanceWithSnapshotHistoryView):
template_name = 'chrono/manager_resource_history.html'
model = ResourceSnapshot
@ -918,6 +931,19 @@ class CategoryDeleteView(DeleteView):
category_delete = CategoryDeleteView.as_view()
class CategoryInspectView(DetailView):
template_name = 'chrono/manager_category_inspect.html'
model = Category
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
category_inspect = CategoryInspectView.as_view()
class CategoryHistoryView(InstanceWithSnapshotHistoryView):
template_name = 'chrono/manager_category_history.html'
model = CategorySnapshot
@ -1092,6 +1118,20 @@ class EventsTypeDeleteView(DeleteView):
events_type_delete = EventsTypeDeleteView.as_view()
class EventsTypeInspectView(DetailView):
template_name = 'chrono/manager_events_type_inspect.html'
model = EventsType
context_object_name = 'events_type'
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
raise PermissionDenied()
return super().dispatch(request, *args, **kwargs)
events_type_inspect = EventsTypeInspectView.as_view()
class EventsTypeHistoryView(InstanceWithSnapshotHistoryView):
template_name = 'chrono/manager_events_type_history.html'
model = EventsTypeSnapshot
@ -4155,6 +4195,37 @@ class TimePeriodExceptionSourceRefreshView(ManagedTimePeriodExceptionMixin, Deta
time_period_exception_source_refresh = TimePeriodExceptionSourceRefreshView.as_view()
class AgendaInspectView(ManagedAgendaMixin, DetailView):
template_name = 'chrono/manager_agenda_inspect.html'
model = Agenda
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(
Agenda.objects.select_related(
'category', 'events_type', 'edit_role', 'view_role'
).prefetch_related(
'resources',
Prefetch(
'desk_set',
queryset=Desk.objects.prefetch_related(
'timeperiod_set',
'timeperiodexception_set',
'timeperiodexceptionsource_set',
'unavailability_calendars',
),
),
Prefetch('event_set', queryset=Event.objects.filter(primary_event__isnull=True)),
),
id=kwargs.get('pk'),
)
def get_object(self):
return self.agenda
agenda_inspect = AgendaInspectView.as_view()
class AgendaHistoryView(ManagedAgendaMixin, InstanceWithSnapshotHistoryView):
template_name = 'chrono/manager_agenda_history.html'
model = AgendaSnapshot
@ -4792,6 +4863,23 @@ class UnavailabilityCalendarImportUnavailabilitiesView(ManagedUnavailabilityCale
unavailability_calendar_import_unavailabilities = UnavailabilityCalendarImportUnavailabilitiesView.as_view()
class UnavailabilityCalendarInspectView(ManagedUnavailabilityCalendarMixin, DetailView):
template_name = 'chrono/manager_unavailability_calendar_inspect.html'
model = UnavailabilityCalendar
context_object_name = 'unavailability_calendar'
def set_unavailability_calendar(self, **kwargs):
self.unavailability_calendar = get_object_or_404(
UnavailabilityCalendar.objects.select_related('edit_role', 'view_role'), pk=kwargs.get('pk')
)
def get_object(self):
return self.unavailability_calendar
unavailability_calendar_inspect = UnavailabilityCalendarInspectView.as_view()
class UnavailabilityCalendarHistoryView(ManagedUnavailabilityCalendarMixin, InstanceWithSnapshotHistoryView):
template_name = 'chrono/manager_unavailability_calendar_history.html'
model = UnavailabilityCalendarSnapshot

View File

@ -13,14 +13,17 @@ from django.test.utils import CaptureQueriesContext
from chrono.agendas.models import (
Agenda,
AgendaNotificationsSettings,
AgendaReminderSettings,
Booking,
Desk,
Event,
EventsType,
MeetingType,
Resource,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
UnavailabilityCalendar,
VirtualMember,
)
@ -800,6 +803,78 @@ def test_options_agenda_as_manager(app, manager_user):
assert '<h2>Settings' in resp.text
def test_inspect_agenda(app, admin_user):
meetings_agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
meetings_agenda.resources.add(Resource.objects.create(slug='foo', label='Foo'))
desk = Desk.objects.create(slug='foo', label='Foo', agenda=meetings_agenda)
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
desk.unavailability_calendars.add(unavailability_calendar)
MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30)
tpx_start = make_aware(datetime.datetime(2017, 5, 22, 8, 0))
tpx_end = make_aware(datetime.datetime(2017, 5, 22, 12, 30))
TimePeriodException.objects.create(desk=desk, start_datetime=tpx_start, end_datetime=tpx_end)
TimePeriod.objects.create(
desk=desk, weekday=2, start_time=tpx_start.time(), end_time=tpx_end.time(), weekday_indexes=[1, 3]
)
TimePeriod.objects.create(
desk=desk, date=datetime.date(2022, 10, 24), start_time=tpx_start.time(), end_time=tpx_end.time()
)
TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
AgendaNotificationsSettings.objects.create(
agenda=meetings_agenda,
full_event=AgendaNotificationsSettings.EMAIL_FIELD,
full_event_emails=['hop@entrouvert.com', 'top@entrouvert.com'],
)
AgendaReminderSettings.objects.create(agenda=meetings_agenda, days_before_email=1, email_extra_info='top')
events_agenda = Agenda.objects.create(label='Events', kind='events')
Event.objects.create(
agenda=events_agenda, start_datetime=make_aware(datetime.datetime(2020, 7, 21, 16, 42, 35)), places=10
)
exceptions_desk = Desk.objects.create(agenda=events_agenda, slug='_exceptions_holder')
tpx_start = make_aware(datetime.datetime(2017, 5, 22, 8, 0))
tpx_end = make_aware(datetime.datetime(2017, 5, 22, 12, 30))
TimePeriodException.objects.create(desk=exceptions_desk, start_datetime=tpx_start, end_datetime=tpx_end)
exceptions_desk.unavailability_calendars.add(unavailability_calendar)
virtual_agenda = Agenda.objects.create(label='Virtual', kind='virtual')
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda)
TimePeriod.objects.create(
agenda=virtual_agenda, weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % meetings_agenda.pk)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 12
resp = app.get('/manage/agendas/%s/settings' % events_agenda.pk)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 12
resp = app.get('/manage/agendas/%s/settings' % virtual_agenda.pk)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 8
def test_inspect_agenda_as_manager(app, manager_user):
agenda = Agenda.objects.create(slug='foo', label='Foo')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
app = login(app, username='manager', password='manager')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
app.get('/manage/agendas/%s/inspect/' % agenda.pk, status=403)
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
app.get('/manage/agendas/%s/inspect/' % agenda.pk, status=200)
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management')
def test_agenda_options_desk_simple_management(available_mock, app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')

View File

@ -1,4 +1,6 @@
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from chrono.agendas.models import Agenda, Category
from chrono.apps.snapshot.models import CategorySnapshot
@ -84,3 +86,14 @@ def test_delete_category_as_manager(app, manager_user):
category = Category.objects.create(label='Foo bar')
app = login(app, username='manager', password='manager')
app.get('/manage/category/%s/delete/' % category.pk, status=403)
def test_inspect_category(app, admin_user):
category = Category.objects.create(label='Foo bar')
app = login(app)
resp = app.get('/manage/category/%s/edit/' % category.pk)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 3

View File

@ -1,4 +1,6 @@
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from chrono.agendas.models import Agenda, EventsType
from chrono.apps.snapshot.models import EventsTypeSnapshot
@ -212,3 +214,14 @@ def test_delete_events_type_as_manager(app, manager_user):
events_type = EventsType.objects.create(label='Foo bar')
app = login(app, username='manager', password='manager')
app.get('/manage/events-type/%s/delete/' % events_type.pk, status=403)
def test_inspect_events_type(app, admin_user):
events_type = EventsType.objects.create(label='Foo bar')
app = login(app)
resp = app.get('/manage/events-type/%s/edit/' % events_type.pk)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 3

View File

@ -1043,3 +1043,14 @@ def test_resource_today_button(app, admin_user):
resp = app.get('/manage/resource/%s/week/%s/%s/%s/' % (resource.pk, today.year, today.month, today.day))
assert 'Today' not in resp.pyquery('a.active').text()
def test_inspect_resource(app, admin_user):
resource = Resource.objects.create(label='Foo bar')
app = login(app)
resp = app.get('/manage/resource/%s/' % resource.pk)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 3

View File

@ -3,6 +3,8 @@ import os
from unittest import mock
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from webtest import Upload
from chrono.agendas.models import (
@ -709,3 +711,32 @@ def test_unavailability_calendar_delete_unavailability_permissions(app, manager_
unavailability_calendar.edit_role = group
unavailability_calendar.save()
app.get(url)
def test_inspect_unavailability_calendar(app, admin_user):
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
TimePeriodException.objects.create(
unavailability_calendar=unavailability_calendar,
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)
with CaptureQueriesContext(connection) as ctx:
resp = resp.click('Inspect')
assert len(ctx.captured_queries) == 4
def test_inspect_unavailability_calendar_as_manager(app, manager_user):
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
app = login(app, username='manager', password='manager')
unavailability_calendar.view_role = manager_user.groups.all()[0]
unavailability_calendar.save()
app.get('/manage/unavailability-calendar/%s/inspect/' % unavailability_calendar.pk, status=403)
unavailability_calendar.edit_role = manager_user.groups.all()[0]
unavailability_calendar.save()
app.get('/manage/unavailability-calendar/%s/inspect/' % unavailability_calendar.pk, status=200)