internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: svthalia/concrexit

Issue:
Delete tpay payment if order is modified
### Summary
Right now it is possible to order a pizza, pay it with tpay, change the order to a pizza with a different price, and the payment will not match the order anymore.

### How to test
1. Order a pizza
2. Pay with tpay
3. Change the order
4. The payment should be deleted
5. If the event is over, or the payment is batched, then changing the order should crash


Discussion:
Thank you for opening a pull request.
* A coverage report will be available on [thalia-coverage.s3.amazonaws.com/fix-pizza-order-modify-tpay/index.html](https://thalia-coverage.s3.amazonaws.com/fix-pizza-order-modify-tpay/index.html) after the "Deploy Coverage Report" job finishes.
* Branch specific documentation will be available on [thalia-documentation.s3.amazonaws.com/fix-pizza-order-modify-tpay/index.html](https://thalia-documentation.s3.amazonaws.com/fix-pizza-order-modify-tpay/index.html) after the "Deploy Documentation" job finishes.
* Once the "Build Docker Image" job finishes you can comment "**deploy review**" to this pull request to setup a review environment. When you are done, you can remove the review environment by commenting "**remove review**".
Code Climate has analyzed commit 6405b36b and detected **0 issues** on this pull request.

View more on [Code Climate](https://codeclimate.com/github/svthalia/concrexit/pull/1763).


Resolving patch:
diff --git a/website/pizzas/views.py b/website/pizzas/views.py
--- a/website/pizzas/views.py
+++ b/website/pizzas/views.py
@@ -67,6 +67,6 @@ def place_order(request):
             obj = FoodOrder(food_event=event, member=request.member)
         obj.product = product
         if obj.payment:
-            delete_payment(obj.payment)
+            delete_payment(obj)
         obj.save()
     return redirect("pizzas:index")


Test patch:
Repository: svthalia/concrexit

Issue:
Shift product sales are not calculated correctly
### Describe the bug
For some reason, the product sales for shifts in the sales app are not calculated properly:

### How to reproduce
1. Check staging.thalia.nu, shift 1
2. 
<img width="453" alt="image" src="https://user-images.githubusercontent.com/7915741/123234193-06af2500-d4db-11eb-99b9-a8be74602c1a.png">
3. It should be waaaaay more as there are 200+ orders

### Expected behaviour
Correct calculation



Discussion:

Resolving patch:
diff --git a/website/sales/models/shift.py b/website/sales/models/shift.py
--- a/website/sales/models/shift.py
+++ b/website/sales/models/shift.py
@@ -151,8 +151,8 @@ def product_sales(self):
         qs = (
             self.orders.exclude(order_items__isnull=True)
             .values("order_items__product")
-            .distinct()
             .annotate(sold=Sum("order_items__amount"))
+            .order_by()
         )
         return {
             item[0]: item[1]


Test patch:
diff --git a/website/sales/tests/test_models.py b/website/sales/tests/test_models.py
--- a/website/sales/tests/test_models.py
+++ b/website/sales/tests/test_models.py
@@ -453,6 +453,17 @@ def test_shift_statistics(self):
         o5.payment = create_payment(o5, processed_by=self.member, pay_type=Payment.CASH)
         o5.save()
 
+        # The test was accidentally passing because orders had the same created at,
+        # resulting in the distinct seeing them as the same order
+        o2.created_at += timezone.timedelta(minutes=1)
+        o3.created_at += timezone.timedelta(minutes=2)
+        o4.created_at += timezone.timedelta(minutes=3)
+        o5.created_at += timezone.timedelta(minutes=4)
+        o2.save()
+        o3.save()
+        o4.save()
+        o5.save()
+
         self.assertEqual(self.shift.total_revenue, 6)
         self.assertEqual(self.shift.total_revenue_paid, 3)
Repository: svthalia/concrexit

Issue:
Still provide payment url for orders if order is free but age restricted
### Is your feature request related to a problem? Please describe.
Free orders don't need payment, so the API doesn't provide a payment url for free orders. However, age restricted orders still need member identification. For that, the API should still provide the URL that sets the payer.

### Describe the solution you'd like
Provide the payment url if the order is free but age restricted. In such cases, The sales order payment view should display a succes message 'you correctly identified yourself' or something like that.

### Motivation
Scanning a QR code for identification is nice

### Describe alternatives you've considered
We could also provide an identification url, which could make more sense as the url isn't exactly used for payments, but that would also make our API unnecessarily more complex as we can just re-use the existing field and view for it.



Discussion:

Resolving patch:
diff --git a/website/sales/models/order.py b/website/sales/models/order.py
--- a/website/sales/models/order.py
+++ b/website/sales/models/order.py
@@ -162,7 +162,6 @@ def payment_url(self):
         return (
             settings.BASE_URL + reverse("sales:order-pay", kwargs={"pk": self.pk})
             if not self.payment
-            and (self.total_amount is not None and self.total_amount != 0)
             else None
         )
 
diff --git a/website/sales/views.py b/website/sales/views.py
--- a/website/sales/views.py
+++ b/website/sales/views.py
@@ -23,10 +23,6 @@ def get(self, request, *args, **kwargs):
         order.payer = request.member
         order.save()
 
-        if order.total_amount == 0:
-            messages.warning(request, _("This order doesn't require payment."))
-            return redirect("index")
-
         if order.age_restricted and not services.is_adult(request.member):
             messages.error(
                 request,
@@ -35,4 +31,19 @@ def get(self, request, *args, **kwargs):
                 ),
             )
             return redirect("index")
+
+        if (
+            order.age_restricted
+            and services.is_adult(request.member)
+            and order.total_amount == 0
+        ):
+            messages.success(
+                request, _("You have successfully identified yourself for this order.")
+            )
+            return redirect("index")
+
+        if order.total_amount == 0:
+            messages.info(request, _("This order doesn't require payment."))
+            return redirect("index")
+
         return render(request, "sales/order_payment.html", {"order": order})


Test patch:
Repository: svthalia/concrexit

Issue:
Change sales API scope to `sales:admin` instead of `read` and `write`
### Describe the change
Change sales API scope to `sales:admin` instead of `read` and `write`

### Motivation
Follow the same convention as other apps

### Current implementation
`sales:read` and `sales:write` scopes are used

### Suggested implementation
Use a single `sales:admin` scope



Discussion:

Resolving patch:
diff --git a/website/sales/api/v2/admin/views.py b/website/sales/api/v2/admin/views.py
--- a/website/sales/api/v2/admin/views.py
+++ b/website/sales/api/v2/admin/views.py
@@ -30,7 +30,7 @@ class ShiftListView(AdminListAPIView):
     )
     ordering_fields = ("start", "end")
     permission_classes = [IsAuthenticatedOrTokenHasScope, DjangoModelPermissions]
-    required_scopes = ["sales:read"]
+    required_scopes = ["sales:admin"]
 
     def get_queryset(self):
         queryset = super().get_queryset().filter(locked=False)
@@ -62,7 +62,7 @@ class ShiftDetailView(AdminRetrieveAPIView):
         DjangoModelPermissions,
         IsManager,
     ]
-    required_scopes = ["sales:read"]
+    required_scopes = ["sales:admin"]
 
 
 class OrderListView(AdminListAPIView, AdminCreateAPIView):
@@ -75,10 +75,7 @@ class OrderListView(AdminListAPIView, AdminCreateAPIView):
         DjangoModelPermissions,
         IsManager,
     ]
-    required_scopes_per_method = {
-        "GET": ["sales:read"],
-        "POST": ["sales:write"],
-    }
+    required_scopes = ["sales:admin"]
     shift_lookup_field = "pk"
 
     def get_serializer_class(self):
@@ -130,12 +127,7 @@ class OrderDetailView(AdminRetrieveAPIView, AdminUpdateAPIView, AdminDestroyAPIV
         DjangoModelPermissions,
         IsManager,
     ]
-    required_scopes_per_method = {
-        "GET": ["sales:read"],
-        "PATCH": ["sales:write"],
-        "PUT": ["sales:write"],
-        "DELETE": ["sales:write"],
-    }
+    required_scopes = ["sales:admin"]
 
     def get_queryset(self):
         queryset = super().get_queryset()
diff --git a/website/thaliawebsite/settings.py b/website/thaliawebsite/settings.py
--- a/website/thaliawebsite/settings.py
+++ b/website/thaliawebsite/settings.py
@@ -532,8 +532,9 @@ def from_env(
         "payments:read": "Read access to payments",
         "payments:write": "Write access to payments",
         "payments:admin": "Admin access to payments",
-        "sales:read": "Read access to Point of Sale orders",
-        "sales:write": "Write access to Point of Sale orders",
+        "sales:read": "Read access to your Point of Sale orders",
+        "sales:order": "Place Point of Sale orders on your behalf",
+        "sales:admin": "Admin access to Point of Sale orders",
     },
 }
 


Test patch:
Repository: svthalia/concrexit

Issue:
Food order can be changed after paying
### Describe the bug
If you order a pizza and pay it, you can still change the product. If you change the product through the api, the payment is not removed.

### How to reproduce
Steps to reproduce the behaviour:
1. Order a pizza
2. Pay with Thalia Pay
3. Change the order through the api
4. Get an expensive pizza for little money

### Expected behaviour
Either changing the order after paying is impossible, or it removes the payment. I think removing the payment (as the website currently seems to do) would be strange, and for event registration we've decided not to enable this.

### Screenshots
<img width="569" alt="image" src="https://user-images.githubusercontent.com/41264528/123456318-01d59880-d5e3-11eb-86c8-9217e4720988.png">

There are probably no food events any time soon, so a hotfix may not be needed, though it might be good to double-check that similar stuff is not possible with registrations.



Discussion:
Did you try this on master? Because exactly this bug was fixed this tuesday when releasing
Nvm, I think it was only fixed in the web view not in the api
>Did you try this on master? Because exactly this bug was fixed this tuesday when releasing

On staging, so that should be master

Resolving patch:
diff --git a/website/pizzas/api/v2/views.py b/website/pizzas/api/v2/views.py
--- a/website/pizzas/api/v2/views.py
+++ b/website/pizzas/api/v2/views.py
@@ -12,6 +12,8 @@
 from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly
 from rest_framework.response import Response
 
+from payments.exceptions import PaymentError
+from payments.services import delete_payment
 from pizzas.api.v2 import filters
 from pizzas.api.v2.serializers import (
     ProductSerializer,
@@ -110,6 +112,18 @@ def dispatch(self, request, *args, **kwargs):
     def update(self, request, *args, **kwargs):
         super().update(request, *args, **kwargs)
         instance = self.get_object()
+
+        if instance.payment:
+            try:
+                delete_payment(
+                    instance, member=request.member, ignore_change_window=True
+                )
+            except PaymentError:
+                return Response(
+                    "Your order could not be updated because it was already paid.",
+                    status=status.HTTP_403_FORBIDDEN,
+                )
+
         return Response(
             FoodOrderSerializer(instance, context=self.get_serializer_context()).data
         )


Test patch:
Repository: svthalia/concrexit

Issue:
Display warning in admin for age restricted orders for under age members
https://github.com/svthalia/concrexit/blob/8244e4bd50db6e64a63aa1605756acc2fb413094/website/sales/admin/order_admin.py#L334


Discussion:

Resolving patch:
diff --git a/website/sales/admin/order_admin.py b/website/sales/admin/order_admin.py
--- a/website/sales/admin/order_admin.py
+++ b/website/sales/admin/order_admin.py
@@ -11,6 +11,7 @@
 from django.utils.translation import gettext_lazy as _
 
 from payments.widgets import PaymentWidget
+from sales import services
 from sales.models.order import Order, OrderItem
 from sales.models.shift import Shift
 from sales.services import is_manager
@@ -221,6 +222,19 @@ def get_readonly_fields(self, request: HttpRequest, obj: Order = None):
 
         return default_fields
 
+    def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
+        if object_id:
+            obj = self.model.objects.get(pk=object_id)
+            if obj.age_restricted and obj.payer and not services.is_adult(obj.payer):
+                self.message_user(
+                    request,
+                    _(
+                        "The payer for this order is under-age while the order is age restricted!"
+                    ),
+                    messages.WARNING,
+                )
+        return super().changeform_view(request, object_id, form_url, extra_context)
+
     def get_queryset(self, request):
         queryset = super().get_queryset(request)
 


Test patch:
Repository: svthalia/concrexit

Issue:
AttributeError: 'Event' object has no attribute 'title_en'
Sentry Issue: [CONCREXIT-70](https://sentry.io/organizations/thalia/issues/2487433496/?referrer=github_integration)

```
AttributeError: 'Event' object has no attribute 'title_en'
(9 additional frame(s) were not displayed)
...
  File "django/contrib/admin/options.py", line 1540, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "django/contrib/admin/options.py", line 1586, in _changeform_view
    self.save_model(request, new_object, form, not add)
  File "photos/admin.py", line 45, in save_model
    super().save_model(request, obj, form, change)
  File "django/contrib/admin/options.py", line 1099, in save_model
    obj.save()
  File "photos/models.py", line 158, in save
    self.title = self.event.title_en
```


Discussion:

Resolving patch:
diff --git a/website/photos/models.py b/website/photos/models.py
--- a/website/photos/models.py
+++ b/website/photos/models.py
@@ -155,7 +155,7 @@ def save(self, **kwargs):
             self.dirname = self.slug
 
         if not self.title and self.event:
-            self.title = self.event.title_en
+            self.title = self.event.title
 
         if not self.date:
             self.date = self.event.start.date()


Test patch:
Repository: svthalia/concrexit

Issue:
Add payment_type or full payment to event admin API
### Motivation
`api/v2/admin/events/<eventPk>/registrations/` currently only gives the uuid of a payment, so to display in the admin screen how it was paid, the payment must be requested separately. Doing this for all of the registrations would be very inefficient (like 40 extra requests to load the event admin). If we simply add the payment_type or replace the payment uuid with a payment serializer, it will be much simpler.



Discussion:
Where are all the other sections from the template?

Resolving patch:
diff --git a/website/events/api/v2/admin/serializers/event_registration.py b/website/events/api/v2/admin/serializers/event_registration.py
--- a/website/events/api/v2/admin/serializers/event_registration.py
+++ b/website/events/api/v2/admin/serializers/event_registration.py
@@ -3,6 +3,7 @@
 from events.models import EventRegistration
 from members.api.v2.serializers.member import MemberSerializer
 from members.models import Member
+from payments.api.v2.serializers import PaymentSerializer
 
 
 class EventRegistrationAdminSerializer(serializers.ModelSerializer):
@@ -22,6 +23,8 @@ class Meta:
         )
         read_only_fields = ("payment",)
 
+    payment = PaymentSerializer()
+
     def to_internal_value(self, data):
         self.fields["member"] = serializers.PrimaryKeyRelatedField(
             queryset=Member.objects.all()
diff --git a/website/events/api/v2/serializers/event_registration.py b/website/events/api/v2/serializers/event_registration.py
--- a/website/events/api/v2/serializers/event_registration.py
+++ b/website/events/api/v2/serializers/event_registration.py
@@ -2,6 +2,7 @@
 
 from events.models import EventRegistration
 from members.api.v2.serializers.member import MemberSerializer
+from payments.api.v2.serializers import PaymentSerializer
 
 
 class EventRegistrationSerializer(serializers.ModelSerializer):
@@ -31,4 +32,5 @@ class Meta:
             "name",
         )
 
+    payment = PaymentSerializer()
     member = MemberSerializer(detailed=False, read_only=True)


Test patch:
Repository: svthalia/concrexit

Issue:
Add search parameter to event registrations admin api
### Is your feature request related to a problem? Please describe.
I'm always frustrated when I can't search for a registration in the event admin.

### Describe the solution you'd like
A search parameter (by member.name or name) on `api/v2/admin/events/<id>/registrations/`.

### Motivation
Then we can search for registrations. A parameter is desirable for consistency in making pagination available.

### Describe alternatives you've considered
Local search.



Discussion:

Resolving patch:
diff --git a/website/events/api/v2/admin/views.py b/website/events/api/v2/admin/views.py
--- a/website/events/api/v2/admin/views.py
+++ b/website/events/api/v2/admin/views.py
@@ -72,9 +72,15 @@ class EventRegistrationAdminListView(AdminListAPIView, AdminCreateAPIView):
     required_scopes = ["events:admin"]
     filter_backends = (
         framework_filters.OrderingFilter,
+        framework_filters.SearchFilter,
         filters.EventRegistrationCancelledFilter,
     )
     ordering_fields = ("queue_position", "date", "date_cancelled")
+    search_fields = (
+        "member__first_name",
+        "member__last_name",
+        "name",
+    )
 
     def get_queryset(self):
         event = get_object_or_404(Event, pk=self.kwargs.get("pk"))


Test patch:
Repository: svthalia/concrexit

Issue:
Sales API uses TokenHasScopeForMethod but no scopes per method are provided
### Describe the change
The sales API uses some IsAuthenticatedOrTokenHasScopeForMethod permission classes, but no scopes per method are provided

### Motivation
Simpler code

### Current implementation
See above

### Suggested implementation
Use IsAuthenticatedOrTokenHasScope, or specify scope per method 




Discussion:

Resolving patch:
diff --git a/website/sales/api/v2/admin/views.py b/website/sales/api/v2/admin/views.py
--- a/website/sales/api/v2/admin/views.py
+++ b/website/sales/api/v2/admin/views.py
@@ -16,7 +16,6 @@
     AdminUpdateAPIView,
     AdminDestroyAPIView,
 )
-from thaliawebsite.api.v2.permissions import IsAuthenticatedOrTokenHasScopeForMethod
 
 
 class ShiftListView(AdminListAPIView):
@@ -71,7 +70,7 @@ class OrderListView(AdminListAPIView, AdminCreateAPIView):
         ("POST",): OrderSerializer,
     }
     permission_classes = [
-        IsAuthenticatedOrTokenHasScopeForMethod,
+        IsAuthenticatedOrTokenHasScope,
         DjangoModelPermissions,
         IsManager,
     ]
@@ -123,7 +122,7 @@ class OrderDetailView(AdminRetrieveAPIView, AdminUpdateAPIView, AdminDestroyAPIV
     serializer_class = OrderSerializer
     queryset = Order.objects.all()
     permission_classes = [
-        IsAuthenticatedOrTokenHasScopeForMethod,
+        IsAuthenticatedOrTokenHasScope,
         DjangoModelPermissions,
         IsManager,
     ]


Test patch:
Repository: svthalia/concrexit

Issue:
DeviceListView permission not checked
### Describe the bug
The `DeviceListView` of `api/v2` has a `IsAuthenticatedOwnerOrReadOnly` permission which is never checked as `get_object` is not used in the view.

### How to reproduce
Steps to reproduce the behaviour:
1. Set a breakpoint in the `IsAuthenticatedOwnerOrReadOnly` class
2. Enable the debugger
3. See that the `has_object_permission` method is not called on a request to the corresponding endpoint



Discussion:

Resolving patch:
diff --git a/website/pushnotifications/api/v2/views.py b/website/pushnotifications/api/v2/views.py
--- a/website/pushnotifications/api/v2/views.py
+++ b/website/pushnotifications/api/v2/views.py
@@ -22,10 +22,7 @@
 class DeviceListView(ListAPIView, CreateAPIView):
     """Returns an overview of all devices that are owner by the user."""
 
-    permission_classes = [
-        IsAuthenticatedOrTokenHasScopeForMethod,
-        IsAuthenticatedOwnerOrReadOnly,
-    ]
+    permission_classes = [IsAuthenticatedOrTokenHasScopeForMethod]
     serializer_class = DeviceSerializer
     queryset = Device.objects.all()
     required_scopes_per_method = {


Test patch:
Repository: svthalia/concrexit

Issue:
Event (registration) status message in the API
### Is your feature request related to a problem? Please describe.
Currently, the event status messages (like 'you cannot cancel your registration without having to pay a fine') are hardcoded and whenever we update them, we must also update the app

### Describe the solution you'd like
Put the message in the API

### Additional context
Also checkout #1381 


Discussion:
We still need this for API v2.

Resolving patch:
diff --git a/website/events/api/v2/serializers/event.py b/website/events/api/v2/serializers/event.py
--- a/website/events/api/v2/serializers/event.py
+++ b/website/events/api/v2/serializers/event.py
@@ -32,6 +32,7 @@ class Meta:
             "num_participants",
             "max_participants",
             "no_registration_message",
+            "cancel_too_late_message",
             "has_fields",
             "food_event",
             "maps_url",


Test patch: