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-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)
unexpected_presence = models.ForeignKey(

View File

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

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>
@ -49,20 +57,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,
CheckTypeGroupUnexpectedPresenceForm,
@ -221,12 +222,21 @@ class ConfigImportView(FormView):
config_import = ConfigImportView.as_view()
class CriteriaListView(ListView):
class CriteriaListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_criteria_list.html'
model = CriteriaCategory
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return CriteriaCategory.objects.prefetch_related('criterias')
queryset = self.with_applications_queryset()
return queryset.prefetch_related('criterias')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return self.with_applications_context_data(context)
criteria_list = CriteriaListView.as_view()
@ -368,14 +378,22 @@ class CriteriaDeleteView(DeleteView):
criteria_delete = CriteriaDeleteView.as_view()
class AgendaListView(ListView):
class AgendaListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_agenda_list.html'
model = Agenda
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
queryset = super().get_queryset()
queryset = self.with_applications_queryset()
return queryset.order_by('category_label', 'label')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return self.with_applications_context_data(context)
agenda_list = AgendaListView.as_view()
@ -467,12 +485,21 @@ class AgendaInvoicingSettingsView(AgendaMixin, UpdateView):
agenda_invoicing_settings = AgendaInvoicingSettingsView.as_view()
class PricingListView(ListView):
class PricingListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_pricing_list.html'
model = Pricing
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return Pricing.objects.all().order_by('flat_fee_schedule', 'date_start', 'date_end')
queryset = self.with_applications_queryset()
return queryset.order_by('flat_fee_schedule', 'date_start', 'date_end')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return self.with_applications_context_data(context)
pricing_list = PricingListView.as_view()
@ -965,12 +992,21 @@ class PricingMatrixEdit(FormView):
pricing_matrix_edit = PricingMatrixEdit.as_view()
class CheckTypeListView(ListView):
class CheckTypeListView(WithApplicationsMixin, ListView):
template_name = 'lingo/pricing/manager_check_type_list.html'
model = CheckTypeGroup
def dispatch(self, request, *args, **kwargs):
self.with_applications_dispatch(request)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return CheckTypeGroup.objects.prefetch_related('check_types')
queryset = self.with_applications_queryset()
return queryset.prefetch_related('check_types')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return self.with_applications_context_data(context)
check_type_list = CheckTypeListView.as_view()

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