diff --git a/debian/control b/debian/control
index 1ecff3d..44ec0d5 100644
--- a/debian/control
+++ b/debian/control
@@ -33,6 +33,7 @@ Depends: python3-django-mellon,
python3-hobo,
python3-lingo (= ${binary:Version}),
python3-psycopg2,
+ python3-sorl-thumbnail,
python3-uwsgidecorators,
uwsgi,
uwsgi-plugin-python3,
diff --git a/lingo/agendas/models.py b/lingo/agendas/models.py
index 1660960..92b98a0 100644
--- a/lingo/agendas/models.py
+++ b/lingo/agendas/models.py
@@ -21,10 +21,11 @@ from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
+from lingo.export_import.models import WithApplicationMixin
from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug
-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)
category_label = models.CharField(_('Category label'), max_length=150, null=True)
@@ -125,7 +126,7 @@ class Agenda(models.Model):
return _('Events')
-class CheckTypeGroup(models.Model):
+class CheckTypeGroup(WithApplicationMixin, models.Model):
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
label = models.CharField(_('Label'), max_length=150)
unexpected_presence = models.ForeignKey(
diff --git a/lingo/export_import/models.py b/lingo/export_import/models.py
index 43bff62..77257ab 100644
--- a/lingo/export_import/models.py
+++ b/lingo/export_import/models.py
@@ -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)
diff --git a/lingo/export_import/templates/lingo/includes/application_appbar_fragment.html b/lingo/export_import/templates/lingo/includes/application_appbar_fragment.html
new file mode 100644
index 0000000..d8d6989
--- /dev/null
+++ b/lingo/export_import/templates/lingo/includes/application_appbar_fragment.html
@@ -0,0 +1,13 @@
+{% load thumbnail %}
+{% if application %}
+
+ {% include 'lingo/includes/application_icon_fragment.html' with object=payer %}
{{ payer.label }}
@@ -26,8 +30,7 @@
{% endfor %}
- {% else %}
-
+ {% elif not no_application %}
{% blocktrans %}
This site doesn't have any payer yet. Click on the "New" button in the top
@@ -38,10 +41,14 @@
{% endblock %}
{% block sidebar %}
-
+ {% endif %}
{% endblock %}
diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html
index 7cbcf8c..9656793 100644
--- a/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html
+++ b/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html
@@ -54,5 +54,8 @@
{% trans 'Refunds' %}
{% trans 'Non invoiced lines' %}
+ {% url 'lingo-manager-invoicing-regie-list' as object_list_url %}
+ {% include 'lingo/includes/application_detail_fragment.html' %}
+
{% endblock %}
diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html b/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html
index 9b7b7b3..fcbf497 100644
--- a/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html
+++ b/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html
@@ -5,11 +5,13 @@
{% block breadcrumb %}
{{ block.super }}
-
{% trans "Regies" %}
+ {% url 'lingo-manager-invoicing-regie-list' as object_list_url %}
+
{% trans "Regies" %}
+ {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Regies outside applications') %}
{% endblock %}
{% block appbar %}
-
{% trans 'Regies' %}
+ {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Regies outside applications') title_object_list=_('Regies') %}
{% endblock %}
{% block content %}
@@ -19,6 +21,7 @@
{% for regie in object_list %}
+ {% include 'lingo/includes/application_icon_fragment.html' with object=regie %}
{{ regie.label }}
@@ -26,8 +29,7 @@
{% endfor %}
- {% else %}
-
+ {% elif not no_application %}
{% blocktrans %}
This site doesn't have any regie yet. Click on the "New" button in the top
@@ -38,16 +40,20 @@
{% endblock %}
{% block sidebar %}
-
+ {% endif %}
{% endblock %}
diff --git a/lingo/invoicing/views/payer.py b/lingo/invoicing/views/payer.py
index 901d870..6b3d118 100644
--- a/lingo/invoicing/views/payer.py
+++ b/lingo/invoicing/views/payer.py
@@ -21,14 +21,26 @@ from django.http import HttpResponse
from django.urls import reverse
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
+from lingo.export_import.views import WithApplicationsMixin
from lingo.invoicing.forms import NewPayerForm, PayerForm, PayerMappingForm
from lingo.invoicing.models import Payer
-class PayersListView(ListView):
+class PayersListView(WithApplicationsMixin, ListView):
template_name = 'lingo/invoicing/manager_payer_list.html'
model = Payer
+ def dispatch(self, request, *args, **kwargs):
+ self.with_applications_dispatch(request)
+ 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)
+
payers_list = PayersListView.as_view()
diff --git a/lingo/invoicing/views/regie.py b/lingo/invoicing/views/regie.py
index 54b9b85..cf6b213 100644
--- a/lingo/invoicing/views/regie.py
+++ b/lingo/invoicing/views/regie.py
@@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
from lingo.agendas.models import Agenda
+from lingo.export_import.views import WithApplicationsMixin
from lingo.invoicing.forms import (
PaymentTypeForm,
RegieCreditFilterSet,
@@ -69,10 +70,21 @@ def import_regies(data):
return results
-class RegiesListView(ListView):
+class RegiesListView(WithApplicationsMixin, ListView):
template_name = 'lingo/invoicing/manager_regie_list.html'
model = Regie
+ def dispatch(self, request, *args, **kwargs):
+ self.with_applications_dispatch(request)
+ 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)
+
regies_list = RegiesListView.as_view()
diff --git a/lingo/manager/static/css/style.scss b/lingo/manager/static/css/style.scss
index a1a2d72..ce900cb 100644
--- a/lingo/manager/static/css/style.scss
+++ b/lingo/manager/static/css/style.scss
@@ -170,3 +170,7 @@ span.invoice-colour {
border-radius: 0;
vertical-align: middle;
}
+
+.application-logo, .application-icon {
+ vertical-align: middle;
+}
diff --git a/lingo/pricing/models.py b/lingo/pricing/models.py
index f8e6972..f8b00dd 100644
--- a/lingo/pricing/models.py
+++ b/lingo/pricing/models.py
@@ -25,6 +25,7 @@ from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from lingo.agendas.models import Agenda, CheckType
+from lingo.export_import.models import WithApplicationMixin
from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug
from lingo.utils.wcs import get_wcs_dependencies_from_template
@@ -99,7 +100,7 @@ class PricingBookingCheckTypeError(PricingError):
pass
-class CriteriaCategory(models.Model):
+class CriteriaCategory(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
@@ -261,7 +262,7 @@ class PricingMatrix:
rows: list[PricingMatrixRow]
-class Pricing(models.Model):
+class Pricing(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150, null=True)
slug = models.SlugField(_('Identifier'), max_length=160, null=True)
diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
index 28fe63e..f582bfe 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
@@ -116,5 +116,8 @@
{% trans "Agenda options" %}
{% endif %}{% endwith %}
+ {% url 'lingo-manager-agenda-list' as object_list_url %}
+ {% include 'lingo/includes/application_detail_fragment.html' %}
+
{% endblock %}
diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html
index d3845bf..178e25f 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html
@@ -1,15 +1,18 @@
{% extends "lingo/pricing/manager_pricing_list.html" %}
-{% load i18n %}
+{% load i18n thumbnail %}
{% block page-title-extra-label %}{% trans "Agendas" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %}
- {{ block.super }}
-
{% trans 'Agendas' %}
+
{% trans "Payments" context 'lingo title' %}
+
{% trans "Pricings" %}
+ {% url 'lingo-manager-agenda-list' as object_list_url %}
+
{% trans "Agendas" %}
+ {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Agendas outside applications') %}
{% endblock %}
{% block appbar %}
-
{% trans 'Agendas' %}
+ {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Agendas outside applications') title_object_list=_('Agendas') %}
{% endblock %}
{% block content %}
@@ -21,14 +24,17 @@
{% endfor %}
- {% else %}
+ {% elif not no_application %}
{% blocktrans trimmed %}
This site doesn't have any agenda yet. Click on the "Refresh agendas" button in the top
@@ -39,10 +45,14 @@
{% endblock %}
{% block sidebar %}
-
+ {% endif %}
{% endblock %}
diff --git a/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html b/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html
index 7661a69..205bb11 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html
@@ -4,22 +4,30 @@
{% block page-title-extra-label %}{% trans "Check types" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %}
- {{ block.super }}
-
{% trans "Check types" %}
+
{% trans "Payments" context 'lingo title' %}
+
{% trans "Pricings" %}
+ {% url 'lingo-manager-check-type-list' as object_list_url %}
+
{% trans "Check types" %}
+ {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Check types outside applications') %}
{% endblock %}
{% block appbar %}
-
{% trans 'Check types' %}
+ {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Check types outside applications') title_object_list=_('Check types') %}
{% endblock %}
{% block content %}
-
+ {% if not application and not no_application %}
+
+ {% endif %}
{% for object in object_list %}
{% empty %}
-
- {% blocktrans trimmed %}
- This site doesn't have any check type group yet. Click on the "New group" button in the top
- right of the page to add a first one.
- {% endblocktrans %}
-
+ {% if not no_application %}
+
+ {% blocktrans trimmed %}
+ This site doesn't have any check type group yet. Click on the "New group" button in the top
+ right of the page to add a first one.
+ {% endblocktrans %}
+
+ {% endif %}
{% endfor %}
{% endblock %}
{% block sidebar %}
-
+ {% endif %}
{% endblock %}
diff --git a/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html b/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html
index ea590d7..9344895 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html
@@ -4,30 +4,40 @@
{% block page-title-extra-label %}{% trans "Criterias" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %}
- {{ block.super }}
- {% trans "Criterias" %}
+ {% trans "Payments" context 'lingo title' %}
+ {% trans "Pricings" %}
+ {% url 'lingo-manager-pricing-criteria-list' as object_list_url %}
+ {% trans "Criterias" %}
+ {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Criterias outside applications') %}
{% endblock %}
{% block appbar %}
- {% trans 'Criterias' %}
+ {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Criterias outside applications') title_object_list=_('Criterias') %}
{% endblock %}
{% block content %}
-
+ {% if not application and not no_application %}
+
+ {% endif %}
{% if object_list %}
-
- {% blocktrans trimmed %}
- Use drag and drop with the ⣿ handles to reorder criterias inside a category.
- {% endblocktrans %}
-
+ {% if not application and not no_application %}
+
+ {% blocktrans trimmed %}
+ Use drag and drop with the ⣿ handles to reorder criterias inside a category.
+ {% endblocktrans %}
+
+ {% endif %}
{% endif %}
{% for object in object_list %}
{% empty %}
-
- {% blocktrans trimmed %}
- This site doesn't have any pricing category yet. Click on the "New category" button in the top
- right of the page to add a first one.
- {% endblocktrans %}
-
+ {% if not no_application %}
+
+ {% blocktrans trimmed %}
+ This site doesn't have any pricing category yet. Click on the "New category" button in the top
+ right of the page to add a first one.
+ {% endblocktrans %}
+
+ {% endif %}
{% endfor %}
{% endblock %}
{% block sidebar %}
-
+ {% endif %}
{% endblock %}
diff --git a/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html b/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html
index 3ca9882..bc31cfe 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html
@@ -283,5 +283,8 @@
{% trans 'Options' %}
{% trans 'Export' %}
+ {% url 'lingo-manager-pricing-list' as object_list_url %}
+ {% include 'lingo/includes/application_detail_fragment.html' %}
+
{% endblock %}
diff --git a/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html b/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html
index 2157561..08040af 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html
@@ -1,21 +1,25 @@
{% extends "lingo/manager_homepage.html" %}
{% load i18n %}
-{% block page-title-extra-label %}{% trans "Pricings" context 'agenda pricing' %}{% endblock %}
+{% block page-title-extra-label %}{% trans "Pricings" %}{% endblock %}
{% block breadcrumb %}
{{ block.super }}
- {% trans "Pricings" context 'agenda pricing' %}
+ {% url 'lingo-manager-pricing-list' as object_list_url %}
+ {% trans "Pricings" %}
+ {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Pricings outside applications') %}
{% endblock %}
{% block appbar %}
- {% trans 'Pricings' context 'agenda pricing' %}
+ {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Pricings outside applications') title_object_list=_('Pricings') %}
{% endblock %}
{% block content %}
-
+ {% if not application and not no_application %}
+
+ {% endif %}
{% if object_list %}
- {% else %}
+ {% elif not no_application %}
{% blocktrans trimmed %}
This site doesn't have any pricing yet. Click on the "New pricing" button in the top
@@ -54,17 +60,21 @@
{% endblock %}
{% block sidebar %}
-
+ {% endif %}
{% endblock %}
diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py
index b92cfe0..d75e623 100644
--- a/lingo/pricing/views.py
+++ b/lingo/pricing/views.py
@@ -41,6 +41,7 @@ from django.views.generic.detail import SingleObjectMixin
from lingo.agendas.chrono import refresh_agendas
from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
from lingo.agendas.views import AgendaMixin
+from lingo.export_import.views import WithApplicationsMixin
from lingo.pricing.forms import (
CheckTypeForm,
CheckTypeGroupUnexpectedPresenceForm,
@@ -221,12 +222,21 @@ class ConfigImportView(FormView):
config_import = ConfigImportView.as_view()
-class CriteriaListView(ListView):
+class CriteriaListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_criteria_list.html'
model = CriteriaCategory
+ def dispatch(self, request, *args, **kwargs):
+ self.with_applications_dispatch(request)
+ return super().dispatch(request, *args, **kwargs)
+
def get_queryset(self):
- return CriteriaCategory.objects.prefetch_related('criterias')
+ queryset = self.with_applications_queryset()
+ return queryset.prefetch_related('criterias')
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ return self.with_applications_context_data(context)
criteria_list = CriteriaListView.as_view()
@@ -368,14 +378,22 @@ class CriteriaDeleteView(DeleteView):
criteria_delete = CriteriaDeleteView.as_view()
-class AgendaListView(ListView):
+class AgendaListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_agenda_list.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()
return queryset.order_by('category_label', 'label')
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ return self.with_applications_context_data(context)
+
agenda_list = AgendaListView.as_view()
@@ -467,12 +485,21 @@ class AgendaInvoicingSettingsView(AgendaMixin, UpdateView):
agenda_invoicing_settings = AgendaInvoicingSettingsView.as_view()
-class PricingListView(ListView):
+class PricingListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_pricing_list.html'
model = Pricing
+ def dispatch(self, request, *args, **kwargs):
+ self.with_applications_dispatch(request)
+ return super().dispatch(request, *args, **kwargs)
+
def get_queryset(self):
- return Pricing.objects.all().order_by('flat_fee_schedule', 'date_start', 'date_end')
+ queryset = self.with_applications_queryset()
+ return queryset.order_by('flat_fee_schedule', 'date_start', 'date_end')
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ return self.with_applications_context_data(context)
pricing_list = PricingListView.as_view()
@@ -965,12 +992,21 @@ class PricingMatrixEdit(FormView):
pricing_matrix_edit = PricingMatrixEdit.as_view()
-class CheckTypeListView(ListView):
+class CheckTypeListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_check_type_list.html'
model = CheckTypeGroup
+ def dispatch(self, request, *args, **kwargs):
+ self.with_applications_dispatch(request)
+ return super().dispatch(request, *args, **kwargs)
+
def get_queryset(self):
- return CheckTypeGroup.objects.prefetch_related('check_types')
+ queryset = self.with_applications_queryset()
+ return queryset.prefetch_related('check_types')
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ return self.with_applications_context_data(context)
check_type_list = CheckTypeListView.as_view()
diff --git a/lingo/settings.py b/lingo/settings.py
index 93dd7c5..5d830ea 100644
--- a/lingo/settings.py
+++ b/lingo/settings.py
@@ -57,6 +57,7 @@ INSTALLED_APPS = (
'gadjo',
'rest_framework',
'django_filters',
+ 'sorl.thumbnail',
'lingo.agendas',
'lingo.api',
'lingo.basket',
@@ -231,6 +232,10 @@ CKEDITOR_CONFIGS = {
BASKET_EXPIRY_DELAY = 60 # 1 hour by default
+# 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(
'LINGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
)
diff --git a/setup.py b/setup.py
index 4ef9275..426bb5f 100644
--- a/setup.py
+++ b/setup.py
@@ -168,6 +168,7 @@ setup(
'djangorestframework>=3.3, <3.15',
'django-filter',
'weasyprint',
+ 'sorl-thumbnail',
],
zip_safe=False,
cmdclass={
diff --git a/tests/data/black.jpeg b/tests/data/black.jpeg
new file mode 100644
index 0000000..6309526
Binary files /dev/null and b/tests/data/black.jpeg differ
diff --git a/tests/test_manager_applications.py b/tests/test_manager_applications.py
new file mode 100644
index 0000000..d3d016d
--- /dev/null
+++ b/tests/test_manager_applications.py
@@ -0,0 +1,538 @@
+import datetime
+import os
+
+import pytest
+from django.core.files import File
+from pyquery import PyQuery
+
+from lingo.agendas.models import Agenda, CheckTypeGroup
+from lingo.export_import.models import Application, ApplicationElement
+from lingo.invoicing.models import Payer, Regie
+from lingo.pricing.models import CriteriaCategory, Pricing
+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)
+
+ resp = app.get('/manage/pricing/agendas/')
+ assert len(resp.pyquery('ul.objects-list li')) == 3
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(1)').text()
+ == 'Agenda 1 [identifier: agenda-1, kind: Events] view'
+ )
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(2)').text()
+ == 'Agenda 2 [identifier: agenda-2, kind: Events] view'
+ )
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(3)').text()
+ == 'Agenda 3 [identifier: agenda-3, kind: Events] view'
+ )
+ 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 '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('ul.objects-list li')) == 2
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(1)').text()
+ == 'Agenda 2 [identifier: agenda-2, kind: Events] view'
+ )
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(2)').text()
+ == 'Agenda 3 [identifier: agenda-3, kind: Events] view'
+ )
+ assert len(resp.pyquery('ul.objects-list li img')) == 0
+
+ # check elements outside applications
+ resp = app.get('/manage/pricing/agendas/')
+ resp = resp.click('Agendas outside applications')
+ assert resp.pyquery('h2').text() == 'Agendas outside applications'
+ assert len(resp.pyquery('ul.objects-list li')) == 1
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(1)').text()
+ == 'Agenda 1 [identifier: agenda-1, kind: Events] view'
+ )
+
+ # check detail page
+ resp = app.get('/manage/pricing/agenda/%s/' % agenda1.pk)
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
+ resp = app.get('/manage/pricing/agenda/%s/' % 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/pricing/agendas/')
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('ul.objects-list img')) == 0
+ app.get('/manage/pricing/agendas/?application=%s' % application.slug, status=404)
+ resp = app.get('/manage/pricing/agenda/%s/' % 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_check_type(app, admin_user, application_with_icon, application_without_icon, icon):
+ if icon:
+ application = application_with_icon
+ else:
+ application = application_without_icon
+
+ CheckTypeGroup.objects.create(label='CheckTypeGroup 1')
+ check_type_group2 = CheckTypeGroup.objects.create(label='CheckTypeGroup 2')
+ ApplicationElement.objects.create(content_object=check_type_group2, application=application)
+ check_type_group3 = CheckTypeGroup.objects.create(label='CheckTypeGroup 3')
+ ApplicationElement.objects.create(content_object=check_type_group3, application=application)
+
+ app = login(app)
+
+ resp = app.get('/manage/pricing/check-types/')
+ assert len(resp.pyquery('.section')) == 3
+ assert len(resp.pyquery('.section h3')) == 3
+ assert PyQuery(resp.pyquery('.section')[0]).find('h3').text() == 'CheckTypeGroup 1 Export Delete'
+ assert PyQuery(resp.pyquery('.section')[1]).find('h3').text() == 'CheckTypeGroup 2 Export Delete'
+ assert PyQuery(resp.pyquery('.section')[2]).find('h3').text() == 'CheckTypeGroup 3 Export Delete'
+ if icon:
+ assert len(resp.pyquery('h3 img')) == 2
+ assert len(PyQuery(resp.pyquery('.section')[0]).find('h3 img')) == 0
+ assert len(PyQuery(resp.pyquery('.section')[1]).find('h3 img.application-icon')) == 1
+ assert len(PyQuery(resp.pyquery('.section')[2]).find('h3 img.application-icon')) == 1
+ else:
+ assert len(resp.pyquery('h3 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 'Check 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('.section')) == 2
+ assert len(resp.pyquery('.section h3')) == 2
+ assert PyQuery(resp.pyquery('.section')[0]).find('h3').text() == 'CheckTypeGroup 2 Export Delete'
+ assert PyQuery(resp.pyquery('.section')[1]).find('h3').text() == 'CheckTypeGroup 3 Export Delete'
+ assert len(resp.pyquery('h3 img')) == 0
+
+ # check elements outside applications
+ resp = app.get('/manage/pricing/check-types/')
+ resp = resp.click('Check types outside applications')
+ assert resp.pyquery('h2').text() == 'Check types outside applications'
+ assert len(resp.pyquery('.section')) == 1
+ assert len(resp.pyquery('.section h3')) == 1
+ assert PyQuery(resp.pyquery('.section')[0]).find('h3').text() == 'CheckTypeGroup 1 Export Delete'
+
+ # check visible flag
+ application.visible = False
+ application.save()
+ resp = app.get('/manage/pricing/check-types/')
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('ul.objects-list img')) == 0
+ app.get('/manage/pricing/check-types/?application=%s' % application.slug, status=404)
+
+
+@pytest.mark.parametrize('icon', [True, False])
+def test_pricing(app, admin_user, application_with_icon, application_without_icon, icon):
+ if icon:
+ application = application_with_icon
+ else:
+ application = application_without_icon
+
+ pricing1 = Pricing.objects.create(
+ label='Pricing 1',
+ date_start=datetime.date(year=2021, month=9, day=1),
+ date_end=datetime.date(year=2021, month=10, day=1),
+ )
+ pricing2 = Pricing.objects.create(
+ label='Pricing 2',
+ date_start=datetime.date(year=2021, month=9, day=1),
+ date_end=datetime.date(year=2021, month=10, day=1),
+ )
+ ApplicationElement.objects.create(content_object=pricing2, application=application)
+ pricing3 = Pricing.objects.create(
+ label='Pricing 3',
+ date_start=datetime.date(year=2021, month=9, day=1),
+ date_end=datetime.date(year=2021, month=10, day=1),
+ )
+ ApplicationElement.objects.create(content_object=pricing3, application=application)
+
+ app = login(app)
+
+ resp = app.get('/manage/pricing/')
+ assert len(resp.pyquery('ul.objects-list li')) == 3
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(1)').text()
+ == 'Pricing 1 - From 01/09/2021 to 01/10/2021) [identifier: pricing-1]'
+ )
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(2)').text()
+ == 'Pricing 2 - From 01/09/2021 to 01/10/2021) [identifier: pricing-2]'
+ )
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(3)').text()
+ == 'Pricing 3 - From 01/09/2021 to 01/10/2021) [identifier: pricing-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 'Pricings 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()
+ == 'Pricing 2 - From 01/09/2021 to 01/10/2021) [identifier: pricing-2]'
+ )
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(2)').text()
+ == 'Pricing 3 - From 01/09/2021 to 01/10/2021) [identifier: pricing-3]'
+ )
+ assert len(resp.pyquery('ul.objects-list li img')) == 0
+
+ # check elements outside applications
+ resp = app.get('/manage/pricing/')
+ resp = resp.click('Pricings outside applications')
+ assert resp.pyquery('h2').text() == 'Pricings outside applications'
+ assert len(resp.pyquery('ul.objects-list li')) == 1
+ assert (
+ resp.pyquery('ul.objects-list li:nth-child(1)').text()
+ == 'Pricing 1 - From 01/09/2021 to 01/10/2021) [identifier: pricing-1]'
+ )
+
+ # check detail page
+ resp = app.get('/manage/pricing/%s/' % pricing1.pk)
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
+ resp = app.get('/manage/pricing/%s/' % pricing2.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/pricing/')
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('ul.objects-list img')) == 0
+ app.get('/manage/pricing/?application=%s' % application.slug, status=404)
+ resp = app.get('/manage/pricing/%s/' % pricing2.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_criteria_category(app, admin_user, application_with_icon, application_without_icon, icon):
+ if icon:
+ application = application_with_icon
+ else:
+ application = application_without_icon
+
+ CriteriaCategory.objects.create(label='CriteriaCategory 1')
+ criteria_category2 = CriteriaCategory.objects.create(label='CriteriaCategory 2')
+ ApplicationElement.objects.create(content_object=criteria_category2, application=application)
+ criteria_category3 = CriteriaCategory.objects.create(label='CriteriaCategory 3')
+ ApplicationElement.objects.create(content_object=criteria_category3, application=application)
+
+ app = login(app)
+
+ resp = app.get('/manage/pricing/criterias/')
+ assert len(resp.pyquery('.section')) == 3
+ assert len(resp.pyquery('.section h3')) == 3
+ assert (
+ PyQuery(resp.pyquery('.section')[0]).find('h3').text()
+ == 'CriteriaCategory 1 [criteriacategory-1] Export Delete'
+ )
+ assert (
+ PyQuery(resp.pyquery('.section')[1]).find('h3').text()
+ == 'CriteriaCategory 2 [criteriacategory-2] Export Delete'
+ )
+ assert (
+ PyQuery(resp.pyquery('.section')[2]).find('h3').text()
+ == 'CriteriaCategory 3 [criteriacategory-3] Export Delete'
+ )
+ if icon:
+ assert len(resp.pyquery('h3 img')) == 2
+ assert len(PyQuery(resp.pyquery('.section')[0]).find('h3 img')) == 0
+ assert len(PyQuery(resp.pyquery('.section')[1]).find('h3 img.application-icon')) == 1
+ assert len(PyQuery(resp.pyquery('.section')[2]).find('h3 img.application-icon')) == 1
+ else:
+ assert len(resp.pyquery('h3 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 'Criterias 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')) == 2
+ assert len(resp.pyquery('.section h3')) == 2
+ assert (
+ PyQuery(resp.pyquery('.section')[0]).find('h3').text()
+ == 'CriteriaCategory 2 [criteriacategory-2] Export Delete'
+ )
+ assert (
+ PyQuery(resp.pyquery('.section')[1]).find('h3').text()
+ == 'CriteriaCategory 3 [criteriacategory-3] Export Delete'
+ )
+ assert len(resp.pyquery('h3 img')) == 0
+
+ # check elements outside applications
+ resp = app.get('/manage/pricing/criterias/')
+ resp = resp.click('Criterias outside applications')
+ assert resp.pyquery('h2').text() == 'Criterias outside applications'
+ assert len(resp.pyquery('.section')) == 1
+ assert len(resp.pyquery('.section h3')) == 1
+ assert (
+ PyQuery(resp.pyquery('.section')[0]).find('h3').text()
+ == 'CriteriaCategory 1 [criteriacategory-1] Export Delete'
+ )
+
+ # check visible flag
+ application.visible = False
+ application.save()
+ resp = app.get('/manage/pricing/criterias/')
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('ul.objects-list img')) == 0
+ app.get('/manage/pricing/criterias/?application=%s' % application.slug, status=404)
+
+
+@pytest.mark.parametrize('icon', [True, False])
+def test_payer(app, admin_user, application_with_icon, application_without_icon, icon):
+ if icon:
+ application = application_with_icon
+ else:
+ application = application_without_icon
+
+ payer1 = Payer.objects.create(label='Payer 1')
+ payer2 = Payer.objects.create(label='Payer 2')
+ ApplicationElement.objects.create(content_object=payer2, application=application)
+ payer3 = Payer.objects.create(label='Payer 3')
+ ApplicationElement.objects.create(content_object=payer3, application=application)
+
+ app = login(app)
+
+ resp = app.get('/manage/invoicing/payers/')
+ assert len(resp.pyquery('ul.objects-list li')) == 3
+ assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Payer 1 [identifier: payer-1]'
+ assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'Payer 2 [identifier: payer-2]'
+ assert resp.pyquery('ul.objects-list li:nth-child(3)').text() == 'Payer 3 [identifier: payer-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 'Payers 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() == 'Payer 2 [identifier: payer-2]'
+ assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'Payer 3 [identifier: payer-3]'
+ assert len(resp.pyquery('ul.objects-list li img')) == 0
+
+ # check elements outside applications
+ resp = app.get('/manage/invoicing/payers/')
+ resp = resp.click('Payers outside applications')
+ assert resp.pyquery('h2').text() == 'Payers outside applications'
+ assert len(resp.pyquery('ul.objects-list li')) == 1
+ assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Payer 1 [identifier: payer-1]'
+
+ # check detail page
+ resp = app.get('/manage/invoicing/payer/%s/' % payer1.pk)
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
+ resp = app.get('/manage/invoicing/payer/%s/' % payer2.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/invoicing/payers/')
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('ul.objects-list img')) == 0
+ app.get('/manage/invoicing/payers/?application=%s' % application.slug, status=404)
+ resp = app.get('/manage/invoicing/payer/%s/' % payer2.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_regie(app, admin_user, application_with_icon, application_without_icon, icon):
+ if icon:
+ application = application_with_icon
+ else:
+ application = application_without_icon
+
+ regie1 = Regie.objects.create(label='Regie 1')
+ regie2 = Regie.objects.create(label='Regie 2')
+ ApplicationElement.objects.create(content_object=regie2, application=application)
+ regie3 = Regie.objects.create(label='Regie 3')
+ ApplicationElement.objects.create(content_object=regie3, application=application)
+
+ app = login(app)
+
+ resp = app.get('/manage/invoicing/regies/')
+ assert len(resp.pyquery('ul.objects-list li')) == 3
+ assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Regie 1 [identifier: regie-1]'
+ assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'Regie 2 [identifier: regie-2]'
+ assert resp.pyquery('ul.objects-list li:nth-child(3)').text() == 'Regie 3 [identifier: regie-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 'Regies 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() == 'Regie 2 [identifier: regie-2]'
+ assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'Regie 3 [identifier: regie-3]'
+ assert len(resp.pyquery('ul.objects-list li img')) == 0
+
+ # check elements outside applications
+ resp = app.get('/manage/invoicing/regies/')
+ resp = resp.click('Regies outside applications')
+ assert resp.pyquery('h2').text() == 'Regies outside applications'
+ assert len(resp.pyquery('ul.objects-list li')) == 1
+ assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'Regie 1 [identifier: regie-1]'
+
+ # check detail page
+ resp = app.get('/manage/invoicing/regie/%s/' % regie1.pk)
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0
+ resp = app.get('/manage/invoicing/regie/%s/' % regie2.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/invoicing/regies/')
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('ul.objects-list img')) == 0
+ app.get('/manage/invoicing/regies/?application=%s' % application.slug, status=404)
+ resp = app.get('/manage/invoicing/regie/%s/' % regie2.pk)
+ assert len(resp.pyquery('h3:contains("Applications")')) == 0
+ assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph')) == 0