From 26200949cc0b7923c28f93849a78a07efc69bb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Thu, 8 Feb 2024 11:49:50 +0100 Subject: [PATCH] manager: display applications (#86636) --- debian/control | 1 + lingo/agendas/models.py | 5 +- lingo/export_import/models.py | 43 +- .../includes/application_appbar_fragment.html | 13 + .../application_breadcrumb_fragment.html | 5 + .../includes/application_detail_fragment.html | 12 + .../includes/application_icon_fragment.html | 8 + .../includes/application_list_fragment.html | 15 + lingo/export_import/views.py | 47 ++ lingo/invoicing/models.py | 5 +- .../lingo/invoicing/manager_payer_detail.html | 3 + .../lingo/invoicing/manager_payer_list.html | 25 +- .../lingo/invoicing/manager_regie_detail.html | 3 + .../lingo/invoicing/manager_regie_list.html | 32 +- lingo/invoicing/views/payer.py | 14 +- lingo/invoicing/views/regie.py | 14 +- lingo/manager/static/css/style.scss | 4 + lingo/pricing/models.py | 5 +- .../lingo/pricing/manager_agenda_detail.html | 3 + .../lingo/pricing/manager_agenda_list.html | 34 +- .../pricing/manager_check_type_list.html | 48 +- .../lingo/pricing/manager_criteria_list.html | 62 +- .../lingo/pricing/manager_pricing_detail.html | 3 + .../lingo/pricing/manager_pricing_list.html | 44 +- lingo/pricing/views.py | 52 +- lingo/settings.py | 5 + setup.py | 1 + tests/data/black.jpeg | Bin 0 -> 558 bytes tests/test_manager_applications.py | 538 ++++++++++++++++++ 29 files changed, 917 insertions(+), 127 deletions(-) create mode 100644 lingo/export_import/templates/lingo/includes/application_appbar_fragment.html create mode 100644 lingo/export_import/templates/lingo/includes/application_breadcrumb_fragment.html create mode 100644 lingo/export_import/templates/lingo/includes/application_detail_fragment.html create mode 100644 lingo/export_import/templates/lingo/includes/application_icon_fragment.html create mode 100644 lingo/export_import/templates/lingo/includes/application_list_fragment.html create mode 100644 lingo/export_import/views.py create mode 100644 tests/data/black.jpeg create mode 100644 tests/test_manager_applications.py diff --git a/debian/control b/debian/control index 1ecff3d..44ec0d5 100644 --- a/debian/control +++ b/debian/control @@ -33,6 +33,7 @@ Depends: python3-django-mellon, python3-hobo, python3-lingo (= ${binary:Version}), python3-psycopg2, + python3-sorl-thumbnail, python3-uwsgidecorators, uwsgi, uwsgi-plugin-python3, diff --git a/lingo/agendas/models.py b/lingo/agendas/models.py index 1660960..92b98a0 100644 --- a/lingo/agendas/models.py +++ b/lingo/agendas/models.py @@ -21,10 +21,11 @@ from django.db import models from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ +from lingo.export_import.models import WithApplicationMixin from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug -class Agenda(models.Model): +class Agenda(WithApplicationMixin, models.Model): label = models.CharField(_('Label'), max_length=150) slug = models.SlugField(_('Identifier'), max_length=160, unique=True) category_label = models.CharField(_('Category label'), max_length=150, null=True) @@ -125,7 +126,7 @@ class Agenda(models.Model): return _('Events') -class CheckTypeGroup(models.Model): +class CheckTypeGroup(WithApplicationMixin, models.Model): slug = models.SlugField(_('Identifier'), max_length=160, unique=True) label = models.CharField(_('Label'), max_length=150) unexpected_presence = models.ForeignKey( diff --git a/lingo/export_import/models.py b/lingo/export_import/models.py index 43bff62..77257ab 100644 --- a/lingo/export_import/models.py +++ b/lingo/export_import/models.py @@ -21,6 +21,14 @@ from django.contrib.contenttypes.models import ContentType from django.db import models +class WithApplicationMixin: + @property + def applications(self): + if getattr(self, '_applications', None) is None: + Application.load_for_object(self) + return self._applications + + class Application(models.Model): name = models.CharField(max_length=100) slug = models.SlugField(max_length=100, unique=True) @@ -71,34 +79,23 @@ class Application(models.Model): @classmethod def populate_objects(cls, object_class, objects): content_type = ContentType.objects.get_for_model(object_class) - elements = ApplicationElement.objects.filter(content_type=content_type) + elements = ApplicationElement.objects.filter( + content_type=content_type, application__visible=True + ).prefetch_related('application') elements_by_objects = collections.defaultdict(list) for element in elements: - elements_by_objects[element.content_object].append(element) - applications_by_ids = { - a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True) - } + elements_by_objects[element.object_id].append(element) for obj in objects: - applications = [] - elements = elements_by_objects.get(obj) or [] - for element in elements: - application = applications_by_ids.get(element.application_id) - if application: - applications.append(application) + applications = [element.application for element in elements_by_objects.get(obj.pk) or []] obj._applications = sorted(applications, key=lambda a: a.name) @classmethod def load_for_object(cls, obj): content_type = ContentType.objects.get_for_model(obj.__class__) - elements = ApplicationElement.objects.filter(content_type=content_type, object_id=obj.pk) - applications_by_ids = { - a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True) - } - applications = [] - for element in elements: - application = applications_by_ids.get(element.application_id) - if application: - applications.append(application) + elements = ApplicationElement.objects.filter( + content_type=content_type, object_id=obj.pk, application__visible=True + ).prefetch_related('application') + applications = [element.application for element in elements] obj._applications = sorted(applications, key=lambda a: a.name) def get_objects_for_object_class(self, object_class): @@ -106,6 +103,12 @@ class Application(models.Model): elements = ApplicationElement.objects.filter(content_type=content_type, application=self) return object_class.objects.filter(pk__in=elements.values('object_id')) + @classmethod + def get_orphan_objects_for_object_class(cls, object_class): + content_type = ContentType.objects.get_for_model(object_class) + elements = ApplicationElement.objects.filter(content_type=content_type, application__visible=True) + return object_class.objects.exclude(pk__in=elements.values('object_id')) + class ApplicationElement(models.Model): application = models.ForeignKey(Application, on_delete=models.CASCADE) diff --git a/lingo/export_import/templates/lingo/includes/application_appbar_fragment.html b/lingo/export_import/templates/lingo/includes/application_appbar_fragment.html new file mode 100644 index 0000000..d8d6989 --- /dev/null +++ b/lingo/export_import/templates/lingo/includes/application_appbar_fragment.html @@ -0,0 +1,13 @@ +{% load thumbnail %} +{% if application %} +

+ {% thumbnail application.icon '64x64' format='PNG' as im %} + + {% endthumbnail %} + {{ application }} +

+{% elif no_application %} +

{{ title_no_application }}

+{% else %} +

{{ title_object_list }}

+{% endif %} diff --git a/lingo/export_import/templates/lingo/includes/application_breadcrumb_fragment.html b/lingo/export_import/templates/lingo/includes/application_breadcrumb_fragment.html new file mode 100644 index 0000000..f7d325f --- /dev/null +++ b/lingo/export_import/templates/lingo/includes/application_breadcrumb_fragment.html @@ -0,0 +1,5 @@ +{% if application %} + {{ application }} +{% elif no_application %} + {{ title_no_application }} +{% endif %} diff --git a/lingo/export_import/templates/lingo/includes/application_detail_fragment.html b/lingo/export_import/templates/lingo/includes/application_detail_fragment.html new file mode 100644 index 0000000..0b13091 --- /dev/null +++ b/lingo/export_import/templates/lingo/includes/application_detail_fragment.html @@ -0,0 +1,12 @@ +{% load i18n thumbnail %} +{% if object.applications %} +

{% trans "Applications" %}

+ {% for application in object.applications %} + + {% thumbnail application.icon '16x16' format='PNG' as im %} + + {% endthumbnail %} + {{ application }} + + {% endfor %} +{% endif %} diff --git a/lingo/export_import/templates/lingo/includes/application_icon_fragment.html b/lingo/export_import/templates/lingo/includes/application_icon_fragment.html new file mode 100644 index 0000000..305415b --- /dev/null +++ b/lingo/export_import/templates/lingo/includes/application_icon_fragment.html @@ -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 %} + + {% endthumbnail %} + {% endfor %} +{% endif %} diff --git a/lingo/export_import/templates/lingo/includes/application_list_fragment.html b/lingo/export_import/templates/lingo/includes/application_list_fragment.html new file mode 100644 index 0000000..16c88e0 --- /dev/null +++ b/lingo/export_import/templates/lingo/includes/application_list_fragment.html @@ -0,0 +1,15 @@ +{% load i18n thumbnail %} +{% if applications %} +

{% trans "Applications" %}

+ {% for application in applications %} + + {% thumbnail application.icon '16x16' format='PNG' as im %} + + {% endthumbnail %} + {{ application }} + + {% endfor %} + + {{ title_no_application }} + +{% endif %} diff --git a/lingo/export_import/views.py b/lingo/export_import/views.py new file mode 100644 index 0000000..c01a58c --- /dev/null +++ b/lingo/export_import/views.py @@ -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 . + +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() diff --git a/lingo/invoicing/models.py b/lingo/invoicing/models.py index f1f3b1d..dda1b09 100644 --- a/lingo/invoicing/models.py +++ b/lingo/invoicing/models.py @@ -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( diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_payer_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_payer_detail.html index f230cda..8163182 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_payer_detail.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_payer_detail.html @@ -93,5 +93,8 @@ {% trans "Edit" %} {% trans 'Export' %} + {% url 'lingo-manager-invoicing-payer-list' as object_list_url %} + {% include 'lingo/includes/application_detail_fragment.html' %} + {% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_payer_list.html b/lingo/invoicing/templates/lingo/invoicing/manager_payer_list.html index d45a1fd..afc88af 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_payer_list.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_payer_list.html @@ -4,12 +4,15 @@ {% block page-title-extra-label %}{% trans "Payers" %}{% endblock %} {% block breadcrumb %} - {{ block.super }} - {% trans "Payers" %} + {% trans "Payments" context 'lingo title' %} + {% trans "Regies" %} + {% url 'lingo-manager-invoicing-payer-list' as object_list_url %} + {% trans "Payers" %} + {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Payers outside applications') %} {% endblock %} {% block appbar %} -

{% trans 'Payers' %}

+ {% 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 %}
  • + {% include 'lingo/includes/application_icon_fragment.html' with object=payer %} {{ payer.label }} [{% trans "identifier:" %} {{ payer.slug }}] @@ -26,8 +30,7 @@ {% endfor %} - {% else %} - + {% elif not no_application %}
    {% blocktrans %} This site doesn't have any payer yet. Click on the "New" button in the top @@ -38,10 +41,14 @@ {% endblock %} {% block sidebar %} - + {% endif %} {% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html index 7cbcf8c..9656793 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_regie_detail.html @@ -54,5 +54,8 @@ {% trans 'Refunds' %} {% trans 'Non invoiced lines' %} + {% url 'lingo-manager-invoicing-regie-list' as object_list_url %} + {% include 'lingo/includes/application_detail_fragment.html' %} + {% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html b/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html index 9b7b7b3..fcbf497 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_regie_list.html @@ -5,11 +5,13 @@ {% block breadcrumb %} {{ block.super }} - {% trans "Regies" %} + {% url 'lingo-manager-invoicing-regie-list' as object_list_url %} + {% trans "Regies" %} + {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Regies outside applications') %} {% endblock %} {% block appbar %} -

    {% trans 'Regies' %}

    + {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Regies outside applications') title_object_list=_('Regies') %} {% endblock %} {% block content %} @@ -19,6 +21,7 @@ {% for regie in object_list %}
  • + {% include 'lingo/includes/application_icon_fragment.html' with object=regie %} {{ regie.label }} [{% trans "identifier:" %} {{ regie.slug }}] @@ -26,8 +29,7 @@ {% endfor %} - {% else %} - + {% elif not no_application %}
    {% blocktrans %} This site doesn't have any regie yet. Click on the "New" button in the top @@ -38,16 +40,20 @@ {% endblock %} {% block sidebar %} - + {% endif %} {% endblock %} diff --git a/lingo/invoicing/views/payer.py b/lingo/invoicing/views/payer.py index 901d870..6b3d118 100644 --- a/lingo/invoicing/views/payer.py +++ b/lingo/invoicing/views/payer.py @@ -21,14 +21,26 @@ from django.http import HttpResponse from django.urls import reverse from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView +from lingo.export_import.views import WithApplicationsMixin from lingo.invoicing.forms import NewPayerForm, PayerForm, PayerMappingForm from lingo.invoicing.models import Payer -class PayersListView(ListView): +class PayersListView(WithApplicationsMixin, ListView): template_name = 'lingo/invoicing/manager_payer_list.html' model = Payer + def dispatch(self, request, *args, **kwargs): + self.with_applications_dispatch(request) + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + return self.with_applications_queryset() + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return self.with_applications_context_data(context) + payers_list = PayersListView.as_view() diff --git a/lingo/invoicing/views/regie.py b/lingo/invoicing/views/regie.py index 54b9b85..cf6b213 100644 --- a/lingo/invoicing/views/regie.py +++ b/lingo/invoicing/views/regie.py @@ -29,6 +29,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView from lingo.agendas.models import Agenda +from lingo.export_import.views import WithApplicationsMixin from lingo.invoicing.forms import ( PaymentTypeForm, RegieCreditFilterSet, @@ -69,10 +70,21 @@ def import_regies(data): return results -class RegiesListView(ListView): +class RegiesListView(WithApplicationsMixin, ListView): template_name = 'lingo/invoicing/manager_regie_list.html' model = Regie + def dispatch(self, request, *args, **kwargs): + self.with_applications_dispatch(request) + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + return self.with_applications_queryset() + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return self.with_applications_context_data(context) + regies_list = RegiesListView.as_view() diff --git a/lingo/manager/static/css/style.scss b/lingo/manager/static/css/style.scss index a1a2d72..ce900cb 100644 --- a/lingo/manager/static/css/style.scss +++ b/lingo/manager/static/css/style.scss @@ -170,3 +170,7 @@ span.invoice-colour { border-radius: 0; vertical-align: middle; } + +.application-logo, .application-icon { + vertical-align: middle; +} diff --git a/lingo/pricing/models.py b/lingo/pricing/models.py index f8e6972..f8b00dd 100644 --- a/lingo/pricing/models.py +++ b/lingo/pricing/models.py @@ -25,6 +25,7 @@ from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from lingo.agendas.models import Agenda, CheckType +from lingo.export_import.models import WithApplicationMixin from lingo.utils.misc import LingoImportError, clean_import_data, generate_slug from lingo.utils.wcs import get_wcs_dependencies_from_template @@ -99,7 +100,7 @@ class PricingBookingCheckTypeError(PricingError): pass -class CriteriaCategory(models.Model): +class CriteriaCategory(WithApplicationMixin, models.Model): label = models.CharField(_('Label'), max_length=150) slug = models.SlugField(_('Identifier'), max_length=160, unique=True) @@ -261,7 +262,7 @@ class PricingMatrix: rows: list[PricingMatrixRow] -class Pricing(models.Model): +class Pricing(WithApplicationMixin, models.Model): label = models.CharField(_('Label'), max_length=150, null=True) slug = models.SlugField(_('Identifier'), max_length=160, null=True) diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html index 28fe63e..f582bfe 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html +++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html @@ -116,5 +116,8 @@ {% trans "Agenda options" %} {% endif %}{% endwith %} + {% url 'lingo-manager-agenda-list' as object_list_url %} + {% include 'lingo/includes/application_detail_fragment.html' %} + {% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html index d3845bf..178e25f 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html +++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_list.html @@ -1,15 +1,18 @@ {% extends "lingo/pricing/manager_pricing_list.html" %} -{% load i18n %} +{% load i18n thumbnail %} {% block page-title-extra-label %}{% trans "Agendas" %} | {{ block.super }}{% endblock %} {% block breadcrumb %} - {{ block.super }} - {% trans 'Agendas' %} + {% trans "Payments" context 'lingo title' %} + {% trans "Pricings" %} + {% url 'lingo-manager-agenda-list' as object_list_url %} + {% trans "Agendas" %} + {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Agendas outside applications') %} {% endblock %} {% block appbar %} -

    {% trans 'Agendas' %}

    + {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Agendas outside applications') title_object_list=_('Agendas') %} {% endblock %} {% block content %} @@ -21,14 +24,17 @@
    {% endfor %} - {% else %} + {% elif not no_application %}
    {% blocktrans trimmed %} This site doesn't have any agenda yet. Click on the "Refresh agendas" button in the top @@ -39,10 +45,14 @@ {% endblock %} {% block sidebar %} - + {% endif %} {% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html b/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html index 7661a69..205bb11 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html +++ b/lingo/pricing/templates/lingo/pricing/manager_check_type_list.html @@ -4,22 +4,30 @@ {% block page-title-extra-label %}{% trans "Check types" %} | {{ block.super }}{% endblock %} {% block breadcrumb %} - {{ block.super }} - {% trans "Check types" %} + {% trans "Payments" context 'lingo title' %} + {% trans "Pricings" %} + {% url 'lingo-manager-check-type-list' as object_list_url %} + {% trans "Check types" %} + {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Check types outside applications') %} {% endblock %} {% block appbar %} -

    {% trans 'Check types' %}

    + {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Check types outside applications') title_object_list=_('Check types') %} {% endblock %} {% block content %} -
    -

    {% trans "Define here check types used in events agendas to check bookings." %}

    -
    + {% if not application and not no_application %} +
    +

    {% trans "Define here check types used in events agendas to check bookings." %}

    +
    + {% endif %} {% for object in object_list %}
    {% empty %} -
    - {% blocktrans trimmed %} - This site doesn't have any check type group yet. Click on the "New group" button in the top - right of the page to add a first one. - {% endblocktrans %} -
    + {% if not no_application %} +
    + {% blocktrans trimmed %} + This site doesn't have any check type group yet. Click on the "New group" button in the top + right of the page to add a first one. + {% endblocktrans %} +
    + {% endif %} {% endfor %} {% endblock %} {% block sidebar %} - + {% endif %} {% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html b/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html index ea590d7..9344895 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html +++ b/lingo/pricing/templates/lingo/pricing/manager_criteria_list.html @@ -4,30 +4,40 @@ {% block page-title-extra-label %}{% trans "Criterias" %} | {{ block.super }}{% endblock %} {% block breadcrumb %} - {{ block.super }} - {% trans "Criterias" %} + {% trans "Payments" context 'lingo title' %} + {% trans "Pricings" %} + {% url 'lingo-manager-pricing-criteria-list' as object_list_url %} + {% trans "Criterias" %} + {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Criterias outside applications') %} {% endblock %} {% block appbar %} -

    {% trans 'Criterias' %}

    + {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Criterias outside applications') title_object_list=_('Criterias') %} {% endblock %} {% block content %} -
    -

    {% trans "Define here pricing criterias used in pricings." %}

    -
    + {% if not application and not no_application %} +
    +

    {% trans "Define here pricing criterias used in pricings." %}

    +
    + {% endif %} {% if object_list %} -

    - {% blocktrans trimmed %} - Use drag and drop with the ⣿ handles to reorder criterias inside a category. - {% endblocktrans %} -

    + {% if not application and not no_application %} +

    + {% blocktrans trimmed %} + Use drag and drop with the ⣿ handles to reorder criterias inside a category. + {% endblocktrans %} +

    + {% endif %} {% endif %} {% for object in object_list %}

    - {{ object }} [{{ object.slug }}] + + {% include 'lingo/includes/application_icon_fragment.html' %} + {{ object }} [{{ object.slug }}] + {% trans "Export"%} {% trans "Delete"%} @@ -37,7 +47,7 @@

    {% empty %} -
    - {% blocktrans trimmed %} - This site doesn't have any pricing category yet. Click on the "New category" button in the top - right of the page to add a first one. - {% endblocktrans %} -
    + {% if not no_application %} +
    + {% blocktrans trimmed %} + This site doesn't have any pricing category yet. Click on the "New category" button in the top + right of the page to add a first one. + {% endblocktrans %} +
    + {% endif %} {% endfor %} {% endblock %} {% block sidebar %} - + {% endif %} {% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html b/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html index 3ca9882..bc31cfe 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html +++ b/lingo/pricing/templates/lingo/pricing/manager_pricing_detail.html @@ -283,5 +283,8 @@ {% trans 'Options' %} {% trans 'Export' %} + {% url 'lingo-manager-pricing-list' as object_list_url %} + {% include 'lingo/includes/application_detail_fragment.html' %} + {% endblock %} diff --git a/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html b/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html index 2157561..08040af 100644 --- a/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html +++ b/lingo/pricing/templates/lingo/pricing/manager_pricing_list.html @@ -1,21 +1,25 @@ {% extends "lingo/manager_homepage.html" %} {% load i18n %} -{% block page-title-extra-label %}{% trans "Pricings" context 'agenda pricing' %}{% endblock %} +{% block page-title-extra-label %}{% trans "Pricings" %}{% endblock %} {% block breadcrumb %} {{ block.super }} - {% trans "Pricings" context 'agenda pricing' %} + {% url 'lingo-manager-pricing-list' as object_list_url %} + {% trans "Pricings" %} + {% include 'lingo/includes/application_breadcrumb_fragment.html' with title_no_application=_('Pricings outside applications') %} {% endblock %} {% block appbar %} -

    {% trans 'Pricings' context 'agenda pricing' %}

    + {% include 'lingo/includes/application_appbar_fragment.html' with title_no_application=_('Pricings outside applications') title_object_list=_('Pricings') %} {% endblock %} {% block content %} -
    -

    {% trans "Define here pricings to attach to events agendas." %}

    -
    + {% if not application and not no_application %} +
    +

    {% trans "Define here pricings to attach to events agendas." %}

    +
    + {% endif %} {% if object_list %}

    {% trans "Pricings" context 'agenda pricing' %}

    @@ -23,6 +27,7 @@ {% for object in object_list %}{% if not object.flat_fee_schedule %}
  • + {% 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 %}) [{% trans "identifier:" %} {{ object.slug }}] @@ -35,6 +40,7 @@ {% for object in object_list %}{% if object.flat_fee_schedule %}
  • + {% 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 %}) [{% trans "identifier:" %} {{ object.slug }}] @@ -43,7 +49,7 @@ {% endif %}{% endfor %} - {% else %} + {% elif not no_application %}
    {% blocktrans trimmed %} This site doesn't have any pricing yet. Click on the "New pricing" button in the top @@ -54,17 +60,21 @@ {% endblock %} {% block sidebar %} - + {% endif %} {% endblock %} diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py index b92cfe0..d75e623 100644 --- a/lingo/pricing/views.py +++ b/lingo/pricing/views.py @@ -41,6 +41,7 @@ from django.views.generic.detail import SingleObjectMixin from lingo.agendas.chrono import refresh_agendas from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup from lingo.agendas.views import AgendaMixin +from lingo.export_import.views import WithApplicationsMixin from lingo.pricing.forms import ( CheckTypeForm, CheckTypeGroupUnexpectedPresenceForm, @@ -221,12 +222,21 @@ class ConfigImportView(FormView): config_import = ConfigImportView.as_view() -class CriteriaListView(ListView): +class CriteriaListView(WithApplicationsMixin, ListView): template_name = 'lingo/pricing/manager_criteria_list.html' model = CriteriaCategory + def dispatch(self, request, *args, **kwargs): + self.with_applications_dispatch(request) + return super().dispatch(request, *args, **kwargs) + def get_queryset(self): - return CriteriaCategory.objects.prefetch_related('criterias') + queryset = self.with_applications_queryset() + return queryset.prefetch_related('criterias') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return self.with_applications_context_data(context) criteria_list = CriteriaListView.as_view() @@ -368,14 +378,22 @@ class CriteriaDeleteView(DeleteView): criteria_delete = CriteriaDeleteView.as_view() -class AgendaListView(ListView): +class AgendaListView(WithApplicationsMixin, ListView): template_name = 'lingo/pricing/manager_agenda_list.html' model = Agenda + def dispatch(self, request, *args, **kwargs): + self.with_applications_dispatch(request) + return super().dispatch(request, *args, **kwargs) + def get_queryset(self): - queryset = super().get_queryset() + queryset = self.with_applications_queryset() return queryset.order_by('category_label', 'label') + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return self.with_applications_context_data(context) + agenda_list = AgendaListView.as_view() @@ -467,12 +485,21 @@ class AgendaInvoicingSettingsView(AgendaMixin, UpdateView): agenda_invoicing_settings = AgendaInvoicingSettingsView.as_view() -class PricingListView(ListView): +class PricingListView(WithApplicationsMixin, ListView): template_name = 'lingo/pricing/manager_pricing_list.html' model = Pricing + def dispatch(self, request, *args, **kwargs): + self.with_applications_dispatch(request) + return super().dispatch(request, *args, **kwargs) + def get_queryset(self): - return Pricing.objects.all().order_by('flat_fee_schedule', 'date_start', 'date_end') + queryset = self.with_applications_queryset() + return queryset.order_by('flat_fee_schedule', 'date_start', 'date_end') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return self.with_applications_context_data(context) pricing_list = PricingListView.as_view() @@ -965,12 +992,21 @@ class PricingMatrixEdit(FormView): pricing_matrix_edit = PricingMatrixEdit.as_view() -class CheckTypeListView(ListView): +class CheckTypeListView(WithApplicationsMixin, ListView): template_name = 'lingo/pricing/manager_check_type_list.html' model = CheckTypeGroup + def dispatch(self, request, *args, **kwargs): + self.with_applications_dispatch(request) + return super().dispatch(request, *args, **kwargs) + def get_queryset(self): - return CheckTypeGroup.objects.prefetch_related('check_types') + queryset = self.with_applications_queryset() + return queryset.prefetch_related('check_types') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return self.with_applications_context_data(context) check_type_list = CheckTypeListView.as_view() diff --git a/lingo/settings.py b/lingo/settings.py index 93dd7c5..5d830ea 100644 --- a/lingo/settings.py +++ b/lingo/settings.py @@ -57,6 +57,7 @@ INSTALLED_APPS = ( 'gadjo', 'rest_framework', 'django_filters', + 'sorl.thumbnail', 'lingo.agendas', 'lingo.api', 'lingo.basket', @@ -231,6 +232,10 @@ CKEDITOR_CONFIGS = { BASKET_EXPIRY_DELAY = 60 # 1 hour by default +# from solr.thumbnail -- https://sorl-thumbnail.readthedocs.io/en/latest/reference/settings.html +THUMBNAIL_PRESERVE_FORMAT = True +THUMBNAIL_FORCE_OVERWRITE = False + local_settings_file = os.environ.get( 'LINGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py') ) diff --git a/setup.py b/setup.py index 4ef9275..426bb5f 100644 --- a/setup.py +++ b/setup.py @@ -168,6 +168,7 @@ setup( 'djangorestframework>=3.3, <3.15', 'django-filter', 'weasyprint', + 'sorl-thumbnail', ], zip_safe=False, cmdclass={ diff --git a/tests/data/black.jpeg b/tests/data/black.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..6309526dc712b2a555f6956e610832233d0a8593 GIT binary patch literal 558 zcmb7;O-chn5QSfLPbZz}o=JDo9kYz!BDWYJ5FJ6{6$Asim4NpUTzePs5dLgDiERx- z0{$eyS8r8(Rqv&j^Z|D^uje2lobn)jVmP~9Uapo`le?SM_2i^Jzew+xK?>2Bm9dpp zD$7*f?RK=ytu?tZMLBRq=}TkW$oXN2G1~sYcofEi5JU5XB;RDJr&KQ#rU?I5dV<%O zVk*f)c#@vdGa~oLcWGXSUt6R%SV<60I)ENFH~j{8Kc5^kuK5Wmv1kDQHz0{B+69OW lur;DnwGmqZsty