manager: display applications (#86636)
gitea/lingo/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2024-02-08 11:49:50 +01:00 committed by Lauréline Guérin
parent 4a81e4207c
commit 26200949cc
29 changed files with 917 additions and 127 deletions

1
debian/control vendored
View File

@ -33,6 +33,7 @@ Depends: python3-django-mellon,
python3-hobo, python3-hobo,
python3-lingo (= ${binary:Version}), python3-lingo (= ${binary:Version}),
python3-psycopg2, python3-psycopg2,
python3-sorl-thumbnail,
python3-uwsgidecorators, python3-uwsgidecorators,
uwsgi, uwsgi,
uwsgi-plugin-python3, uwsgi-plugin-python3,

View File

@ -21,10 +21,11 @@ from django.db import models
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ 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 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) label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
category_label = models.CharField(_('Category label'), max_length=150, null=True) category_label = models.CharField(_('Category label'), max_length=150, null=True)
@ -125,7 +126,7 @@ class Agenda(models.Model):
return _('Events') return _('Events')
class CheckTypeGroup(models.Model): class CheckTypeGroup(WithApplicationMixin, models.Model):
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
label = models.CharField(_('Label'), max_length=150) label = models.CharField(_('Label'), max_length=150)
unexpected_presence = models.ForeignKey( unexpected_presence = models.ForeignKey(

View File

@ -21,6 +21,14 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models 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): class Application(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, unique=True) slug = models.SlugField(max_length=100, unique=True)
@ -71,34 +79,23 @@ class Application(models.Model):
@classmethod @classmethod
def populate_objects(cls, object_class, objects): def populate_objects(cls, object_class, objects):
content_type = ContentType.objects.get_for_model(object_class) 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) elements_by_objects = collections.defaultdict(list)
for element in elements: for element in elements:
elements_by_objects[element.content_object].append(element) elements_by_objects[element.object_id].append(element)
applications_by_ids = {
a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True)
}
for obj in objects: for obj in objects:
applications = [] applications = [element.application for element in elements_by_objects.get(obj.pk) or []]
elements = elements_by_objects.get(obj) or []
for element in elements:
application = applications_by_ids.get(element.application_id)
if application:
applications.append(application)
obj._applications = sorted(applications, key=lambda a: a.name) obj._applications = sorted(applications, key=lambda a: a.name)
@classmethod @classmethod
def load_for_object(cls, obj): def load_for_object(cls, obj):
content_type = ContentType.objects.get_for_model(obj.__class__) content_type = ContentType.objects.get_for_model(obj.__class__)
elements = ApplicationElement.objects.filter(content_type=content_type, object_id=obj.pk) elements = ApplicationElement.objects.filter(
applications_by_ids = { content_type=content_type, object_id=obj.pk, application__visible=True
a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True) ).prefetch_related('application')
} applications = [element.application for element in elements]
applications = []
for element in elements:
application = applications_by_ids.get(element.application_id)
if application:
applications.append(application)
obj._applications = sorted(applications, key=lambda a: a.name) obj._applications = sorted(applications, key=lambda a: a.name)
def get_objects_for_object_class(self, object_class): 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) elements = ApplicationElement.objects.filter(content_type=content_type, application=self)
return object_class.objects.filter(pk__in=elements.values('object_id')) 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): class ApplicationElement(models.Model):
application = models.ForeignKey(Application, on_delete=models.CASCADE) application = models.ForeignKey(Application, on_delete=models.CASCADE)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,47 @@
# lingo - payment and billing system
# Copyright (C) 2022-2024 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.shortcuts import get_object_or_404
from lingo.export_import.models import Application
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()

View File

@ -40,6 +40,7 @@ from django.utils.translation import gettext_lazy as _
from lingo.agendas.chrono import ChronoError, lock_events_check from lingo.agendas.chrono import ChronoError, lock_events_check
from lingo.agendas.models import Agenda from lingo.agendas.models import Agenda
from lingo.export_import.models import WithApplicationMixin
from lingo.utils.fields import RichTextField from lingo.utils.fields import RichTextField
from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug
from lingo.utils.wcs import ( from lingo.utils.wcs import (
@ -74,7 +75,7 @@ class PayerDataError(InvoicingError):
pass pass
class Payer(models.Model): class Payer(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150) label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
description = models.TextField(_('Description'), null=True, blank=True) description = models.TextField(_('Description'), null=True, blank=True)
@ -296,7 +297,7 @@ INVOICE_MODELS = [
] ]
class Regie(models.Model): class Regie(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150) label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
description = models.TextField( description = models.TextField(

View File

@ -93,5 +93,8 @@
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-edit' pk=payer.pk %}" rel="popup">{% trans "Edit" %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-edit' pk=payer.pk %}" rel="popup">{% trans "Edit" %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-export' pk=payer.pk %}">{% trans 'Export' %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-export' pk=payer.pk %}">{% trans 'Export' %}</a>
{% url 'lingo-manager-invoicing-payer-list' as object_list_url %}
{% include 'lingo/includes/application_detail_fragment.html' %}
</aside> </aside>
{% endblock %} {% endblock %}

View File

@ -4,12 +4,15 @@
{% block page-title-extra-label %}{% trans "Payers" %}{% endblock %} {% block page-title-extra-label %}{% trans "Payers" %}{% endblock %}
{% block breadcrumb %} {% block breadcrumb %}
{{ block.super }} <a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</a>
<a href="{% url 'lingo-manager-invoicing-payer-list' %}">{% trans "Payers" %}</a> <a href="{% url 'lingo-manager-invoicing-regie-list' %}">{% trans "Regies" %}</a>
{% url 'lingo-manager-invoicing-payer-list' as object_list_url %}
<a href="{{ object_list_url }}">{% trans "Payers" %}</a>
{% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Payers outside applications') %}
{% endblock %} {% endblock %}
{% block appbar %} {% block appbar %}
<h2>{% trans 'Payers' %}</h2> {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Payers outside applications') title_object_list=_('Payers') %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -19,6 +22,7 @@
{% for payer in object_list %} {% for payer in object_list %}
<li> <li>
<a href="{% url 'lingo-manager-invoicing-payer-detail' pk=payer.pk %}"> <a href="{% url 'lingo-manager-invoicing-payer-detail' pk=payer.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' with object=payer %}
{{ payer.label }} {{ payer.label }}
<span class="extra-info"> [{% trans "identifier:" %} {{ payer.slug }}]</span> <span class="extra-info"> [{% trans "identifier:" %} {{ payer.slug }}]</span>
</a> </a>
@ -26,8 +30,7 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% else %} {% elif not no_application %}
<div class="big-msg-info"> <div class="big-msg-info">
{% blocktrans %} {% blocktrans %}
This site doesn't have any payer yet. Click on the "New" button in the top This site doesn't have any payer yet. Click on the "New" button in the top
@ -38,10 +41,14 @@
{% endblock %} {% endblock %}
{% block sidebar %} {% block sidebar %}
<aside id="sidebar"> {% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3> <h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-payer-add' %}">{% trans 'New payer' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-payer-add' %}">{% trans 'New payer' %}</a>
</aside> {% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Payers outside applications') %}
</aside>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -54,5 +54,8 @@
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-refund-list' regie_pk=regie.pk %}">{% trans 'Refunds' %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-regie-refund-list' regie_pk=regie.pk %}">{% trans 'Refunds' %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' regie_pk=regie.pk %}">{% trans 'Non invoiced lines' %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' regie_pk=regie.pk %}">{% trans 'Non invoiced lines' %}</a>
{% url 'lingo-manager-invoicing-regie-list' as object_list_url %}
{% include 'lingo/includes/application_detail_fragment.html' %}
</aside> </aside>
{% endblock %} {% endblock %}

View File

@ -5,11 +5,13 @@
{% block breadcrumb %} {% block breadcrumb %}
{{ block.super }} {{ block.super }}
<a href="{% url 'lingo-manager-invoicing-regie-list' %}">{% trans "Regies" %}</a> {% url 'lingo-manager-invoicing-regie-list' as object_list_url %}
<a href="{{ object_list_url }}">{% trans "Regies" %}</a>
{% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Regies outside applications') %}
{% endblock %} {% endblock %}
{% block appbar %} {% block appbar %}
<h2>{% trans 'Regies' %}</h2> {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Regies outside applications') title_object_list=_('Regies') %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -19,6 +21,7 @@
{% for regie in object_list %} {% for regie in object_list %}
<li> <li>
<a href="{% url 'lingo-manager-invoicing-regie-detail' pk=regie.pk %}"> <a href="{% url 'lingo-manager-invoicing-regie-detail' pk=regie.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' with object=regie %}
{{ regie.label }} {{ regie.label }}
<span class="extra-info"> [{% trans "identifier:" %} {{ regie.slug }}]</span> <span class="extra-info"> [{% trans "identifier:" %} {{ regie.slug }}]</span>
</a> </a>
@ -26,8 +29,7 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% else %} {% elif not no_application %}
<div class="big-msg-info"> <div class="big-msg-info">
{% blocktrans %} {% blocktrans %}
This site doesn't have any regie yet. Click on the "New" button in the top This site doesn't have any regie yet. Click on the "New" button in the top
@ -38,16 +40,20 @@
{% endblock %} {% endblock %}
{% block sidebar %} {% block sidebar %}
<aside id="sidebar"> {% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3> <h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-regie-add' %}">{% trans 'New regie' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-regie-add' %}">{% trans 'New regie' %}</a>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-config-import' %}">{% trans 'Import site' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-config-import' %}">{% trans 'Import site' %}</a>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-config-export' %}" data-autoclose-dialog="true">{% trans 'Export site' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-invoicing-config-export' %}" data-autoclose-dialog="true">{% trans 'Export site' %}</a>
<h3>{% trans "Navigation" %}</h3> <h3>{% trans "Navigation" %}</h3>
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-appearance-settings' %}">{% trans "Appearance Settings" %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-appearance-settings' %}">{% trans "Appearance Settings" %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-list' %}">{% trans "Payers" %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-list' %}">{% trans "Payers" %}</a>
</aside> {% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Regies outside applications') %}
</aside>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -21,14 +21,26 @@ from django.http import HttpResponse
from django.urls import reverse from django.urls import reverse
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView 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.forms import NewPayerForm, PayerForm, PayerMappingForm
from lingo.invoicing.models import Payer from lingo.invoicing.models import Payer
class PayersListView(ListView): class PayersListView(WithApplicationsMixin, ListView):
template_name = 'lingo/invoicing/manager_payer_list.html' template_name = 'lingo/invoicing/manager_payer_list.html'
model = Payer 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() payers_list = PayersListView.as_view()

View File

@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
from lingo.agendas.models import Agenda from lingo.agendas.models import Agenda
from lingo.export_import.views import WithApplicationsMixin
from lingo.invoicing.forms import ( from lingo.invoicing.forms import (
PaymentTypeForm, PaymentTypeForm,
RegieCreditFilterSet, RegieCreditFilterSet,
@ -69,10 +70,21 @@ def import_regies(data):
return results return results
class RegiesListView(ListView): class RegiesListView(WithApplicationsMixin, ListView):
template_name = 'lingo/invoicing/manager_regie_list.html' template_name = 'lingo/invoicing/manager_regie_list.html'
model = Regie 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() regies_list = RegiesListView.as_view()

View File

@ -170,3 +170,7 @@ span.invoice-colour {
border-radius: 0; border-radius: 0;
vertical-align: middle; vertical-align: middle;
} }
.application-logo, .application-icon {
vertical-align: middle;
}

View File

@ -25,6 +25,7 @@ from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from lingo.agendas.models import Agenda, CheckType 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.misc import LingoImportError, clean_import_data, generate_slug
from lingo.utils.wcs import get_wcs_dependencies_from_template from lingo.utils.wcs import get_wcs_dependencies_from_template
@ -99,7 +100,7 @@ class PricingBookingCheckTypeError(PricingError):
pass pass
class CriteriaCategory(models.Model): class CriteriaCategory(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150) label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
@ -261,7 +262,7 @@ class PricingMatrix:
rows: list[PricingMatrixRow] rows: list[PricingMatrixRow]
class Pricing(models.Model): class Pricing(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150, null=True) label = models.CharField(_('Label'), max_length=150, null=True)
slug = models.SlugField(_('Identifier'), max_length=160, null=True) slug = models.SlugField(_('Identifier'), max_length=160, null=True)

View File

@ -116,5 +116,8 @@
<a class="button button-paragraph" href="{{ chrono_url }}">{% trans "Agenda options" %}</a> <a class="button button-paragraph" href="{{ chrono_url }}">{% trans "Agenda options" %}</a>
{% endif %}{% endwith %} {% endif %}{% endwith %}
{% url 'lingo-manager-agenda-list' as object_list_url %}
{% include 'lingo/includes/application_detail_fragment.html' %}
</aside> </aside>
{% endblock %} {% endblock %}

View File

@ -1,15 +1,18 @@
{% extends "lingo/pricing/manager_pricing_list.html" %} {% extends "lingo/pricing/manager_pricing_list.html" %}
{% load i18n %} {% load i18n thumbnail %}
{% block page-title-extra-label %}{% trans "Agendas" %} | {{ block.super }}{% endblock %} {% block page-title-extra-label %}{% trans "Agendas" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %} {% block breadcrumb %}
{{ block.super }} <a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</a>
<a href="{% url 'lingo-manager-agenda-list' %}">{% trans 'Agendas' %}</a> <a href="{% url 'lingo-manager-pricing-list' %}">{% trans "Pricings" %}</a>
{% url 'lingo-manager-agenda-list' as object_list_url %}
<a href="{{ object_list_url }}">{% trans "Agendas" %}</a>
{% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Agendas outside applications') %}
{% endblock %} {% endblock %}
{% block appbar %} {% block appbar %}
<h2>{% trans 'Agendas' %}</h2> {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Agendas outside applications') title_object_list=_('Agendas') %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -21,14 +24,17 @@
<ul class="objects-list single-links"> <ul class="objects-list single-links">
{% for object in group.list %} {% for object in group.list %}
<li> <li>
<a href="{% url 'lingo-manager-agenda-detail' object.pk %}">{{ object.label }} <span class="identifier">[{% trans "identifier:" %} {{ object.slug }}, {% trans "kind:" %} {{ object.get_real_kind_display }}]</a> <a href="{% url 'lingo-manager-agenda-detail' object.pk %}">
{% with chrono_url=object.get_chrono_url %}{% if chrono_url %}<a href="{{ chrono_url }}" class="link-action-icon link">{% trans "view" %}</a>{% endif %}{% endwith %} {% include 'lingo/includes/application_icon_fragment.html' %}
</li> {{ object.label }} <span class="identifier">[{% trans "identifier:" %} {{ object.slug }}, {% trans "kind:" %} {{ object.get_real_kind_display }}]</span>
</a>
{% with chrono_url=object.get_chrono_url %}{% if chrono_url %}<a href="{{ chrono_url }}" class="link-action-icon link">{% trans "view" %}</a>{% endif %}{% endwith %}
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% elif not no_application %}
<div class="big-msg-info"> <div class="big-msg-info">
{% blocktrans trimmed %} {% blocktrans trimmed %}
This site doesn't have any agenda yet. Click on the "Refresh agendas" button in the top This site doesn't have any agenda yet. Click on the "Refresh agendas" button in the top
@ -39,10 +45,14 @@
{% endblock %} {% endblock %}
{% block sidebar %} {% block sidebar %}
<aside id="sidebar"> {% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3> <h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" href="{% url 'lingo-manager-agenda-sync' %}">{% trans 'Refresh agendas' %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-agenda-sync' %}">{% trans 'Refresh agendas' %}</a>
</aside> {% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Agendas outside applications') %}
</aside>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -4,22 +4,30 @@
{% block page-title-extra-label %}{% trans "Check types" %} | {{ block.super }}{% endblock %} {% block page-title-extra-label %}{% trans "Check types" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %} {% block breadcrumb %}
{{ block.super }} <a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</a>
<a href="{% url 'lingo-manager-check-type-list' %}">{% trans "Check types" %}</a> <a href="{% url 'lingo-manager-pricing-list' %}">{% trans "Pricings" %}</a>
{% url 'lingo-manager-check-type-list' as object_list_url %}
<a href="{{ object_list_url }}">{% trans "Check types" %}</a>
{% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Check types outside applications') %}
{% endblock %} {% endblock %}
{% block appbar %} {% block appbar %}
<h2>{% trans 'Check types' %}</h2> {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Check types outside applications') title_object_list=_('Check types') %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="pk-information"> {% if not application and not no_application %}
<p>{% trans "Define here check types used in events agendas to check bookings." %}</p> <div class="pk-information">
</div> <p>{% trans "Define here check types used in events agendas to check bookings." %}</p>
</div>
{% endif %}
{% for object in object_list %} {% for object in object_list %}
<div class="section check-type-group"> <div class="section check-type-group">
<h3> <h3>
<a rel="popup" href="{% url 'lingo-manager-check-type-group-edit' object.pk %}">{{ object }}</a> <a rel="popup" href="{% url 'lingo-manager-check-type-group-edit' object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ object }}
</a>
<span> <span>
<a class="button" href="{% url 'lingo-manager-check-type-group-export' object.pk %}">{% trans "Export"%}</a> <a class="button" href="{% url 'lingo-manager-check-type-group-export' object.pk %}">{% trans "Export"%}</a>
<a class="button" rel="popup" href="{% url 'lingo-manager-check-type-group-delete' object.pk %}">{% trans "Delete"%}</a> <a class="button" rel="popup" href="{% url 'lingo-manager-check-type-group-delete' object.pk %}">{% trans "Delete"%}</a>
@ -49,20 +57,26 @@
</div> </div>
</div> </div>
{% empty %} {% empty %}
<div class="big-msg-info"> {% if not no_application %}
{% blocktrans trimmed %} <div class="big-msg-info">
This site doesn't have any check type group yet. Click on the "New group" button in the top {% blocktrans trimmed %}
right of the page to add a first one. This site doesn't have any check type group yet. Click on the "New group" button in the top
{% endblocktrans %} right of the page to add a first one.
</div> {% endblocktrans %}
</div>
{% endif %}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
{% block sidebar %} {% block sidebar %}
<aside id="sidebar"> {% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3> <h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-check-type-group-add' %}">{% trans 'New group' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-check-type-group-add' %}">{% trans 'New group' %}</a>
</aside> {% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Check types outside applications') %}
</aside>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -4,30 +4,40 @@
{% block page-title-extra-label %}{% trans "Criterias" %} | {{ block.super }}{% endblock %} {% block page-title-extra-label %}{% trans "Criterias" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %} {% block breadcrumb %}
{{ block.super }} <a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</a>
<a href="{% url 'lingo-manager-pricing-criteria-list' %}">{% trans "Criterias" %}</a> <a href="{% url 'lingo-manager-pricing-list' %}">{% trans "Pricings" %}</a>
{% url 'lingo-manager-pricing-criteria-list' as object_list_url %}
<a href="{{ object_list_url }}">{% trans "Criterias" %}</a>
{% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Criterias outside applications') %}
{% endblock %} {% endblock %}
{% block appbar %} {% block appbar %}
<h2>{% trans 'Criterias' %}</h2> {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Criterias outside applications') title_object_list=_('Criterias') %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="pk-information"> {% if not application and not no_application %}
<p>{% trans "Define here pricing criterias used in pricings." %}</p> <div class="pk-information">
</div> <p>{% trans "Define here pricing criterias used in pricings." %}</p>
</div>
{% endif %}
{% if object_list %} {% if object_list %}
<p class="hint"> {% if not application and not no_application %}
{% blocktrans trimmed %} <p class="hint">
Use drag and drop with the ⣿ handles to reorder criterias inside a category. {% blocktrans trimmed %}
{% endblocktrans %} Use drag and drop with the ⣿ handles to reorder criterias inside a category.
</p> {% endblocktrans %}
</p>
{% endif %}
{% endif %} {% endif %}
{% for object in object_list %} {% for object in object_list %}
<div class="section criteria-category"> <div class="section criteria-category">
<h3> <h3>
<a rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-edit' object.pk %}">{{ object }} [{{ object.slug }}]</a> <a rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-edit' object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ object }} [{{ object.slug }}]
</a>
<span> <span>
<a class="button" href="{% url 'lingo-manager-pricing-criteria-category-export' object.pk %}">{% trans "Export"%}</a> <a class="button" href="{% url 'lingo-manager-pricing-criteria-category-export' object.pk %}">{% trans "Export"%}</a>
<a class="button" rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-delete' object.pk %}">{% trans "Delete"%}</a> <a class="button" rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-delete' object.pk %}">{% trans "Delete"%}</a>
@ -37,7 +47,7 @@
<ul class="objects-list single-links sortable" data-order-url="{% url 'lingo-manager-pricing-criteria-order' object.pk %}"> <ul class="objects-list single-links sortable" data-order-url="{% url 'lingo-manager-pricing-criteria-order' object.pk %}">
{% for criteria in object.criterias.all %} {% for criteria in object.criterias.all %}
<li{% if not criteria.default %} class="sortable-item" data-item-id="{{ criteria.pk }}"{% endif %}> <li{% if not criteria.default %} class="sortable-item" data-item-id="{{ criteria.pk }}"{% endif %}>
{% if not criteria.default %}<span class="handle"></span>{% endif %} {% if not criteria.default and not application and not no_application %}<span class="handle"></span>{% endif %}
<a rel="popup" href="{% url 'lingo-manager-pricing-criteria-edit' object.pk criteria.pk %}">{{ criteria }}{% if criteria.default %} <span class="extra-info">- {% trans "default" %}</span>{% endif %}</a> <a rel="popup" href="{% url 'lingo-manager-pricing-criteria-edit' object.pk criteria.pk %}">{{ criteria }}{% if criteria.default %} <span class="extra-info">- {% trans "default" %}</span>{% endif %}</a>
<a class="delete" rel="popup" href="{% url 'lingo-manager-pricing-criteria-delete' object.pk criteria.pk %}">{% trans "delete"%}</a> <a class="delete" rel="popup" href="{% url 'lingo-manager-pricing-criteria-delete' object.pk criteria.pk %}">{% trans "delete"%}</a>
</li> </li>
@ -47,20 +57,26 @@
</div> </div>
</div> </div>
{% empty %} {% empty %}
<div class="big-msg-info"> {% if not no_application %}
{% blocktrans trimmed %} <div class="big-msg-info">
This site doesn't have any pricing category yet. Click on the "New category" button in the top {% blocktrans trimmed %}
right of the page to add a first one. This site doesn't have any pricing category yet. Click on the "New category" button in the top
{% endblocktrans %} right of the page to add a first one.
</div> {% endblocktrans %}
</div>
{% endif %}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
{% block sidebar %} {% block sidebar %}
<aside id="sidebar"> {% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3> <h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-add' %}">{% trans 'New category' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-add' %}">{% trans 'New category' %}</a>
</aside> {% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Criterias outside applications') %}
</aside>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -283,5 +283,8 @@
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-edit' object.pk %}">{% trans 'Options' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-edit' object.pk %}">{% trans 'Options' %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-pricing-export' pk=object.pk %}">{% trans 'Export' %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-pricing-export' pk=object.pk %}">{% trans 'Export' %}</a>
{% url 'lingo-manager-pricing-list' as object_list_url %}
{% include 'lingo/includes/application_detail_fragment.html' %}
</aside> </aside>
{% endblock %} {% endblock %}

View File

@ -1,21 +1,25 @@
{% extends "lingo/manager_homepage.html" %} {% extends "lingo/manager_homepage.html" %}
{% load i18n %} {% load i18n %}
{% block page-title-extra-label %}{% trans "Pricings" context 'agenda pricing' %}{% endblock %} {% block page-title-extra-label %}{% trans "Pricings" %}{% endblock %}
{% block breadcrumb %} {% block breadcrumb %}
{{ block.super }} {{ block.super }}
<a href="{% url 'lingo-manager-pricing-list' %}">{% trans "Pricings" context 'agenda pricing' %}</a> {% url 'lingo-manager-pricing-list' as object_list_url %}
<a href="{{ object_list_url }}">{% trans "Pricings" %}</a>
{% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Pricings outside applications') %}
{% endblock %} {% endblock %}
{% block appbar %} {% block appbar %}
<h2>{% trans 'Pricings' context 'agenda pricing' %}</h2> {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Pricings outside applications') title_object_list=_('Pricings') %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="pk-information"> {% if not application and not no_application %}
<p>{% trans "Define here pricings to attach to events agendas." %}</p> <div class="pk-information">
</div> <p>{% trans "Define here pricings to attach to events agendas." %}</p>
</div>
{% endif %}
{% if object_list %} {% if object_list %}
<div> <div>
<h3>{% trans "Pricings" context 'agenda pricing' %}</h3> <h3>{% trans "Pricings" context 'agenda pricing' %}</h3>
@ -23,6 +27,7 @@
{% for object in object_list %}{% if not object.flat_fee_schedule %} {% for object in object_list %}{% if not object.flat_fee_schedule %}
<li> <li>
<a href="{% url 'lingo-manager-pricing-detail' pk=object.pk %}"> <a href="{% url 'lingo-manager-pricing-detail' pk=object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ object }} {{ object }}
- {% blocktrans trimmed with start=object.date_start|date:'d/m/Y' end=object.date_end|date:'d/m/Y' %}From {{ start }} to {{ end }}{% endblocktrans %}) - {% blocktrans trimmed with start=object.date_start|date:'d/m/Y' end=object.date_end|date:'d/m/Y' %}From {{ start }} to {{ end }}{% endblocktrans %})
<span class="extra-info"> [{% trans "identifier:" %} {{ object.slug }}]</span> <span class="extra-info"> [{% trans "identifier:" %} {{ object.slug }}]</span>
@ -35,6 +40,7 @@
{% for object in object_list %}{% if object.flat_fee_schedule %} {% for object in object_list %}{% if object.flat_fee_schedule %}
<li> <li>
<a href="{% url 'lingo-manager-pricing-detail' pk=object.pk %}"> <a href="{% url 'lingo-manager-pricing-detail' pk=object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ object }} {{ object }}
- {% blocktrans trimmed with start=object.date_start|date:'d/m/Y' end=object.date_end|date:'d/m/Y' %}From {{ start }} to {{ end }}{% endblocktrans %}) - {% blocktrans trimmed with start=object.date_start|date:'d/m/Y' end=object.date_end|date:'d/m/Y' %}From {{ start }} to {{ end }}{% endblocktrans %})
<span class="extra-info"> [{% trans "identifier:" %} {{ object.slug }}]</span> <span class="extra-info"> [{% trans "identifier:" %} {{ object.slug }}]</span>
@ -43,7 +49,7 @@
{% endif %}{% endfor %} {% endif %}{% endfor %}
</ul> </ul>
</div> </div>
{% else %} {% elif not no_application %}
<div class="big-msg-info"> <div class="big-msg-info">
{% blocktrans trimmed %} {% blocktrans trimmed %}
This site doesn't have any pricing yet. Click on the "New pricing" button in the top This site doesn't have any pricing yet. Click on the "New pricing" button in the top
@ -54,17 +60,21 @@
{% endblock %} {% endblock %}
{% block sidebar %} {% block sidebar %}
<aside id="sidebar"> {% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3> <h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-add' %}">{% trans 'New pricing' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-add' %}">{% trans 'New pricing' %}</a>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-config-import' %}">{% trans 'Import site' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-config-import' %}">{% trans 'Import site' %}</a>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-config-export' %}" data-autoclose-dialog="true">{% trans 'Export site' %}</a> <a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-config-export' %}" data-autoclose-dialog="true">{% trans 'Export site' %}</a>
<h3>{% trans "Navigation" %}</h3> <h3>{% trans "Navigation" %}</h3>
<a class="button button-paragraph" href="{% url 'lingo-manager-pricing-criteria-list' %}">{% trans "Criterias" %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-pricing-criteria-list' %}">{% trans "Criterias" %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-agenda-list' %}">{% trans "Agendas" %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-agenda-list' %}">{% trans "Agendas" %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-check-type-list' %}">{% trans "Check types" %}</a> <a class="button button-paragraph" href="{% url 'lingo-manager-check-type-list' %}">{% trans "Check types" %}</a>
</aside> {% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Pricings outside applications') %}
</aside>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -41,6 +41,7 @@ from django.views.generic.detail import SingleObjectMixin
from lingo.agendas.chrono import refresh_agendas from lingo.agendas.chrono import refresh_agendas
from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
from lingo.agendas.views import AgendaMixin from lingo.agendas.views import AgendaMixin
from lingo.export_import.views import WithApplicationsMixin
from lingo.pricing.forms import ( from lingo.pricing.forms import (
CheckTypeForm, CheckTypeForm,
CheckTypeGroupUnexpectedPresenceForm, CheckTypeGroupUnexpectedPresenceForm,
@ -221,12 +222,21 @@ class ConfigImportView(FormView):
config_import = ConfigImportView.as_view() config_import = ConfigImportView.as_view()
class CriteriaListView(ListView): class CriteriaListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_criteria_list.html' template_name = 'lingo/pricing/manager_criteria_list.html'
model = CriteriaCategory model = CriteriaCategory
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self): 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() criteria_list = CriteriaListView.as_view()
@ -368,14 +378,22 @@ class CriteriaDeleteView(DeleteView):
criteria_delete = CriteriaDeleteView.as_view() criteria_delete = CriteriaDeleteView.as_view()
class AgendaListView(ListView): class AgendaListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_agenda_list.html' template_name = 'lingo/pricing/manager_agenda_list.html'
model = Agenda model = Agenda
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = self.with_applications_queryset()
return queryset.order_by('category_label', 'label') 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() agenda_list = AgendaListView.as_view()
@ -467,12 +485,21 @@ class AgendaInvoicingSettingsView(AgendaMixin, UpdateView):
agenda_invoicing_settings = AgendaInvoicingSettingsView.as_view() agenda_invoicing_settings = AgendaInvoicingSettingsView.as_view()
class PricingListView(ListView): class PricingListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_pricing_list.html' template_name = 'lingo/pricing/manager_pricing_list.html'
model = Pricing model = Pricing
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self): 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() pricing_list = PricingListView.as_view()
@ -965,12 +992,21 @@ class PricingMatrixEdit(FormView):
pricing_matrix_edit = PricingMatrixEdit.as_view() pricing_matrix_edit = PricingMatrixEdit.as_view()
class CheckTypeListView(ListView): class CheckTypeListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_check_type_list.html' template_name = 'lingo/pricing/manager_check_type_list.html'
model = CheckTypeGroup model = CheckTypeGroup
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self): 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() check_type_list = CheckTypeListView.as_view()

View File

@ -57,6 +57,7 @@ INSTALLED_APPS = (
'gadjo', 'gadjo',
'rest_framework', 'rest_framework',
'django_filters', 'django_filters',
'sorl.thumbnail',
'lingo.agendas', 'lingo.agendas',
'lingo.api', 'lingo.api',
'lingo.basket', 'lingo.basket',
@ -231,6 +232,10 @@ CKEDITOR_CONFIGS = {
BASKET_EXPIRY_DELAY = 60 # 1 hour by default 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( local_settings_file = os.environ.get(
'LINGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py') 'LINGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
) )

View File

@ -168,6 +168,7 @@ setup(
'djangorestframework>=3.3, <3.15', 'djangorestframework>=3.3, <3.15',
'django-filter', 'django-filter',
'weasyprint', 'weasyprint',
'sorl-thumbnail',
], ],
zip_safe=False, zip_safe=False,
cmdclass={ cmdclass={

BIN
tests/data/black.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

View File

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