prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  PaymentDetailView in admin API allows deleting payments unauthorized
  https://github.com/svthalia/concrexit/blob/4ab37961f50e398cc52422cdc1df66f6ab8ff2ee/website/payments/api/v2/admin/views.py#L69
  
  ### Describe the bug
  Payments sometimes should be undeletable. For example, TPay payments that are in a batch. The PaymentAdmin prevents such deletions. However, the rest framework DestroyAPIView does not respect that.
  
  ### How to reproduce
  Steps to reproduce the behaviour:
  1. Have a payment 
  2. Add it to a batch
  3. Process the batch
  4. Do the API `DELETE` request at `/api/v2/admin/payments/<id>`
  
  ### Expected behaviour
  Either disable payment deletion at all from the API, or manually implement a check that the payment is not in a processed batch.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `website/payments/api/v2/admin/views.py`
  Content:
  ```
  1 import rest_framework.filters as framework_filters
  2 from django.apps import apps
  3 from django.http import Http404
  4 from django.utils.translation import gettext_lazy as _
  5 from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
  6 from rest_framework import status, serializers
  7 from rest_framework.exceptions import PermissionDenied, ValidationError
  8 from rest_framework.generics import get_object_or_404
  9 from rest_framework.permissions import IsAdminUser
  10 from rest_framework.response import Response
  11 from rest_framework.settings import api_settings
  12 from rest_framework.views import APIView
  13 
  14 from payments import services, payables, NotRegistered
  15 from payments.api.v2 import filters
  16 from payments.api.v2.admin.serializers.payable_create import (
  17     PayableCreateAdminSerializer,
  18 )
  19 from payments.api.v2.admin.serializers.payable_detail import PayableAdminSerializer
  20 from payments.api.v2.admin.serializers.payment import (
  21     PaymentAdminSerializer,
  22     PaymentCreateSerializer,
  23 )
  24 from payments.exceptions import PaymentError
  25 from payments.models import Payment, PaymentUser
  26 from thaliawebsite.api.v2.admin import (
  27     AdminListAPIView,
  28     AdminCreateAPIView,
  29     AdminRetrieveAPIView,
  30     AdminDestroyAPIView,
  31 )
  32 
  33 
  34 class PaymentListCreateView(AdminListAPIView, AdminCreateAPIView):
  35     """View that allows you to create and list payments as admin."""
  36 
  37     queryset = Payment.objects.prefetch_related(
  38         "paid_by__profile",
  39         "paid_by__membership_set",
  40         "processed_by__profile",
  41         "processed_by__membership_set",
  42     )
  43 
  44     required_scopes = ["payments:admin"]
  45     filter_backends = (
  46         framework_filters.OrderingFilter,
  47         filters.CreatedAtFilter,
  48         filters.PaymentTypeFilter,
  49     )
  50     ordering_fields = ("created_at",)
  51 
  52     def get_serializer_class(self):
  53         if self.request.method.lower() == "post":
  54             return PaymentCreateSerializer
  55         return PaymentAdminSerializer
  56 
  57     def create(self, request, *args, **kwargs):
  58         serializer = self.get_serializer(data=request.data)
  59         serializer.is_valid(raise_exception=True)
  60         self.perform_create(serializer)
  61         return Response(
  62             PaymentAdminSerializer(
  63                 serializer.instance, context=self.get_serializer_context()
  64             ).data,
  65             status=status.HTTP_201_CREATED,
  66         )
  67 
  68 
  69 class PaymentDetailView(AdminRetrieveAPIView, AdminDestroyAPIView):
  70     """View that allows you to manage a single payment as admin."""
  71 
  72     queryset = Payment.objects.all()
  73     serializer_class = PaymentAdminSerializer
  74     permission_classes = [IsAuthenticatedOrTokenHasScope]
  75     required_scopes = ["payments:admin"]
  76 
  77 
  78 class PayableDetailView(APIView):
  79     """View that allows you to manipulate the payment for the payable.
  80 
  81     Permissions of this view are based on the payable.
  82     """
  83 
  84     required_scopes = ["payments:admin"]
  85     permission_classes = [IsAuthenticatedOrTokenHasScope, IsAdminUser]
  86 
  87     def get_serializer_context(self):
  88         return {"request": self.request, "format": self.format_kwarg, "view": self}
  89 
  90     def get_payable(self):
  91         app_label = self.kwargs["app_label"]
  92         model_name = self.kwargs["model_name"]
  93         payable_pk = self.kwargs["payable_pk"]
  94 
  95         try:
  96             payable_model = apps.get_model(app_label=app_label, model_name=model_name)
  97             payable = payables.get_payable(
  98                 get_object_or_404(payable_model, pk=payable_pk)
  99             )
  100         except (LookupError, NotRegistered) as e:
  101             raise serializers.ValidationError(
  102                 {api_settings.NON_FIELD_ERRORS_KEY: [_("Payable model not found")]}
  103             ) from e
  104 
  105         if not payable.can_manage_payment(self.request.member):
  106             raise PermissionDenied(
  107                 detail=_("You do not have permission to perform this action.")
  108             )
  109 
  110         return payable
  111 
  112     def get(self, request, *args, **kwargs):
  113         """Get information about a payable."""
  114         serializer = PayableAdminSerializer(
  115             self.get_payable(), context=self.get_serializer_context()
  116         )
  117         return Response(serializer.data, status=status.HTTP_200_OK)
  118 
  119     def delete(self, request, *args, **kwargs):
  120         """Remove the current payment for a payable."""
  121         payable = self.get_payable()
  122 
  123         if not payable.model.payment:
  124             raise Http404
  125 
  126         try:
  127             services.delete_payment(
  128                 payable.model, request.member,
  129             )
  130             payable.model.save()
  131         except PaymentError as e:
  132             raise PermissionDenied(detail=str(e))
  133 
  134         return Response(status=status.HTTP_204_NO_CONTENT)
  135 
  136     def patch(self, request, *args, **kwargs):
  137         """Mark the payable as paid by creating a payment for it."""
  138         serializer = PayableCreateAdminSerializer(
  139             data=request.data, context=self.get_serializer_context()
  140         )
  141         serializer.is_valid(raise_exception=True)
  142 
  143         payable = self.get_payable()
  144 
  145         try:
  146             services.create_payment(
  147                 payable,
  148                 PaymentUser.objects.get(pk=request.user.pk),
  149                 serializer.data["payment_type"],
  150             )
  151             payable.model.save()
  152         except PaymentError as e:
  153             raise ValidationError(detail={api_settings.NON_FIELD_ERRORS_KEY: [str(e)]})
  154 
  155         return Response(
  156             PayableAdminSerializer(payable, context=self.get_serializer_context()).data,
  157             status=status.HTTP_201_CREATED,
  158         )
  159
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/website/payments/api/v2/admin/views.py b/website/payments/api/v2/admin/views.py
  --- a/website/payments/api/v2/admin/views.py
  +++ b/website/payments/api/v2/admin/views.py
  @@ -74,6 +74,11 @@
       permission_classes = [IsAuthenticatedOrTokenHasScope]
       required_scopes = ["payments:admin"]
   
  +    def delete(self, request, *args, **kwargs):
  +        if self.get_object().batch and self.get_object().batch.processed:
  +            raise PermissionDenied("This payment cannot be deleted.")
  +        return super().delete(request, *args, **kwargs)
  +
   
   class PayableDetailView(APIView):
       """View that allows you to manipulate the payment for the payable.
  
verification_info: {"golden_diff": "diff --git a/website/payments/api/v2/admin/views.py b/website/payments/api/v2/admin/views.py\n--- a/website/payments/api/v2/admin/views.py\n+++ b/website/payments/api/v2/admin/views.py\n@@ -74,6 +74,11 @@\n     permission_classes = [IsAuthenticatedOrTokenHasScope]\n     required_scopes = [\"payments:admin\"]\n \n+    def delete(self, request, *args, **kwargs):\n+        if self.get_object().batch and self.get_object().batch.processed:\n+            raise PermissionDenied(\"This payment cannot be deleted.\")\n+        return super().delete(request, *args, **kwargs)\n+\n \n class PayableDetailView(APIView):\n     \"\"\"View that allows you to manipulate the payment for the payable.\n", "issue": "PaymentDetailView in admin API allows deleting payments unauthorized\nhttps://github.com/svthalia/concrexit/blob/4ab37961f50e398cc52422cdc1df66f6ab8ff2ee/website/payments/api/v2/admin/views.py#L69\r\n\r\n### Describe the bug\r\nPayments sometimes should be undeletable. For example, TPay payments that are in a batch. The PaymentAdmin prevents such deletions. However, the rest framework DestroyAPIView does not respect that.\r\n\r\n### How to reproduce\r\nSteps to reproduce the behaviour:\r\n1. Have a payment \r\n2. Add it to a batch\r\n3. Process the batch\r\n4. Do the API `DELETE` request at `/api/v2/admin/payments/<id>`\r\n\r\n### Expected behaviour\r\nEither disable payment deletion at all from the API, or manually implement a check that the payment is not in a processed batch.\r\n\n", "before_files": [{"content": "import rest_framework.filters as framework_filters\nfrom django.apps import apps\nfrom django.http import Http404\nfrom django.utils.translation import gettext_lazy as _\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import status, serializers\nfrom rest_framework.exceptions import PermissionDenied, ValidationError\nfrom rest_framework.generics import get_object_or_404\nfrom rest_framework.permissions import IsAdminUser\nfrom rest_framework.response import Response\nfrom rest_framework.settings import api_settings\nfrom rest_framework.views import APIView\n\nfrom payments import services, payables, NotRegistered\nfrom payments.api.v2 import filters\nfrom payments.api.v2.admin.serializers.payable_create import (\n    PayableCreateAdminSerializer,\n)\nfrom payments.api.v2.admin.serializers.payable_detail import PayableAdminSerializer\nfrom payments.api.v2.admin.serializers.payment import (\n    PaymentAdminSerializer,\n    PaymentCreateSerializer,\n)\nfrom payments.exceptions import PaymentError\nfrom payments.models import Payment, PaymentUser\nfrom thaliawebsite.api.v2.admin import (\n    AdminListAPIView,\n    AdminCreateAPIView,\n    AdminRetrieveAPIView,\n    AdminDestroyAPIView,\n)\n\n\nclass PaymentListCreateView(AdminListAPIView, AdminCreateAPIView):\n    \"\"\"View that allows you to create and list payments as admin.\"\"\"\n\n    queryset = Payment.objects.prefetch_related(\n        \"paid_by__profile\",\n        \"paid_by__membership_set\",\n        \"processed_by__profile\",\n        \"processed_by__membership_set\",\n    )\n\n    required_scopes = [\"payments:admin\"]\n    filter_backends = (\n        framework_filters.OrderingFilter,\n        filters.CreatedAtFilter,\n        filters.PaymentTypeFilter,\n    )\n    ordering_fields = (\"created_at\",)\n\n    def get_serializer_class(self):\n        if self.request.method.lower() == \"post\":\n            return PaymentCreateSerializer\n        return PaymentAdminSerializer\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        self.perform_create(serializer)\n        return Response(\n            PaymentAdminSerializer(\n                serializer.instance, context=self.get_serializer_context()\n            ).data,\n            status=status.HTTP_201_CREATED,\n        )\n\n\nclass PaymentDetailView(AdminRetrieveAPIView, AdminDestroyAPIView):\n    \"\"\"View that allows you to manage a single payment as admin.\"\"\"\n\n    queryset = Payment.objects.all()\n    serializer_class = PaymentAdminSerializer\n    permission_classes = [IsAuthenticatedOrTokenHasScope]\n    required_scopes = [\"payments:admin\"]\n\n\nclass PayableDetailView(APIView):\n    \"\"\"View that allows you to manipulate the payment for the payable.\n\n    Permissions of this view are based on the payable.\n    \"\"\"\n\n    required_scopes = [\"payments:admin\"]\n    permission_classes = [IsAuthenticatedOrTokenHasScope, IsAdminUser]\n\n    def get_serializer_context(self):\n        return {\"request\": self.request, \"format\": self.format_kwarg, \"view\": self}\n\n    def get_payable(self):\n        app_label = self.kwargs[\"app_label\"]\n        model_name = self.kwargs[\"model_name\"]\n        payable_pk = self.kwargs[\"payable_pk\"]\n\n        try:\n            payable_model = apps.get_model(app_label=app_label, model_name=model_name)\n            payable = payables.get_payable(\n                get_object_or_404(payable_model, pk=payable_pk)\n            )\n        except (LookupError, NotRegistered) as e:\n            raise serializers.ValidationError(\n                {api_settings.NON_FIELD_ERRORS_KEY: [_(\"Payable model not found\")]}\n            ) from e\n\n        if not payable.can_manage_payment(self.request.member):\n            raise PermissionDenied(\n                detail=_(\"You do not have permission to perform this action.\")\n            )\n\n        return payable\n\n    def get(self, request, *args, **kwargs):\n        \"\"\"Get information about a payable.\"\"\"\n        serializer = PayableAdminSerializer(\n            self.get_payable(), context=self.get_serializer_context()\n        )\n        return Response(serializer.data, status=status.HTTP_200_OK)\n\n    def delete(self, request, *args, **kwargs):\n        \"\"\"Remove the current payment for a payable.\"\"\"\n        payable = self.get_payable()\n\n        if not payable.model.payment:\n            raise Http404\n\n        try:\n            services.delete_payment(\n                payable.model, request.member,\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise PermissionDenied(detail=str(e))\n\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    def patch(self, request, *args, **kwargs):\n        \"\"\"Mark the payable as paid by creating a payment for it.\"\"\"\n        serializer = PayableCreateAdminSerializer(\n            data=request.data, context=self.get_serializer_context()\n        )\n        serializer.is_valid(raise_exception=True)\n\n        payable = self.get_payable()\n\n        try:\n            services.create_payment(\n                payable,\n                PaymentUser.objects.get(pk=request.user.pk),\n                serializer.data[\"payment_type\"],\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise ValidationError(detail={api_settings.NON_FIELD_ERRORS_KEY: [str(e)]})\n\n        return Response(\n            PayableAdminSerializer(payable, context=self.get_serializer_context()).data,\n            status=status.HTTP_201_CREATED,\n        )\n", "path": "website/payments/api/v2/admin/views.py"}], "after_files": [{"content": "import rest_framework.filters as framework_filters\nfrom django.apps import apps\nfrom django.http import Http404\nfrom django.utils.translation import gettext_lazy as _\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import status, serializers\nfrom rest_framework.exceptions import PermissionDenied, ValidationError\nfrom rest_framework.generics import get_object_or_404\nfrom rest_framework.permissions import IsAdminUser\nfrom rest_framework.response import Response\nfrom rest_framework.settings import api_settings\nfrom rest_framework.views import APIView\n\nfrom payments import services, payables, NotRegistered\nfrom payments.api.v2 import filters\nfrom payments.api.v2.admin.serializers.payable_create import (\n    PayableCreateAdminSerializer,\n)\nfrom payments.api.v2.admin.serializers.payable_detail import PayableAdminSerializer\nfrom payments.api.v2.admin.serializers.payment import (\n    PaymentAdminSerializer,\n    PaymentCreateSerializer,\n)\nfrom payments.exceptions import PaymentError\nfrom payments.models import Payment, PaymentUser\nfrom thaliawebsite.api.v2.admin import (\n    AdminListAPIView,\n    AdminCreateAPIView,\n    AdminRetrieveAPIView,\n    AdminDestroyAPIView,\n)\n\n\nclass PaymentListCreateView(AdminListAPIView, AdminCreateAPIView):\n    \"\"\"View that allows you to create and list payments as admin.\"\"\"\n\n    queryset = Payment.objects.prefetch_related(\n        \"paid_by__profile\",\n        \"paid_by__membership_set\",\n        \"processed_by__profile\",\n        \"processed_by__membership_set\",\n    )\n\n    required_scopes = [\"payments:admin\"]\n    filter_backends = (\n        framework_filters.OrderingFilter,\n        filters.CreatedAtFilter,\n        filters.PaymentTypeFilter,\n    )\n    ordering_fields = (\"created_at\",)\n\n    def get_serializer_class(self):\n        if self.request.method.lower() == \"post\":\n            return PaymentCreateSerializer\n        return PaymentAdminSerializer\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        self.perform_create(serializer)\n        return Response(\n            PaymentAdminSerializer(\n                serializer.instance, context=self.get_serializer_context()\n            ).data,\n            status=status.HTTP_201_CREATED,\n        )\n\n\nclass PaymentDetailView(AdminRetrieveAPIView, AdminDestroyAPIView):\n    \"\"\"View that allows you to manage a single payment as admin.\"\"\"\n\n    queryset = Payment.objects.all()\n    serializer_class = PaymentAdminSerializer\n    permission_classes = [IsAuthenticatedOrTokenHasScope]\n    required_scopes = [\"payments:admin\"]\n\n    def delete(self, request, *args, **kwargs):\n        if self.get_object().batch and self.get_object().batch.processed:\n            raise PermissionDenied(\"This payment cannot be deleted.\")\n        return super().delete(request, *args, **kwargs)\n\n\nclass PayableDetailView(APIView):\n    \"\"\"View that allows you to manipulate the payment for the payable.\n\n    Permissions of this view are based on the payable.\n    \"\"\"\n\n    required_scopes = [\"payments:admin\"]\n    permission_classes = [IsAuthenticatedOrTokenHasScope, IsAdminUser]\n\n    def get_serializer_context(self):\n        return {\"request\": self.request, \"format\": self.format_kwarg, \"view\": self}\n\n    def get_payable(self):\n        app_label = self.kwargs[\"app_label\"]\n        model_name = self.kwargs[\"model_name\"]\n        payable_pk = self.kwargs[\"payable_pk\"]\n\n        try:\n            payable_model = apps.get_model(app_label=app_label, model_name=model_name)\n            payable = payables.get_payable(\n                get_object_or_404(payable_model, pk=payable_pk)\n            )\n        except (LookupError, NotRegistered) as e:\n            raise serializers.ValidationError(\n                {api_settings.NON_FIELD_ERRORS_KEY: [_(\"Payable model not found\")]}\n            ) from e\n\n        if not payable.can_manage_payment(self.request.member):\n            raise PermissionDenied(\n                detail=_(\"You do not have permission to perform this action.\")\n            )\n\n        return payable\n\n    def get(self, request, *args, **kwargs):\n        \"\"\"Get information about a payable.\"\"\"\n        serializer = PayableAdminSerializer(\n            self.get_payable(), context=self.get_serializer_context()\n        )\n        return Response(serializer.data, status=status.HTTP_200_OK)\n\n    def delete(self, request, *args, **kwargs):\n        \"\"\"Remove the current payment for a payable.\"\"\"\n        payable = self.get_payable()\n\n        if not payable.model.payment:\n            raise Http404\n\n        try:\n            services.delete_payment(\n                payable.model, request.member,\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise PermissionDenied(detail=str(e))\n\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    def patch(self, request, *args, **kwargs):\n        \"\"\"Mark the payable as paid by creating a payment for it.\"\"\"\n        serializer = PayableCreateAdminSerializer(\n            data=request.data, context=self.get_serializer_context()\n        )\n        serializer.is_valid(raise_exception=True)\n\n        payable = self.get_payable()\n\n        try:\n            services.create_payment(\n                payable,\n                PaymentUser.objects.get(pk=request.user.pk),\n                serializer.data[\"payment_type\"],\n            )\n            payable.model.save()\n        except PaymentError as e:\n            raise ValidationError(detail={api_settings.NON_FIELD_ERRORS_KEY: [str(e)]})\n\n        return Response(\n            PayableAdminSerializer(payable, context=self.get_serializer_context()).data,\n            status=status.HTTP_201_CREATED,\n        )\n", "path": "website/payments/api/v2/admin/views.py"}]}