manager: display applications (#86636) #162

Merged
lguerin merged 1 commits from wip/86636-applification-in-ui into main 2024-03-15 17:14:41 +01:00
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-lingo (= ${binary:Version}),
python3-psycopg2,
python3-sorl-thumbnail,
python3-uwsgidecorators,
uwsgi,
uwsgi-plugin-python3,

View File

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

View File

@ -21,6 +21,14 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models
class WithApplicationMixin:
@property
def applications(self):
pmarillonnet marked this conversation as resolved Outdated

Ok, j’ai loupé un bout de l’UI applification je pense alors je ne suis pas sûr de comprendre : je vois plus bas cette propriété utilisée dans les templates, mais c’est dans quels cas qu’on a intérêt à consulter les applications dont l’objet fait partie plutôt que le (a priori plus intuitif) inverse à savoir les objets contenus dans une application ?

Edit: Ah, ok, pigé dans la suite de la relecture, on affiche l’info de la liste des applications concernées pour chaque objet concerné dans le BO.

Ok, j’ai loupé un bout de l’UI applification je pense alors je ne suis pas sûr de comprendre : je vois plus bas cette propriété utilisée dans les templates, mais c’est dans quels cas qu’on a intérêt à consulter les applications dont l’objet fait partie plutôt que le (a priori plus intuitif) inverse à savoir les objets contenus dans une application ? Edit: Ah, ok, pigé dans la suite de la relecture, on affiche l’info de la liste des applications concernées pour chaque objet concerné dans le BO.
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)
pmarillonnet marked this conversation as resolved Outdated

Pour quelle raison ici la recherche des objets orphelins se soucie de application__visible=True alors que la méthode similaire get_objects_for_object_class (juste au dessus) ne prend pas en compte ce flag visible ?

Pour quelle raison ici la recherche des objets orphelins se soucie de `application__visible=True` alors que la méthode similaire `get_objects_for_object_class` (juste au dessus) ne prend pas en compte ce flag `visible` ?

get_objects_for_object_class s'applique à une Application déjà connue (on l'a dans l'url), et visible:


        if 'application' in self.request.GET:
            self.application = get_object_or_404(
                Application, slug=self.request.GET['application'], visible=True
            )

(cf WithApplicationsMixin)

get_orphan_objects_for_object_classcherche les objets ne faisant partie d'aucune application visible

`get_objects_for_object_class` s'applique à une Application déjà connue (on l'a dans l'url), et visible: ``` if 'application' in self.request.GET: self.application = get_object_or_404( Application, slug=self.request.GET['application'], visible=True ) ``` (cf WithApplicationsMixin) `get_orphan_objects_for_object_class`cherche les objets ne faisant partie d'aucune application visible

Hmm, okay, j’avais loupé ça, merci pour l’explication.

Hmm, okay, j’avais loupé ça, merci pour l’explication.
return object_class.objects.exclude(pk__in=elements.values('object_id'))
class ApplicationElement(models.Model):
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.models import Agenda
from lingo.export_import.models import WithApplicationMixin
from lingo.utils.fields import RichTextField
from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug
from lingo.utils.wcs import (
@ -74,7 +75,7 @@ class PayerDataError(InvoicingError):
pass
class Payer(models.Model):
class Payer(WithApplicationMixin, models.Model):
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Identifier'), max_length=160, unique=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)
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
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-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>
{% endblock %}

View File

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

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-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>
{% endblock %}

View File

@ -5,11 +5,13 @@
{% block breadcrumb %}
{{ 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 %}
{% 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 %}
{% block content %}
@ -19,6 +21,7 @@
{% for regie in object_list %}
<li>
<a href="{% url 'lingo-manager-invoicing-regie-detail' pk=regie.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' with object=regie %}
{{ regie.label }}
<span class="extra-info"> [{% trans "identifier:" %} {{ regie.slug }}]</span>
</a>
@ -26,8 +29,7 @@
{% endfor %}
</ul>
</div>
{% else %}
{% elif not no_application %}
<div class="big-msg-info">
{% blocktrans %}
This site doesn't have any regie yet. Click on the "New" button in the top
@ -38,16 +40,20 @@
{% endblock %}
{% block sidebar %}
<aside id="sidebar">
{% if not application and not no_application %}
<aside id="sidebar">
<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-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>
<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-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>
<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-payer-list' %}">{% trans "Payers" %}</a>
<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-payer-list' %}">{% trans "Payers" %}</a>
</aside>
{% include 'lingo/includes/application_list_fragment.html' with title_no_application=_('Regies outside applications') %}
</aside>
{% endif %}
{% endblock %}

View File

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

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

View File

@ -170,3 +170,7 @@ span.invoice-colour {
border-radius: 0;
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 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)

View File

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

View File

@ -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 }}
<a href="{% url 'lingo-manager-agenda-list' %}">{% trans 'Agendas' %}</a>
<a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</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 %}
{% 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 %}
{% block content %}
@ -21,14 +24,17 @@
<ul class="objects-list single-links">
{% for object in group.list %}
<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>
{% 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>
<a href="{% url 'lingo-manager-agenda-detail' object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ 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 %}
</ul>
</div>
{% endfor %}
{% else %}
{% elif not no_application %}
<div class="big-msg-info">
{% blocktrans trimmed %}
This site doesn't have any agenda yet. Click on the "Refresh agendas" button in the top
@ -39,10 +45,14 @@
{% endblock %}
{% block sidebar %}
<aside id="sidebar">
{% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" href="{% url 'lingo-manager-agenda-sync' %}">{% trans 'Refresh agendas' %}</a>
<h3>{% trans "Actions" %}</h3>
<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 %}

View File

@ -4,22 +4,30 @@
{% block page-title-extra-label %}{% trans "Check types" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-check-type-list' %}">{% trans "Check types" %}</a>
<a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</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 %}
{% 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 %}
{% block content %}
<div class="pk-information">
<p>{% trans "Define here check types used in events agendas to check bookings." %}</p>
</div>
{% if not application and not no_application %}
<div class="pk-information">
<p>{% trans "Define here check types used in events agendas to check bookings." %}</p>
</div>
{% endif %}
{% for object in object_list %}
<div class="section check-type-group">
<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>
<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>
@ -48,20 +56,26 @@
</div>
</div>
{% empty %}
<div class="big-msg-info">
{% 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 %}
</div>
{% if not no_application %}
<div class="big-msg-info">
{% 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 %}
</div>
{% endif %}
{% endfor %}
{% endblock %}
{% block sidebar %}
<aside id="sidebar">
{% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-check-type-group-add' %}">{% trans 'New group' %}</a>
<h3>{% trans "Actions" %}</h3>
<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 %}

View File

@ -4,30 +4,40 @@
{% block page-title-extra-label %}{% trans "Criterias" %} | {{ block.super }}{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-pricing-criteria-list' %}">{% trans "Criterias" %}</a>
<a href="{% url 'lingo-manager-homepage' %}">{% trans "Payments" context 'lingo title' %}</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 %}
{% 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 %}
{% block content %}
<div class="pk-information">
<p>{% trans "Define here pricing criterias used in pricings." %}</p>
</div>
{% if not application and not no_application %}
<div class="pk-information">
<p>{% trans "Define here pricing criterias used in pricings." %}</p>
</div>
{% endif %}
{% if object_list %}
<p class="hint">
{% blocktrans trimmed %}
Use drag and drop with the ⣿ handles to reorder criterias inside a category.
{% endblocktrans %}
</p>
{% if not application and not no_application %}
<p class="hint">
{% blocktrans trimmed %}
Use drag and drop with the ⣿ handles to reorder criterias inside a category.
{% endblocktrans %}
</p>
{% endif %}
{% endif %}
{% for object in object_list %}
<div class="section criteria-category">
<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>
<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>
@ -37,7 +47,7 @@
<ul class="objects-list single-links sortable" data-order-url="{% url 'lingo-manager-pricing-criteria-order' object.pk %}">
{% for criteria in object.criterias.all %}
<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 class="delete" rel="popup" href="{% url 'lingo-manager-pricing-criteria-delete' object.pk criteria.pk %}">{% trans "delete"%}</a>
</li>
@ -47,20 +57,26 @@
</div>
</div>
{% empty %}
<div class="big-msg-info">
{% 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 %}
</div>
{% if not no_application %}
<div class="big-msg-info">
{% 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 %}
</div>
{% endif %}
{% endfor %}
{% endblock %}
{% block sidebar %}
<aside id="sidebar">
{% if not application and not no_application %}
<aside id="sidebar">
<h3>{% trans "Actions" %}</h3>
<a class="button button-paragraph" rel="popup" href="{% url 'lingo-manager-pricing-criteria-category-add' %}">{% trans 'New category' %}</a>
<h3>{% trans "Actions" %}</h3>
<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 %}

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" 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>
{% endblock %}

View File

@ -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 }}
<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 %}
{% 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 %}
{% block content %}
<div class="pk-information">
<p>{% trans "Define here pricings to attach to events agendas." %}</p>
</div>
{% if not application and not no_application %}
<div class="pk-information">
<p>{% trans "Define here pricings to attach to events agendas." %}</p>
</div>
{% endif %}
{% if object_list %}
<div>
<h3>{% trans "Pricings" context 'agenda pricing' %}</h3>
@ -23,6 +27,7 @@
{% for object in object_list %}{% if not object.flat_fee_schedule %}
<li>
<a href="{% url 'lingo-manager-pricing-detail' pk=object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ 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 %})
<span class="extra-info"> [{% trans "identifier:" %} {{ object.slug }}]</span>
@ -35,6 +40,7 @@
{% for object in object_list %}{% if object.flat_fee_schedule %}
<li>
<a href="{% url 'lingo-manager-pricing-detail' pk=object.pk %}">
{% include 'lingo/includes/application_icon_fragment.html' %}
{{ 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 %})
<span class="extra-info"> [{% trans "identifier:" %} {{ object.slug }}]</span>
@ -43,7 +49,7 @@
{% endif %}{% endfor %}
</ul>
</div>
{% else %}
{% elif not no_application %}
<div class="big-msg-info">
{% 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 %}
<aside id="sidebar">
{% if not application and not no_application %}
<aside id="sidebar">
<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-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>
<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-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>
<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-agenda-list' %}">{% trans "Agendas" %}</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-check-type-list' %}">{% trans "Check types" %}</a>
<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-agenda-list' %}">{% trans "Agendas" %}</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 %}

View File

@ -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,
CriteriaForm,
@ -220,12 +221,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()
@ -367,14 +377,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()
@ -466,12 +484,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()
@ -964,12 +991,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()

View File

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

View File

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