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

Issue:
It is possible to delete payments from orders in locked shifts
### Describe the bug
It is possible to delete payments from orders in locked shifts

### How to reproduce
1. Have a sales shift
2. Make an order
3. Pay it
4. Lock the shift
5. Delete the order again
6. The shift contains unpaid orders now, although it is locked
 
### Expected behaviour
Deleting payments should be PROTECTed.

### Screenshots
<img width="630" alt="image" src="https://user-images.githubusercontent.com/7915741/133924492-423e9292-9be7-4daf-9482-24d9b5f8ea6a.png">

### Additional context
Actually, you would only want to PROTECT deleting payments if the shift is locked. Otherwise, you want to set the payment for an order to NULL... But I think this is impossible to implement in Django...  


Discussion:
https://github.com/svthalia/concrexit/blob/f1cf6819c67cdef916ea9a69e2d12353893fd8f1/website/sales/models/order.py#L70

Resolving patch:
diff --git a/website/sales/migrations/0003_alter_order_payment.py b/website/sales/migrations/0003_alter_order_payment.py
new file mode 100644
--- /dev/null
+++ b/website/sales/migrations/0003_alter_order_payment.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.0.2 on 2022-02-24 13:02
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('payments', '0019_alter_payment_paid_by'),
+        ('sales', '0002_auto_20211216_2300'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='order',
+            name='payment',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sales_order', to='payments.payment', verbose_name='payment'),
+        ),
+    ]
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
@@ -68,7 +68,7 @@ class Meta:
         Payment,
         verbose_name=_("payment"),
         related_name="sales_order",
-        on_delete=models.SET_NULL,
+        on_delete=models.PROTECT,
         blank=True,
         null=True,
     )


Test patch:
Repository: svthalia/concrexit

Issue:
True personal Agenda ICal Feed
### Problem
I believe it is annoying that when exporting the Personal iCal Feed to the agenda, you get the events your registered for + the open events + the events with optional registration. In practice this is very annoying as you don't want all these open events you might not go to in your agenda.

### Solution
That is why I suggest:
- Creating a 3rd button "iCal feed (personal)", which exports an iCal feed only containing the events you actually registered for.
- Renaming the current "iCal feed personal" to "iCal feed personal + open events"

### Motivation
A better user experience 

### Describe alternatives you've considered
If this is not possible, I would consider adding an "add to agenda" button to the  event pages so you can add events to your agenda individually.



Discussion:
When I look at this issue I see a valid improvement in UX. However, I do not think that adding a third option will improve UX. It would just add more options for us to support and explain. I think we can do better than that :)

Therefore I suggest the simple solution of just removing all events without mandatory registrations from the calendar feed.

Why? Because we added support for non-mandatory registrations. Take [this event](https://thalia.nu/events/892/) for example. The description already says "mark the event as 'registered' in your calendar". Sounds to me that we only need to change the 'personal' in the calendar to 'personal registrations' and we have the solution. If you're interested you will mark as registered and it will show up. In the case you're not interested it will no longer show up in your calendar :)

This would essentially end up being like adding individual events to your calendar. Aka the alternative.

(oh and this would make the issue 'easy and fun' imo, since it only requires the removal of [one line of code](https://github.com/svthalia/concrexit/blob/master/website/events/feeds.py#L42))

What do you think?

Resolving patch:
diff --git a/website/events/feeds.py b/website/events/feeds.py
--- a/website/events/feeds.py
+++ b/website/events/feeds.py
@@ -39,9 +39,8 @@ def items(self):
         query = Q(published=True)
 
         if self.user:
-            query &= Q(registration_start__isnull=True) | (
-                Q(eventregistration__member=self.user)
-                & Q(eventregistration__date_cancelled=None)
+            query &= Q(eventregistration__member=self.user) & Q(
+                eventregistration__date_cancelled=None
             )
 
         return Event.objects.filter(query).order_by("-start")


Test patch:
Repository: svthalia/concrexit

Issue:
Do not escape HTML for 'no registration message'
### Is your feature request related to a problem? Please describe.
When custom registration applies, there often is a link to a different ticket vendor. I want that link to be clickabe

### Describe the solution you'd like
Do not escape HTML in the `message if no registration is required`

### Motivation
More user-friendly 

### Describe alternatives you've considered
Have a specific URL field for "other ticket sale URL" and include that

### Additional context
<img width="1549" alt="image" src="https://user-images.githubusercontent.com/7915741/161990340-0d4a7603-ee92-48c2-a833-44ff36de9635.png">



Discussion:

Resolving patch:
diff --git a/website/events/migrations/0055_alter_event_no_registration_message.py b/website/events/migrations/0055_alter_event_no_registration_message.py
new file mode 100644
--- /dev/null
+++ b/website/events/migrations/0055_alter_event_no_registration_message.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0.3 on 2022-04-15 11:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('events', '0054_externalevent'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='event',
+            name='no_registration_message',
+            field=models.CharField(blank=True, help_text='Default: No registration required. This field accepts HTML tags as well, e.g. links with &lta href="https://example.com" target="_blank"&gthttps://example.com&lt/a&gt', max_length=200, null=True, verbose_name='message when there is no registration'),
+        ),
+    ]
diff --git a/website/events/models/event.py b/website/events/models/event.py
--- a/website/events/models/event.py
+++ b/website/events/models/event.py
@@ -151,7 +151,14 @@ class Event(models.Model):
         blank=True,
         null=True,
         help_text=(
-            format_lazy("{} {}", _("Default:"), DEFAULT_NO_REGISTRATION_MESSAGE)
+            format_lazy(
+                "{} {}. {}",
+                _("Default:"),
+                DEFAULT_NO_REGISTRATION_MESSAGE,
+                _(
+                    'This field accepts HTML tags as well, e.g. links with &lta href="https://example.com" target="_blank"&gthttps://example.com&lt/a&gt'
+                ),
+            )
         ),
     )
 


Test patch:
Repository: svthalia/concrexit

Issue:
Can't set registration information fields from admin (for non-member registrations)
### Describe the bug
It is not possible to manually set the event registration information fields from the admin. This is quite problematic when adding non-member's registrations

### How to reproduce
Steps to reproduce the behaviour:
1. add a manual event registration
2. try to edit the information fields (this works)
3. you cannot save it

### Expected behaviour
It should save




Discussion:

Resolving patch:
diff --git a/website/events/admin/views.py b/website/events/admin/views.py
--- a/website/events/admin/views.py
+++ b/website/events/admin/views.py
@@ -97,7 +97,9 @@ def form_valid(self, form):
         values = form.field_values()
         try:
             services.update_registration(
-                registration=self.registration, field_values=values
+                registration=self.registration,
+                field_values=values,
+                actor=self.request.user,
             )
             messages.success(self.request, _("Registration successfully saved."))
             if "_save" in self.request.POST:


Test patch:
Repository: svthalia/concrexit

Issue:
Order.DoesNotExist: Order matching query does not exist.
This error should be caught (I think it happens in very specific cases when orders are being deleted while you're loading the admin)


Sentry Issue: [CONCREXIT-86](https://sentry.io/organizations/thalia/issues/2542195680/?referrer=github_integration)

```
Order.DoesNotExist: Order matching query does not exist.
(6 additional frame(s) were not displayed)
...
  File "sales/admin/order_admin.py", line 342, in change_view
    return super().change_view(request, object_id, form_url, extra_context)
  File "django/contrib/admin/options.py", line 1660, in change_view
    return self.changeform_view(request, object_id, form_url, extra_context)
  File "sales/admin/order_admin.py", line 227, in changeform_view
    obj = self.model.objects.get(pk=object_id)
  File "django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "django/db/models/query.py", line 435, in get
    raise self.model.DoesNotExist(
```


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
@@ -347,7 +347,23 @@ def changelist_view(self, request, extra_context=None):
         return super().changelist_view(request, extra_context)
 
     def change_view(self, request, object_id, form_url="", extra_context=None):
-        object_id
+        if object_id:
+            try:
+                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,
+                    )
+            except self.model.DoesNotExist:
+                pass
         return super().change_view(request, object_id, form_url, extra_context)
 
     def order_description(self, obj):


Test patch:
Repository: svthalia/concrexit

Issue:
payment method totals in shift
### Is your feature request related to a problem? Please describe.
For bookkeeping we(CAD) would like to see totals of payments per payment method in ThaliaPay

### Describe the solution you'd like
An overview of how much was paid per payment method during a shift

### Motivation
For more efficient bookkeeping of the treasurer and CAD



Discussion:

Resolving patch:
diff --git a/website/sales/admin/shift_admin.py b/website/sales/admin/shift_admin.py
--- a/website/sales/admin/shift_admin.py
+++ b/website/sales/admin/shift_admin.py
@@ -3,6 +3,7 @@
 
 from django.utils.translation import gettext_lazy as _
 
+from payments.models import Payment
 from sales.models.order import Order
 from sales.models.shift import Shift, SelfOrderPeriod
 from sales.services import is_manager
@@ -129,6 +130,7 @@ class ShiftAdmin(admin.ModelAdmin):
         "product_list",
         "managers",
         "product_sales",
+        "payment_method_sales",
         "num_orders",
         "total_revenue",
         "locked",
@@ -139,6 +141,7 @@ class ShiftAdmin(admin.ModelAdmin):
         "total_revenue",
         "num_orders",
         "product_sales",
+        "payment_method_sales",
     )
 
     def get_readonly_fields(self, request, obj=None):
@@ -215,3 +218,10 @@ def product_sales(self, obj):
         if obj.num_orders != obj.num_orders_paid:
             return f"{output}\n{_('This includes some orders that are unpaid.')}"
         return output
+
+    def payment_method_sales(self, obj):
+        output = "\n".join(
+            f"- {dict(Payment.PAYMENT_TYPE)[k] if k else _('Unpaid')}: €{v:.2f}"
+            for k, v in obj.payment_method_sales.items()
+        )
+        return output
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
@@ -163,6 +163,15 @@ def product_sales(self):
             for item in qs.values_list("order_items__product__product__name", "sold")
         }
 
+    @property
+    def payment_method_sales(self):
+        qs = (
+            self.orders.values("payment__type")
+            .annotate(sold=Sum("order_items__total"))
+            .order_by()
+        )
+        return {item[0]: item[1] for item in qs.values_list("payment__type", "sold")}
+
     @property
     def user_orders_allowed(self):
         return self.selforderperiod_set.filter(


Test patch:
Repository: svthalia/concrexit

Issue:
Add global uniqueness constraint on push notification tokens
### Describe the bug
Currently, in a `Device`, the combination of `user` and `registration_id` (a Firebase Messaging token) is unique. This means that it's possible for one token to be used by multiple users. This makes it possible for one device to receive notifications intended for multiple users. For example.

Alice has registered a device with token `a`. Bob also registers a device with token `a` (although he doesn't have a device that receives from token `a`). Alice now receives push notifications that are sent only to Bob. 

It's quite unlikely that this would ever happen (Bob would need to read Alice's token from the admin and then use it in a device), and even then it would still only mean that Alice receives wrong notifications, but I think this should just be made impossible in the code as it is not logical behaviour.

https://github.com/svthalia/concrexit/blob/3dceb39fe0e0d251c0e8a81393d40d1ad6b564e2/website/pushnotifications/models.py#L68-L72




Discussion:

Resolving patch:
diff --git a/website/pushnotifications/migrations/0020_alter_device_registration_id.py b/website/pushnotifications/migrations/0020_alter_device_registration_id.py
new file mode 100644
--- /dev/null
+++ b/website/pushnotifications/migrations/0020_alter_device_registration_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0.3 on 2022-04-15 14:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('pushnotifications', '0019_remove_multilang_field'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='device',
+            name='registration_id',
+            field=models.TextField(unique=True, verbose_name='registration token'),
+        ),
+    ]
diff --git a/website/pushnotifications/models.py b/website/pushnotifications/models.py
--- a/website/pushnotifications/models.py
+++ b/website/pushnotifications/models.py
@@ -44,7 +44,9 @@ class Device(models.Model):
 
     DEVICE_TYPES = (("ios", "iOS"), ("android", "Android"))
 
-    registration_id = models.TextField(verbose_name=_("registration token"))
+    registration_id = models.TextField(
+        verbose_name=_("registration token"), unique=True
+    )
     type = models.CharField(choices=DEVICE_TYPES, max_length=10)
     active = models.BooleanField(
         verbose_name=_("active"),


Test patch:
Repository: svthalia/concrexit

Issue:
Remove Covid-19 from the message when the cancellation deadline passed for an event
### Is your feature request related to a problem? Please describe.
Since most of the effects from corona are gone, we would like to change the text that is shown at an event when its cancellation deadline has passed. The text I mean is the following:
![image](https://user-images.githubusercontent.com/65032271/170491603-5e6fb98f-404b-4b1f-a0d9-4930d6b8c6ae.png)

### Describe the solution you'd like
change the text from:
_"Registration is not possible anymore. Cancellation isn't possible anymore without having to pay the full costs of €XXX. Also note that you will be unable to re-register. Note: If you have any COVID-19 symptoms you will not have to pay these fees. Let us know via info@thalia.nu that this is this reason for your cancellation. "_

To a more general message like:
_"Registration is not possible anymore. Cancellation isn't possible anymore without having to pay the full costs of €XXX. Also note that you will be unable to re-register. If you had to cancel due to unforseen circumstances, send a mail to info@thalia.nu "_

### Motivation
The main effects from corona are over so we need to update this

### Describe alternatives you've considered
-

### Additional context
no additional context needed



Discussion:

Resolving patch:
diff --git a/website/events/models/event.py b/website/events/models/event.py
--- a/website/events/models/event.py
+++ b/website/events/models/event.py
@@ -206,10 +206,7 @@ def cancel_too_late_message(self):
         return _(
             "Cancellation isn't possible anymore without having to pay "
             "the full costs of €" + str(self.fine) + ". Also note that "
-            "you will be unable to re-register. Note: If you have any "
-            "COVID-19 symptoms you will not have to pay these fees. "
-            "Let us know via info@thalia.nu that this is this reason "
-            "for your cancellation."
+            "you will be unable to re-register."
         )
 
     @property


Test patch:
Repository: svthalia/concrexit

Issue:
Promotion Request not showing
### Describe the bug
The priority requests section in the administration shows 0 requests, while it is known there have been requests made

### How to reproduce
Steps to reproduce the behaviour:
1. Go to 'user options'
2. Click on 'administration'
3. Scroll down to 'promotion'
4. Click on 'promotion requests'

### Expected behaviour
The page should show all requests that have been made.

### Screenshots
![afbeelding](https://user-images.githubusercontent.com/12158186/188486344-65345249-fe61-438a-b98f-0c89a6241026.png)


### Additional context
Add any other context about the problem here.



Discussion:
@nvoers currently 

```python
class UpcomingRequestManager(models.Manager):
    def get_queryset(self):
        end_date = timezone.localdate()
        start_date = end_date - PROMO_PUBLISH_DATE_TIMEDELTA
        return super().get_queryset().filter(created_at__range=(start_date, end_date))
        
``` 
is the default manager for PromotionRequest, so it's not possible to view any non-upcoming promo request in the admin. Should there just be the default .objects manager with a default ordering for the admin instead?
The code is fine like this imo. But I made promo requests for borrels a month from now which for some reason have disappeared. Cato told me it ended up being something else rather than a mistake in the code
Well I don't think it's all intended behaviour. There are filters in the sidebar, even though only a fraction (requests made in the last week) can be seen.

So if you make a request for a borrel in november today, next week it will not be accessible any more, even thouh the publish date is over a month later.

Resolving patch:
diff --git a/website/promotion/migrations/0003_alter_promotionrequest_managers.py b/website/promotion/migrations/0003_alter_promotionrequest_managers.py
new file mode 100644
--- /dev/null
+++ b/website/promotion/migrations/0003_alter_promotionrequest_managers.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0.7 on 2022-09-14 19:42
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('promotion', '0002_alter_promotionrequest_managers_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterModelManagers(
+            name='promotionrequest',
+            managers=[
+            ],
+        ),
+    ]
diff --git a/website/promotion/models.py b/website/promotion/models.py
--- a/website/promotion/models.py
+++ b/website/promotion/models.py
@@ -31,7 +31,7 @@ def get_queryset(self):
 
 
 class PromotionRequest(models.Model):
-
+    objects = models.Manager()
     upcoming_requests = UpcomingRequestManager()
     new_requests = NewRequestManager()
 


Test patch:
Repository: svthalia/concrexit

Issue:
Cannot lock shifts
### Describe the bug
shifts cannot be locked anymore. backend says that there is an error, but there seems not to be a visible error

### How to reproduce
1. go to an unlocked shift
2. check the 'locked' box
3. save the shift
4. get an error that something is wrong, but 'locked' box becomes an icon
5. reload the page
6. shift is still unlocked

### Expected behaviour
shift should properly lock


Discussion:
I cannot reproduce this as superadmin on staging.
Okay reloading doesn't unlock the shift apparently. If you go back and click on the shift again, it should be unlocked again
I actually have been able to reproduce it in a shift with 1 unpaid order

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
@@ -22,6 +22,7 @@ class OrderItemInline(admin.TabularInline):
     extra = 0
 
     fields = ("product", "product_name", "amount", "total")
+    readonly_fields = ("product_name",)
 
     def get_readonly_fields(self, request: HttpRequest, obj: Order = None):
         default_fields = self.readonly_fields
diff --git a/website/sales/admin/shift_admin.py b/website/sales/admin/shift_admin.py
--- a/website/sales/admin/shift_admin.py
+++ b/website/sales/admin/shift_admin.py
@@ -146,7 +146,7 @@ class ShiftAdmin(admin.ModelAdmin):
 
     def get_readonly_fields(self, request, obj=None):
         fields = super().get_readonly_fields(request, obj)
-        if not obj:
+        if not obj or obj.locked:
             fields += ("locked",)
         return fields
 
@@ -221,7 +221,7 @@ def product_sales(self, obj):
 
     def payment_method_sales(self, obj):
         output = "\n".join(
-            f"- {dict(Payment.PAYMENT_TYPE)[k] if k else _('Unpaid')}: €{v:.2f}"
+            f"- {dict(Payment.PAYMENT_TYPE)[k] if k else _('Unpaid')}: €{v or '':.2f}"
             for k, v in obj.payment_method_sales.items()
         )
         return output
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
@@ -156,9 +156,6 @@ def clean(self):
         if self.shift.start > timezone.now():
             errors.update({"shift": _("The shift hasn't started yet.")})
 
-        if self.shift.locked:
-            errors.update({"shift": _("The shift this order belongs to is locked.")})
-
         if self.discount and self.discount > self.total_amount:
             errors.update(
                 {"discount": _("Discount cannot be higher than total amount.")}


Test patch: