This commit is contained in:
parent
3071fab8f8
commit
895758c70c
|
@ -76,6 +76,7 @@ from django.utils.translation import gettext
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext, pgettext_lazy
|
||||
|
||||
from chrono.apps.export_import.models import WithApplicationMixin
|
||||
from chrono.utils.date import get_weekday_index
|
||||
from chrono.utils.db import ArraySubquery, SumCardinality
|
||||
from chrono.utils.interval import Interval, IntervalSet
|
||||
|
@ -172,7 +173,7 @@ TimeSlot = collections.namedtuple(
|
|||
)
|
||||
|
||||
|
||||
class Agenda(models.Model):
|
||||
class Agenda(WithApplicationMixin, models.Model):
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
kind = models.CharField(_('Kind'), max_length=20, choices=AGENDA_KINDS, default='events')
|
||||
|
@ -2809,7 +2810,7 @@ class Event(models.Model):
|
|||
return custom_fields
|
||||
|
||||
|
||||
class EventsType(models.Model):
|
||||
class EventsType(WithApplicationMixin, models.Model):
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
custom_fields = models.JSONField(blank=True, default=list)
|
||||
|
@ -3424,7 +3425,7 @@ class Desk(models.Model):
|
|||
).delete() # source was not in settings anymore
|
||||
|
||||
|
||||
class Resource(models.Model):
|
||||
class Resource(WithApplicationMixin, models.Model):
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
description = models.TextField(_('Description'), blank=True, help_text=_('Optional description.'))
|
||||
|
@ -3472,7 +3473,7 @@ class Resource(models.Model):
|
|||
}
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
class Category(WithApplicationMixin, models.Model):
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
|
||||
|
@ -3788,7 +3789,7 @@ class TimePeriodExceptionSource(models.Model):
|
|||
}
|
||||
|
||||
|
||||
class UnavailabilityCalendar(models.Model):
|
||||
class UnavailabilityCalendar(WithApplicationMixin, 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')
|
||||
|
|
|
@ -21,6 +21,14 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from django.db import models
|
||||
|
||||
|
||||
class WithApplicationMixin:
|
||||
@property
|
||||
def applications(self):
|
||||
if getattr(self, '_applications', None) is None:
|
||||
Application.load_for_object(self)
|
||||
return self._applications
|
||||
|
||||
|
||||
class Application(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField(max_length=100, unique=True)
|
||||
|
@ -71,34 +79,23 @@ class Application(models.Model):
|
|||
@classmethod
|
||||
def populate_objects(cls, object_class, objects):
|
||||
content_type = ContentType.objects.get_for_model(object_class)
|
||||
elements = ApplicationElement.objects.filter(content_type=content_type)
|
||||
elements = ApplicationElement.objects.filter(
|
||||
content_type=content_type, application__visible=True
|
||||
).prefetch_related('application')
|
||||
elements_by_objects = collections.defaultdict(list)
|
||||
for element in elements:
|
||||
elements_by_objects[element.content_object].append(element)
|
||||
applications_by_ids = {
|
||||
a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True)
|
||||
}
|
||||
elements_by_objects[element.object_id].append(element)
|
||||
for obj in objects:
|
||||
applications = []
|
||||
elements = elements_by_objects.get(obj) or []
|
||||
for element in elements:
|
||||
application = applications_by_ids.get(element.application_id)
|
||||
if application:
|
||||
applications.append(application)
|
||||
applications = [element.application for element in elements_by_objects.get(obj.pk) or []]
|
||||
obj._applications = sorted(applications, key=lambda a: a.name)
|
||||
|
||||
@classmethod
|
||||
def load_for_object(cls, obj):
|
||||
content_type = ContentType.objects.get_for_model(obj.__class__)
|
||||
elements = ApplicationElement.objects.filter(content_type=content_type, object_id=obj.pk)
|
||||
applications_by_ids = {
|
||||
a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True)
|
||||
}
|
||||
applications = []
|
||||
for element in elements:
|
||||
application = applications_by_ids.get(element.application_id)
|
||||
if application:
|
||||
applications.append(application)
|
||||
elements = ApplicationElement.objects.filter(
|
||||
content_type=content_type, object_id=obj.pk, application__visible=True
|
||||
).prefetch_related('application')
|
||||
applications = [element.application for element in elements]
|
||||
obj._applications = sorted(applications, key=lambda a: a.name)
|
||||
|
||||
def get_objects_for_object_class(self, object_class):
|
||||
|
@ -106,6 +103,12 @@ class Application(models.Model):
|
|||
elements = ApplicationElement.objects.filter(content_type=content_type, application=self)
|
||||
return object_class.objects.filter(pk__in=elements.values('object_id'))
|
||||
|
||||
@classmethod
|
||||
def get_orphan_objects_for_object_class(cls, object_class):
|
||||
content_type = ContentType.objects.get_for_model(object_class)
|
||||
elements = ApplicationElement.objects.filter(content_type=content_type, application__visible=True)
|
||||
return object_class.objects.exclude(pk__in=elements.values('object_id'))
|
||||
|
||||
|
||||
class ApplicationElement(models.Model):
|
||||
application = models.ForeignKey(Application, on_delete=models.CASCADE)
|
||||
|
|
|
@ -952,3 +952,7 @@ a.button.button-paragraph {
|
|||
padding-top: 0.8em;
|
||||
padding-bottom: 0.8em;
|
||||
}
|
||||
|
||||
.application-logo, .application-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{% load thumbnail %}
|
||||
{% if application %}
|
||||
<h2>
|
||||
{% thumbnail application.icon '64x64' format='PNG' as im %}
|
||||
<img src="{{ im.url }}" alt="" class="application-logo" />
|
||||
{% endthumbnail %}
|
||||
{{ application }}
|
||||
</h2>
|
||||
{% elif no_application %}
|
||||
<h2>{{ title_no_application }}</h2>
|
||||
{% else %}
|
||||
<h2>{{ title_object_list }}</h2>
|
||||
{% endif %}
|
|
@ -0,0 +1,5 @@
|
|||
{% if application %}
|
||||
<a href="{{ object_list_url }}?application={{ application.slug }}">{{ application }}</a>
|
||||
{% elif no_application %}
|
||||
<a href="{{ object_list_url }}?no-application">{{ title_no_application }}</a>
|
||||
{% endif %}
|
|
@ -0,0 +1,12 @@
|
|||
{% load i18n thumbnail %}
|
||||
{% if object.applications %}
|
||||
<h3>{% trans "Applications" %}</h3>
|
||||
{% for application in object.applications %}
|
||||
<a class="button button-paragraph" href="{{ object_list_url }}?application={{ application.slug }}">
|
||||
{% thumbnail application.icon '16x16' format='PNG' as im %}
|
||||
<img src="{{ im.url }}" alt="" class="application-icon" width="16" />
|
||||
{% endthumbnail %}
|
||||
{{ application }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
|
@ -0,0 +1,8 @@
|
|||
{% load thumbnail %}
|
||||
{% if not application and not no_application %}
|
||||
{% for application in object.applications %}
|
||||
{% thumbnail application.icon '16x16' format='PNG' as im %}
|
||||
<img src="{{ im.url }}" alt="" class="application-icon" width="16" />
|
||||
{% endthumbnail %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
|
@ -0,0 +1,15 @@
|
|||
{% load i18n thumbnail %}
|
||||
{% if applications %}
|
||||
<h3>{% trans "Applications" %}</h3>
|
||||
{% for application in applications %}
|
||||
<a class="button button-paragraph" href="?application={{ application.slug }}">
|
||||
{% thumbnail application.icon '16x16' format='PNG' as im %}
|
||||
<img src="{{ im.url }}" alt="" class="application-icon" width="16" />
|
||||
{% endthumbnail %}
|
||||
{{ application }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<a class="button button-paragraph" href="?no-application">
|
||||
{{ title_no_application }}
|
||||
</a>
|
||||
{% endif %}
|
|
@ -121,5 +121,8 @@
|
|||
{% endif %}
|
||||
|
||||
{% block agenda-extra-navigation-actions %}{% endblock %}
|
||||
|
||||
{% url 'chrono-manager-homepage' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,27 +7,31 @@
|
|||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-category-list' %}">{% trans "Categories" %}</a>
|
||||
{% url 'chrono-manager-category-list' as object_list_url %}
|
||||
<a href="{{ object_list_url }}">{% trans "Categories" %}</a>
|
||||
{% include 'chrono/includes/application_breadcrumb_fragment.html' with title_no_application=_('Categories outside applications') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Categories' %}</h2>
|
||||
{% include 'chrono/includes/application_appbar_fragment.html' with title_no_application=_('Categories outside applications') title_object_list=_('Categories') %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% if object_list %}
|
||||
<div>
|
||||
<ul class="objects-list single-links">
|
||||
{% for object in object_list %}
|
||||
<li>
|
||||
<a href="{% url 'chrono-manager-category-edit' pk=object.pk %}">{{ object.label }} ({{ object.slug }})</a>
|
||||
<a href="{% url 'chrono-manager-category-edit' pk=object.pk %}">
|
||||
{% include 'chrono/includes/application_icon_fragment.html' %}
|
||||
{{ object.label }} ({{ object.slug }})
|
||||
</a>
|
||||
<a rel="popup" class="delete" href="{% url 'chrono-manager-category-delete' pk=object.id %}">{% trans "remove" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{% elif not no_application %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This site doesn't have any category yet. Click on the "New" button in the top
|
||||
|
@ -38,8 +42,12 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<aside id="sidebar">
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-category-add' %}">{% trans 'New category' %}</a>
|
||||
</aside>
|
||||
{% if not application and not no_application %}
|
||||
<aside id="sidebar">
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-category-add' %}">{% trans 'New category' %}</a>
|
||||
|
||||
{% include 'chrono/includes/application_list_fragment.html' with title_no_application=_('Categories outside applications') %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-events-type-list' %}">{% trans "Events types" %}</a>
|
||||
{% url 'chrono-manager-events-type-list' as object_list_url %}
|
||||
<a href="{{ object_list_url }}">{% trans "Events types" %}</a>
|
||||
{% include 'chrono/includes/application_breadcrumb_fragment.html' with title_no_application=_('Events types outside applications') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Events types' %}</h2>
|
||||
{% include 'chrono/includes/application_appbar_fragment.html' with title_no_application=_('Events types outside applications') title_object_list=_('Events types') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -23,13 +25,16 @@
|
|||
<ul class="objects-list single-links">
|
||||
{% for object in object_list %}
|
||||
<li>
|
||||
<a href="{% url 'chrono-manager-events-type-edit' pk=object.pk %}">{{ object.label }} ({{ object.slug }})</a>
|
||||
<a href="{% url 'chrono-manager-events-type-edit' pk=object.pk %}">
|
||||
{% include 'chrono/includes/application_icon_fragment.html' %}
|
||||
{{ object.label }} ({{ object.slug }})
|
||||
</a>
|
||||
<a rel="popup" class="delete" href="{% url 'chrono-manager-events-type-delete' pk=object.pk %}">{% trans "remove" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{% elif not no_application %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This site doesn't have any events type yet. Click on the "New" button in the top
|
||||
|
@ -40,8 +45,12 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<aside id="sidebar">
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-events-type-add' %}">{% trans 'New events type' %}</a>
|
||||
</aside>
|
||||
{% if not application and not no_application %}
|
||||
<aside id="sidebar">
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-events-type-add' %}">{% trans 'New events type' %}</a>
|
||||
|
||||
{% include 'chrono/includes/application_list_fragment.html' with title_no_application=_('Events types outside applications') %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
{% extends "chrono/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load i18n thumbnail %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Agendas' %}</h2>
|
||||
{% include 'chrono/includes/application_appbar_fragment.html' with title_no_application=_('Agendas outside applications') title_object_list=_('Agendas') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
{% url 'chrono-manager-homepage' as object_list_url %}
|
||||
{% include 'chrono/includes/application_breadcrumb_fragment.html' with title_no_application=_('Agendas outside applications') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -14,12 +20,18 @@
|
|||
{% if group.grouper %}<h3>{{ group.grouper }}</h3>{% elif not forloop.first %}<h3>{% trans "Misc" %}</h3>{% endif %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for object in group.list %}
|
||||
<li><a href="{% url 'chrono-manager-agenda-view' pk=object.id %}"><span class="badge">{{ object.get_real_kind_display }}</span> {{ object.label }}{% if user.is_staff %} <span class="identifier">[{% trans "identifier:" %} {{ object.slug }}]{% endif %}</span></a></li>
|
||||
<li>
|
||||
<a href="{% url 'chrono-manager-agenda-view' pk=object.id %}">
|
||||
<span class="badge">{{ object.get_real_kind_display }}</span>
|
||||
{% include 'chrono/includes/application_icon_fragment.html' %}
|
||||
{{ object.label }}{% if user.is_staff %} <span class="identifier">[{% trans "identifier:" %} {{ object.slug }}]{% endif %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% elif not no_application %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This site doesn't have any agenda yet. Click on the "New" button in the top
|
||||
|
@ -31,7 +43,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% if with_sidebar %}
|
||||
{% if with_sidebar and not application and not no_application %}
|
||||
<aside id="sidebar">
|
||||
|
||||
{% if user.is_staff %}
|
||||
|
@ -60,6 +72,8 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% include 'chrono/includes/application_list_fragment.html' with title_no_application=_('Agendas outside applications') %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -62,5 +62,8 @@
|
|||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-resource-edit' pk=resource.pk %}">{% trans 'Edit' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% url 'chrono-manager-resource-list' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-resource-list' %}">{% trans "Resources" %}</a>
|
||||
{% url 'chrono-manager-resource-list' as object_list_url %}
|
||||
<a href="{{ object_list_url }}">{% trans "Resources" %}</a>
|
||||
{% include 'chrono/includes/application_breadcrumb_fragment.html' with title_no_application=_('Resources outside applications') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Resources' %}</h2>
|
||||
{% include 'chrono/includes/application_appbar_fragment.html' with title_no_application=_('Resources outside applications') title_object_list=_('Resources') %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
@ -21,12 +23,15 @@
|
|||
<ul class="objects-list single-links">
|
||||
{% for object in object_list %}
|
||||
<li>
|
||||
<a href="{% url 'chrono-manager-resource-view' pk=object.pk %}">{{ object.label }} ({{ object.slug }})</a>
|
||||
<a href="{% url 'chrono-manager-resource-view' pk=object.pk %}">
|
||||
{% include 'chrono/includes/application_icon_fragment.html' %}
|
||||
{{ object.label }} ({{ object.slug }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{% elif not no_application %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This site doesn't have any resource yet. Click on the "New" button in the top
|
||||
|
@ -37,8 +42,12 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<aside id="sidebar">
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-resource-add' %}">{% trans 'New resource' %}</a>
|
||||
</aside>
|
||||
{% if not application and not no_application %}
|
||||
<aside id="sidebar">
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-resource-add' %}">{% trans 'New resource' %}</a>
|
||||
|
||||
{% include 'chrono/includes/application_list_fragment.html' with title_no_application=_('Resources outside applications') %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-list' %}">{% trans "Unavailability Calendars" %}</a>
|
||||
{% url 'chrono-manager-unavailability-calendar-list' as object_list_url %}
|
||||
<a href="{{ object_list_url }}">{% trans "Unavailability Calendars" %}</a>
|
||||
{% include 'chrono/includes/application_breadcrumb_fragment.html' with title_no_application=_('Unavailability Calendars outside applications') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Unavailability Calendars' %}</h2>
|
||||
{% include 'chrono/includes/application_appbar_fragment.html' with title_no_application=_('Unavailability Calendars outside applications') title_object_list=_('Unavailability Calendars') %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
@ -21,12 +23,15 @@
|
|||
<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>
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-view' pk=object.pk %}">
|
||||
{% include 'chrono/includes/application_icon_fragment.html' %}
|
||||
{{ object.label }} ({{ object.slug }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{% elif not no_application %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This site doesn't have any unavailability calendar yet. Click on the "New" button in the top
|
||||
|
@ -37,10 +42,14 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<aside id="sidebar">
|
||||
{% if user.is_staff %}
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-unavailability-calendar-add' %}">{% trans 'New unavailability calendar' %}</a>
|
||||
{% endif %}
|
||||
</aside>
|
||||
{% if not application and not no_application %}
|
||||
<aside id="sidebar">
|
||||
{% if user.is_staff %}
|
||||
<h3>{% trans "Actions" %}</h3>
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-unavailability-calendar-add' %}">{% trans 'New unavailability calendar' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% include 'chrono/includes/application_list_fragment.html' with title_no_application=_('Unavailability Calendars outside applications') %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -53,5 +53,8 @@
|
|||
{% if user.is_staff %}
|
||||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-unavailability-calendar-delete' pk=unavailability_calendar.id %}">{% trans 'Delete' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% url 'chrono-manager-unavailability-calendar-list' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
{% endblock %}
|
||||
|
|
|
@ -91,6 +91,7 @@ from chrono.agendas.models import (
|
|||
UnavailabilityCalendar,
|
||||
VirtualMember,
|
||||
)
|
||||
from chrono.apps.export_import.models import Application
|
||||
from chrono.utils.date import get_weekday_index
|
||||
from chrono.utils.timezone import localtime, make_aware, make_naive, now
|
||||
|
||||
|
@ -152,16 +153,48 @@ def is_ajax(request):
|
|||
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
||||
|
||||
|
||||
class HomepageView(ListView):
|
||||
class WithApplicationsMixin:
|
||||
def with_applications_dispatch(self, request):
|
||||
self.application = None
|
||||
self.no_application = False
|
||||
if 'application' in self.request.GET:
|
||||
self.application = get_object_or_404(
|
||||
Application, slug=self.request.GET['application'], visible=True
|
||||
)
|
||||
elif 'no-application' in self.request.GET:
|
||||
self.no_application = True
|
||||
|
||||
def with_applications_context_data(self, context):
|
||||
if self.application:
|
||||
context['application'] = self.application
|
||||
elif not self.no_application:
|
||||
Application.populate_objects(self.model, self.object_list)
|
||||
context['applications'] = Application.select_for_object_class(self.model)
|
||||
context['no_application'] = self.no_application
|
||||
return context
|
||||
|
||||
def with_applications_queryset(self):
|
||||
if self.application:
|
||||
return self.application.get_objects_for_object_class(self.model)
|
||||
if self.no_application:
|
||||
return Application.get_orphan_objects_for_object_class(self.model)
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class HomepageView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_home.html'
|
||||
model = Agenda
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.with_applications_dispatch(request)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = self.with_applications_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('category__label', 'label')
|
||||
return queryset.select_related('category').order_by('category__label', 'label')
|
||||
|
||||
def has_access_to_unavailability_calendars(self):
|
||||
if self.request.user.is_staff:
|
||||
|
@ -181,7 +214,7 @@ class HomepageView(ListView):
|
|||
context['shared_custody_enabled'] = settings.SHARED_CUSTODY_ENABLED
|
||||
context['ants_hub_enabled'] = bool(settings.CHRONO_ANTS_HUB_URL)
|
||||
context['with_sidebar'] = True
|
||||
return context
|
||||
return self.with_applications_context_data(context)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not self.has_access():
|
||||
|
@ -221,15 +254,23 @@ class AgendasExportView(FormView):
|
|||
agendas_export = AgendasExportView.as_view()
|
||||
|
||||
|
||||
class ResourceListView(ListView):
|
||||
class ResourceListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_resource_list.html'
|
||||
model = Resource
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.with_applications_dispatch(request)
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.with_applications_queryset()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return self.with_applications_context_data(context)
|
||||
|
||||
|
||||
resource_list = ResourceListView.as_view()
|
||||
|
||||
|
@ -736,15 +777,23 @@ class ResourceDeleteView(DeleteView):
|
|||
resource_delete = ResourceDeleteView.as_view()
|
||||
|
||||
|
||||
class CategoryListView(ListView):
|
||||
class CategoryListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_category_list.html'
|
||||
model = Category
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.with_applications_dispatch(request)
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.with_applications_queryset()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return self.with_applications_context_data(context)
|
||||
|
||||
|
||||
category_list = CategoryListView.as_view()
|
||||
|
||||
|
@ -799,15 +848,23 @@ class CategoryDeleteView(DeleteView):
|
|||
category_delete = CategoryDeleteView.as_view()
|
||||
|
||||
|
||||
class EventsTypeListView(ListView):
|
||||
class EventsTypeListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_events_type_list.html'
|
||||
model = EventsType
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.with_applications_dispatch(request)
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.with_applications_queryset()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return self.with_applications_context_data(context)
|
||||
|
||||
|
||||
events_type_list = EventsTypeListView.as_view()
|
||||
|
||||
|
@ -4184,12 +4241,16 @@ class ManagedUnavailabilityCalendarMixin(ViewableUnavailabilityCalendarMixin):
|
|||
)
|
||||
|
||||
|
||||
class UnavailabilityCalendarListView(ListView):
|
||||
class UnavailabilityCalendarListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_list.html'
|
||||
model = UnavailabilityCalendar
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.with_applications_dispatch(request)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = self.with_applications_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))
|
||||
|
@ -4197,6 +4258,10 @@ class UnavailabilityCalendarListView(ListView):
|
|||
raise PermissionDenied
|
||||
return queryset.order_by('label')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return self.with_applications_context_data(context)
|
||||
|
||||
|
||||
unavailability_calendar_list = UnavailabilityCalendarListView.as_view()
|
||||
|
||||
|
@ -4760,6 +4825,8 @@ partial_booking_check_view = PartialBookingCheckView.as_view()
|
|||
def menu_json(request):
|
||||
if not request.user.is_staff:
|
||||
homepage_view = HomepageView(request=request)
|
||||
homepage_view.application = None
|
||||
homepage_view.no_application = False
|
||||
if not (
|
||||
homepage_view.get_queryset().exists() or homepage_view.has_access_to_unavailability_calendars()
|
||||
):
|
||||
|
|
|
@ -54,13 +54,14 @@ INSTALLED_APPS = (
|
|||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'gadjo',
|
||||
'rest_framework',
|
||||
'django_filters',
|
||||
'sorl.thumbnail',
|
||||
'chrono.agendas',
|
||||
'chrono.api',
|
||||
'chrono.manager',
|
||||
'chrono.apps.ants_hub',
|
||||
'chrono.apps.export_import',
|
||||
'rest_framework',
|
||||
'django_filters',
|
||||
)
|
||||
|
||||
MIDDLEWARE = (
|
||||
|
@ -207,6 +208,10 @@ PARTIAL_BOOKINGS_ENABLED = False
|
|||
|
||||
CHRONO_ANTS_HUB_URL = None
|
||||
|
||||
# from solr.thumbnail -- https://sorl-thumbnail.readthedocs.io/en/latest/reference/settings.html
|
||||
THUMBNAIL_PRESERVE_FORMAT = True
|
||||
THUMBNAIL_FORCE_OVERWRITE = False
|
||||
|
||||
local_settings_file = os.environ.get(
|
||||
'CHRONO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
|
||||
)
|
||||
|
|
|
@ -34,6 +34,7 @@ Depends: libcairo-gobject2,
|
|||
python3-django-tenant-schemas,
|
||||
python3-hobo (>= 1.34),
|
||||
python3-psycopg2,
|
||||
python3-sorl-thumbnail,
|
||||
python3-vobject,
|
||||
uwsgi,
|
||||
uwsgi-plugin-python3,
|
||||
|
|
1
setup.py
1
setup.py
|
@ -168,6 +168,7 @@ setup(
|
|||
'requests',
|
||||
'workalendar',
|
||||
'weasyprint',
|
||||
'sorl-thumbnail',
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass={
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 558 B |
|
@ -0,0 +1,432 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from django.core.files import File
|
||||
from pyquery import PyQuery
|
||||
|
||||
from chrono.agendas.models import Agenda, Category, EventsType, Resource, UnavailabilityCalendar
|
||||
from chrono.apps.export_import.models import Application, ApplicationElement
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
TESTS_DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def application_with_icon():
|
||||
application = Application.objects.create(
|
||||
name='App 1',
|
||||
slug='app-1',
|
||||
version_number='1',
|
||||
)
|
||||
with open(os.path.join(TESTS_DATA_DIR, 'black.jpeg'), mode='rb') as fd:
|
||||
application.icon.save('black.jpeg', File(fd), save=True)
|
||||
return application
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def application_without_icon():
|
||||
application = Application.objects.create(
|
||||
name='App 2',
|
||||
slug='app-2',
|
||||
version_number='1',
|
||||
)
|
||||
return application
|
||||
|
||||
|
||||
@pytest.mark.parametrize('icon', [True, False])
|
||||
def test_agenda(app, admin_user, application_with_icon, application_without_icon, icon):
|
||||
if icon:
|
||||
application = application_with_icon
|
||||
else:
|
||||
application = application_without_icon
|
||||
|
||||
agenda1 = Agenda.objects.create(label='Agenda 1')
|
||||
agenda2 = Agenda.objects.create(label='Agenda 2')
|
||||
ApplicationElement.objects.create(content_object=agenda2, application=application)
|
||||
agenda3 = Agenda.objects.create(label='Agenda 3')
|
||||
ApplicationElement.objects.create(content_object=agenda3, application=application)
|
||||
|
||||
app = login(app)
|
||||
|
||||
# no categories
|
||||
resp = app.get('/manage/')
|
||||
assert len(resp.pyquery('.section')) == 1
|
||||
assert len(resp.pyquery('.section h3')) == 0
|
||||
assert len(resp.pyquery('.section ul.objects-list li')) == 3
|
||||
assert (
|
||||
resp.pyquery('.section ul.objects-list li:nth-child(1)').text()
|
||||
== 'Events Agenda 1 [identifier: agenda-1]'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('.section ul.objects-list li:nth-child(2)').text()
|
||||
== 'Events Agenda 2 [identifier: agenda-2]'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('.section ul.objects-list li:nth-child(3)').text()
|
||||
== 'Events Agenda 3 [identifier: agenda-3]'
|
||||
)
|
||||
if icon:
|
||||
assert len(resp.pyquery('.section ul.objects-list img')) == 2
|
||||
assert len(resp.pyquery('.section ul.objects-list li:nth-child(1) img')) == 0
|
||||
assert len(resp.pyquery('.section ul.objects-list li:nth-child(2) img.application-icon')) == 1
|
||||
assert len(resp.pyquery('.section ul.objects-list li:nth-child(3) img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('.section ul.objects-list img')) == 0
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Agendas outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(application.name)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h2 img.application-logo')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h2 img')) == 0
|
||||
assert len(resp.pyquery('.section ul.objects-list li')) == 2
|
||||
assert (
|
||||
resp.pyquery('.section ul.objects-list li:nth-child(1)').text()
|
||||
== 'Events Agenda 2 [identifier: agenda-2]'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('.section ul.objects-list li:nth-child(2)').text()
|
||||
== 'Events Agenda 3 [identifier: agenda-3]'
|
||||
)
|
||||
assert len(resp.pyquery('.section ul.objects-list li img')) == 0
|
||||
|
||||
# check elements outside applications
|
||||
resp = app.get('/manage/')
|
||||
resp = resp.click('Agendas outside applications')
|
||||
assert resp.pyquery('h2').text() == 'Agendas outside applications'
|
||||
assert len(resp.pyquery('.section ul.objects-list li')) == 1
|
||||
assert (
|
||||
resp.pyquery('.section ul.objects-list li:nth-child(1)').text()
|
||||
== 'Events Agenda 1 [identifier: agenda-1]'
|
||||
)
|
||||
|
||||
# with category
|
||||
cat = Category.objects.create(label='Cat')
|
||||
ApplicationElement.objects.create(content_object=cat, application=application)
|
||||
agenda2.category = cat
|
||||
agenda2.save()
|
||||
resp = app.get('/manage/')
|
||||
assert len(resp.pyquery('.section')) == 2
|
||||
assert PyQuery(resp.pyquery('.section')[0]).find('h3').text() == 'Cat'
|
||||
assert len(PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list li')) == 1
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list li').text()
|
||||
== 'Events Agenda 2 [identifier: agenda-2]'
|
||||
)
|
||||
assert PyQuery(resp.pyquery('.section')[1]).find('h3').text() == 'Misc'
|
||||
assert len(PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li')) == 2
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li:nth-child(1)').text()
|
||||
== 'Events Agenda 1 [identifier: agenda-1]'
|
||||
)
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li:nth-child(2)').text()
|
||||
== 'Events Agenda 3 [identifier: agenda-3]'
|
||||
)
|
||||
if icon:
|
||||
assert len(PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list img')) == 1
|
||||
assert (
|
||||
len(
|
||||
PyQuery(resp.pyquery('.section')[0]).find(
|
||||
'ul.objects-list li:nth-child(1) img.application-icon'
|
||||
)
|
||||
)
|
||||
== 1
|
||||
)
|
||||
assert len(PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list img')) == 1
|
||||
assert len(PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li:nth-child(1) img')) == 0
|
||||
assert (
|
||||
len(
|
||||
PyQuery(resp.pyquery('.section')[1]).find(
|
||||
'ul.objects-list li:nth-child(2) img.application-icon'
|
||||
)
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
||||
# check application view
|
||||
resp = resp.click(application.name)
|
||||
assert len(resp.pyquery('.section')) == 2
|
||||
assert PyQuery(resp.pyquery('.section')[0]).find('h3').text() == 'Cat'
|
||||
assert len(PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list li')) == 1
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list li:nth-child(1)').text()
|
||||
== 'Events Agenda 2 [identifier: agenda-2]'
|
||||
)
|
||||
assert PyQuery(resp.pyquery('.section')[1]).find('h3').text() == 'Misc'
|
||||
assert len(PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li')) == 1
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li:nth-child(1)').text()
|
||||
== 'Events Agenda 3 [identifier: agenda-3]'
|
||||
)
|
||||
|
||||
# check categories
|
||||
Category.objects.create(label='Cat2')
|
||||
resp = app.get('/manage/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
resp = resp.click(application.name)
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check detail page
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda1.pk)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda2.pk)
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
|
||||
# check visible flag
|
||||
application.visible = False
|
||||
application.save()
|
||||
resp = app.get('/manage/')
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('.section ul.objects-list img')) == 0
|
||||
app.get('/manage/?application=%s' % application.slug, status=404)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda2.pk)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('icon', [True, False])
|
||||
def test_events_type(app, admin_user, application_with_icon, application_without_icon, icon):
|
||||
if icon:
|
||||
application = application_with_icon
|
||||
else:
|
||||
application = application_without_icon
|
||||
|
||||
EventsType.objects.create(label='EventsType 1')
|
||||
events_type2 = EventsType.objects.create(label='EventsType 2')
|
||||
ApplicationElement.objects.create(content_object=events_type2, application=application)
|
||||
events_type3 = EventsType.objects.create(label='EventsType 3')
|
||||
ApplicationElement.objects.create(content_object=events_type3, application=application)
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/events-types/')
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 3
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'EventsType 1 (eventstype-1) remove'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'EventsType 2 (eventstype-2) remove'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(3)').text() == 'EventsType 3 (eventstype-3) remove'
|
||||
if icon:
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 2
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(1) img')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(2) img.application-icon')) == 1
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(3) img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 0
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Events types outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(application.name)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h2 img.application-logo')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h2 img')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'EventsType 2 (eventstype-2) remove'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'EventsType 3 (eventstype-3) remove'
|
||||
assert len(resp.pyquery('ul.objects-list li img')) == 0
|
||||
|
||||
# check elements outside applications
|
||||
resp = app.get('/manage/events-types/')
|
||||
resp = resp.click('Events types outside applications')
|
||||
assert resp.pyquery('h2').text() == 'Events types outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'EventsType 1 (eventstype-1) remove'
|
||||
|
||||
# check visible flag
|
||||
application.visible = False
|
||||
application.save()
|
||||
resp = app.get('/manage/events-types/')
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 0
|
||||
app.get('/manage/events-types/?application=%s' % application.slug, status=404)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('icon', [True, False])
|
||||
def test_resource(app, admin_user, application_with_icon, application_without_icon, icon):
|
||||
if icon:
|
||||
application = application_with_icon
|
||||
else:
|
||||
application = application_without_icon
|
||||
|
||||
resource1 = Resource.objects.create(label='Resource 1')
|
||||
resource2 = Resource.objects.create(label='Resource 2')
|
||||
ApplicationElement.objects.create(content_object=resource2, application=application)
|
||||
resource3 = Resource.objects.create(label='Resource 3')
|
||||
ApplicationElement.objects.create(content_object=resource3, application=application)
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/resources/')
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 3
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Resource 1 (resource-1)'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'Resource 2 (resource-2)'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(3)').text() == 'Resource 3 (resource-3)'
|
||||
if icon:
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 2
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(1) img')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(2) img.application-icon')) == 1
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(3) img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 0
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Resources outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(application.name)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h2 img.application-logo')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h2 img')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Resource 2 (resource-2)'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'Resource 3 (resource-3)'
|
||||
assert len(resp.pyquery('ul.objects-list li img')) == 0
|
||||
|
||||
# check elements outside applications
|
||||
resp = app.get('/manage/resources/')
|
||||
resp = resp.click('Resources outside applications')
|
||||
assert resp.pyquery('h2').text() == 'Resources outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Resource 1 (resource-1)'
|
||||
|
||||
# check detail page
|
||||
resp = app.get('/manage/resource/%s/' % resource1.pk)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
|
||||
resp = app.get('/manage/resource/%s/' % resource2.pk)
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
|
||||
# check visible flag
|
||||
application.visible = False
|
||||
application.save()
|
||||
resp = app.get('/manage/resources/')
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 0
|
||||
app.get('/manage/resources/?application=%s' % application.slug, status=404)
|
||||
resp = app.get('/manage/resource/%s/' % resource2.pk)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('icon', [True, False])
|
||||
def test_unavailability_calendar(app, admin_user, application_with_icon, application_without_icon, icon):
|
||||
if icon:
|
||||
application = application_with_icon
|
||||
else:
|
||||
application = application_without_icon
|
||||
|
||||
unavailability_calendar1 = UnavailabilityCalendar.objects.create(label='UnavailabilityCalendar 1')
|
||||
unavailability_calendar2 = UnavailabilityCalendar.objects.create(label='UnavailabilityCalendar 2')
|
||||
ApplicationElement.objects.create(content_object=unavailability_calendar2, application=application)
|
||||
unavailability_calendar3 = UnavailabilityCalendar.objects.create(label='UnavailabilityCalendar 3')
|
||||
ApplicationElement.objects.create(content_object=unavailability_calendar3, application=application)
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/unavailability-calendars/')
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 3
|
||||
assert (
|
||||
resp.pyquery('ul.objects-list li:nth-child(1)').text()
|
||||
== 'UnavailabilityCalendar 1 (unavailabilitycalendar-1)'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('ul.objects-list li:nth-child(2)').text()
|
||||
== 'UnavailabilityCalendar 2 (unavailabilitycalendar-2)'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('ul.objects-list li:nth-child(3)').text()
|
||||
== 'UnavailabilityCalendar 3 (unavailabilitycalendar-3)'
|
||||
)
|
||||
if icon:
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 2
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(1) img')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(2) img.application-icon')) == 1
|
||||
assert len(resp.pyquery('ul.objects-list li:nth-child(3) img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 0
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Unavailability Calendars outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(application.name)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h2 img.application-logo')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h2 img')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
assert (
|
||||
resp.pyquery('ul.objects-list li:nth-child(1)').text()
|
||||
== 'UnavailabilityCalendar 2 (unavailabilitycalendar-2)'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('ul.objects-list li:nth-child(2)').text()
|
||||
== 'UnavailabilityCalendar 3 (unavailabilitycalendar-3)'
|
||||
)
|
||||
assert len(resp.pyquery('ul.objects-list li img')) == 0
|
||||
|
||||
# check elements outside applications
|
||||
resp = app.get('/manage/unavailability-calendars/')
|
||||
resp = resp.click('Unavailability Calendars outside applications')
|
||||
assert resp.pyquery('h2').text() == 'Unavailability Calendars outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert (
|
||||
resp.pyquery('ul.objects-list li:nth-child(1)').text()
|
||||
== 'UnavailabilityCalendar 1 (unavailabilitycalendar-1)'
|
||||
)
|
||||
|
||||
# check detail page
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar1.pk)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar2.pk)
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
if icon:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
|
||||
# check visible flag
|
||||
application.visible = False
|
||||
application.save()
|
||||
resp = app.get('/manage/unavailability-calendars/')
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('ul.objects-list img')) == 0
|
||||
app.get('/manage/unavailability-calendars/?application=%s' % application.slug, status=404)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar2.pk)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
|
|
@ -60,7 +60,7 @@ def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
|
|||
app = login(app)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert len(ctx.captured_queries) == 13
|
||||
assert len(ctx.captured_queries) in [14, 15]
|
||||
|
||||
# check with different kind of exceptions:
|
||||
|
||||
|
@ -76,7 +76,7 @@ def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
|
|||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert len(ctx.captured_queries) == 13
|
||||
assert len(ctx.captured_queries) == 14
|
||||
|
||||
# exception starts in the past but ends in the futur
|
||||
exception.delete()
|
||||
|
@ -92,7 +92,7 @@ def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
|
|||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert len(ctx.captured_queries) == 13
|
||||
assert len(ctx.captured_queries) == 14
|
||||
|
||||
# exception in more than 2 weeks
|
||||
exception.delete()
|
||||
|
@ -107,7 +107,7 @@ def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
|
|||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert len(ctx.captured_queries) == 13
|
||||
assert len(ctx.captured_queries) == 14
|
||||
|
||||
|
||||
def test_meetings_agenda_add_meeting_type(app, admin_user):
|
||||
|
|
Loading…
Reference in New Issue