From 75fe3e33df954bc3f7c3a3e8118aa420260c7c7c Mon Sep 17 00:00:00 2001 From: George Katsikas <giorgakis.katsikas@gmail.com> Date: Tue, 11 Mar 2025 14:50:13 +0100 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E2=9C=A8=20add=20private=20subsid?= =?UTF-8?q?y=20payment=20api=20viewset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scipost_django/api/urls.py | 7 +- scipost_django/finances/api/filtersets.py | 82 ++++++++++++++++++- scipost_django/finances/api/serializers.py | 17 +++- scipost_django/finances/api/viewsets.py | 59 ++++++++++++- .../commands/add_groups_and_permissions.py | 10 +++ 5 files changed, 169 insertions(+), 6 deletions(-) diff --git a/scipost_django/api/urls.py b/scipost_django/api/urls.py index d0e0922b4..90e304b02 100644 --- a/scipost_django/api/urls.py +++ b/scipost_django/api/urls.py @@ -31,7 +31,11 @@ from organizations.api.viewsets import ( ) # finances -from finances.api.viewsets import SubsidyFinAdminAPIViewSet, SubsidyPublicAPIViewSet +from finances.api.viewsets import ( + SubsidyFinAdminAPIViewSet, + SubsidyPublicAPIViewSet, + SubsidyPaymentPrivateAPIViewSet, +) # Next two: old style, to be deprecated: @@ -83,6 +87,7 @@ router.register("nap", OrganizationNAPViewSet, basename="organization_nap") router.register( "finadmin/subsidies", SubsidyFinAdminAPIViewSet, basename="subsidies_finadmin" ) +router.register("subsidies/payments", SubsidyPaymentPrivateAPIViewSet) router.register("subsidies", SubsidyPublicAPIViewSet) # Next two: old style, to be deprecated: diff --git a/scipost_django/finances/api/filtersets.py b/scipost_django/finances/api/filtersets.py index 3bb68b2ba..02e830a91 100644 --- a/scipost_django/finances/api/filtersets.py +++ b/scipost_django/finances/api/filtersets.py @@ -4,7 +4,7 @@ __license__ = "AGPL v3" from django_filters import rest_framework as df_filters -from finances.models import Subsidy +from finances.models import Subsidy, SubsidyPayment class SubsidyFinAdminAPIFilterSet(df_filters.FilterSet): @@ -55,3 +55,83 @@ class SubsidyFinAdminAPIFilterSet(df_filters.FilterSet): "range", ], } + + +class SubsidyPaymentAPIFilterSet(df_filters.FilterSet): + class Meta: + model = SubsidyPayment + fields = { + "subsidy__organization__name": [ + "icontains", + "contains", + "istartswith", + "iregex", + "regex", + ], + "subsidy__organization__acronym": [ + "icontains", + "contains", + "istartswith", + "iregex", + "regex", + ], + "subsidy__organization__country": [ + "icontains", + ], + "subsidy__description": [ + "icontains", + ], + "subsidy__amount": ["gte", "lte", "range"], + "subsidy__date_from": [ + "year", + "month", + "exact", + "year__gte", + "year__lte", + "year__range", + "gte", + "lte", + "range", + ], + "subsidy__date_until": [ + "year", + "exact", + "year__gte", + "year__lte", + "year__range", + "gte", + "lte", + "range", + ], + "invoice__date": [ + "year", + "exact", + "year__gte", + "year__lte", + "year__range", + "gte", + "lte", + "range", + ], + "proof_of_payment__date": [ + "year", + "exact", + "year__gte", + "year__lte", + "year__range", + "gte", + "lte", + "range", + ], + "date_scheduled": [ + "year", + "exact", + "year__gte", + "year__lte", + "year__range", + "gte", + "lte", + "range", + ], + "amount": ["gte", "lte", "range"], + } diff --git a/scipost_django/finances/api/serializers.py b/scipost_django/finances/api/serializers.py index 9d6f5e8eb..58e1fe2c4 100644 --- a/scipost_django/finances/api/serializers.py +++ b/scipost_django/finances/api/serializers.py @@ -6,7 +6,7 @@ from rest_framework import serializers from api.serializers import DynamicFieldsModelSerializer -from finances.models import Subsidy +from finances.models import Subsidy, SubsidyPayment from organizations.api.serializers import OrganizationPublicSerializer @@ -32,3 +32,18 @@ class SubsidyFinAdminSerializer(DynamicFieldsModelSerializer): "renewable", "renewal_of", ] + + +class SubsidyPaymentSerializer(DynamicFieldsModelSerializer): + subsidy = SubsidyFinAdminSerializer() + + class Meta: + model = SubsidyPayment + fields = [ + "subsidy", + "amount", + "date_scheduled", + "status", + "invoice_date", + "payment_date", + ] diff --git a/scipost_django/finances/api/viewsets.py b/scipost_django/finances/api/viewsets.py index d0be52cab..694c4be27 100644 --- a/scipost_django/finances/api/viewsets.py +++ b/scipost_django/finances/api/viewsets.py @@ -9,9 +9,12 @@ from rest_framework_csv import renderers as r from api.viewsets.base import ExtraFilteredReadOnlyModelViewSet from api.viewsets.mixins import FilteringOptionsActionMixin -from finances.models import Subsidy -from finances.api.filtersets import SubsidyFinAdminAPIFilterSet -from finances.api.serializers import SubsidyFinAdminSerializer +from finances.models import Subsidy, SubsidyPayment +from finances.api.filtersets import ( + SubsidyFinAdminAPIFilterSet, + SubsidyPaymentAPIFilterSet, +) +from finances.api.serializers import SubsidyFinAdminSerializer, SubsidyPaymentSerializer class CanManageSubsidies(BasePermission): @@ -19,6 +22,11 @@ class CanManageSubsidies(BasePermission): return request.user.has_perm("scipost.can_manage_subsidies") +class CanUseSubsidyPaymentAPI(BasePermission): + def has_permission(self, request, view): + return request.user.has_perm("scipost.can_use_private_api_subsidy_payments") + + class SubsidyFinAdminAPIViewSet( FilteringOptionsActionMixin, ExtraFilteredReadOnlyModelViewSet ): @@ -44,9 +52,54 @@ class SubsidyFinAdminAPIViewSet( "organization__acronym__icontains", ] + def get_queryset(self): + return ( + super() + .get_queryset() + .select_related("organization") + .prefetch_related("renewal_of") + ) + class SubsidyPublicAPIViewSet(SubsidyFinAdminAPIViewSet): queryset = Subsidy.objects.filter(amount_publicly_shown=True) permission_classes = [ AllowAny, ] + + +class SubsidyPaymentPrivateAPIViewSet( + FilteringOptionsActionMixin, ExtraFilteredReadOnlyModelViewSet +): + queryset = SubsidyPayment.objects.all() + permission_classes = [ + CanUseSubsidyPaymentAPI, + ] + serializer_class = SubsidyPaymentSerializer + renderer_classes = tuple(api_settings.DEFAULT_RENDERER_CLASSES) + (r.CSVRenderer,) + search_fields = [ + "subsidy__organization__name", + "subsidy__organization__acronym", + ] + filterset_class = SubsidyPaymentAPIFilterSet + ordering_fields = [ + "subsidy__organization__name", + "subsidy__amount", + "amount", + "date_scheduled", + "proof_of_payment__date", + "invoice__date", + ] + + def get_queryset(self): + return ( + super() + .get_queryset() + .select_related( + "subsidy", + "subsidy__organization", + "proof_of_payment", + "invoice", + ) + .prefetch_related("subsidy__renewal_of") + ) diff --git a/scipost_django/scipost/management/commands/add_groups_and_permissions.py b/scipost_django/scipost/management/commands/add_groups_and_permissions.py index 262979120..8edb5a1c7 100644 --- a/scipost_django/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost_django/scipost/management/commands/add_groups_and_permissions.py @@ -560,6 +560,16 @@ class Command(BaseCommand): content_type=content_type, ) + # API private access + # These are likely to be granted on a per-user basis + can_use_private_api_subsidy_payments, created = ( + Permission.objects.get_or_create( + codename="can_use_private_api_subsidy_payments", + name="Can use the private API for subsidy payments", + content_type=content_type, + ) + ) + # Assign permissions to groups SciPostAdmin.permissions.set( [ -- GitLab