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

Issue:
Admin sales shift API should also return total_paid_revenue
### Is your feature request related to a problem? Please describe.
The current admin sales shift api route only gives the total_revenue for a shift, but this might contain unpaid orders. We don't want those in certain scoreboards, like for the rag week.

### Describe the solution you'd like
Add `total_paid_revenue`

### Motivation

### Describe alternatives you've considered

### Additional context



Discussion:

Resolving patch:
diff --git a/website/sales/api/v2/admin/serializers/shift.py b/website/sales/api/v2/admin/serializers/shift.py
--- a/website/sales/api/v2/admin/serializers/shift.py
+++ b/website/sales/api/v2/admin/serializers/shift.py
@@ -1,5 +1,6 @@
 from rest_framework import serializers
 
+from payments.api.v2.serializers.payment_amount import PaymentAmountSerializer
 from sales.models.product import ProductListItem
 from sales.models.shift import Shift
 
@@ -36,13 +37,13 @@ class Meta:
             "end",
             "products",
             "total_revenue",
+            "total_revenue_paid",
             "num_orders",
             "product_sales",
         )
 
-    total_revenue = serializers.DecimalField(
-        max_digits=10, decimal_places=2, min_value=0, read_only=True
-    )
+    total_revenue = PaymentAmountSerializer(min_value=0, read_only=True)
+    total_revenue_paid = PaymentAmountSerializer(min_value=0, read_only=True)
 
     products = ProductListItemSerializer(
         source="product_list.product_items", many=True, read_only=True


Test patch:
Repository: svthalia/concrexit

Issue:
Fix reference face upload UI
The reference face UI should display better buttons for deleting a reference face, and for displaying whether a reference face is processing or accepted / rejected.


Discussion:

Resolving patch:
diff --git a/website/facedetection/services.py b/website/facedetection/services.py
--- a/website/facedetection/services.py
+++ b/website/facedetection/services.py
@@ -30,7 +30,7 @@ def execute_data_minimisation(dry_run=False):
 
     queryset = ReferenceFace.objects.filter(
         Q(marked_for_deletion_at__lte=delete_period_marked_for_deletion)
-        | Q(member__last_login__lte=delete_period_inactive_member)
+        | Q(user__last_login__lte=delete_period_inactive_member)
     )
 
     if not dry_run:
diff --git a/website/facedetection/views.py b/website/facedetection/views.py
--- a/website/facedetection/views.py
+++ b/website/facedetection/views.py
@@ -75,7 +75,6 @@ def get_context_data(self, **kwargs):
 
 class ReferenceFaceView(LoginRequiredMixin, ListView):
     template_name = "facedetection/reference-faces.html"
-    context_object_name = "reference_faces"
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
@@ -91,6 +90,12 @@ def get_context_data(self, **kwargs):
             ).count()
             >= settings.FACEDETECTION_MAX_NUM_REFERENCE_FACES
         )
+        context[
+            "has_rejected_reference_faces"
+        ] = self.request.member.reference_faces.filter(
+            status=ReferenceFace.Status.REJECTED,
+            marked_for_deletion_at__isnull=True,
+        ).exists()
         return context
 
     def get_queryset(self):


Test patch:
Repository: svthalia/concrexit

Issue:
Add http endpoint for the next event 
### Is your feature request related to a problem? Please describe.
We want to use a dns redirect to redirect to the first upcoming Thalia event. 

### Describe the solution you'd like
An endpoint (like /events/next) that redicects to the next Thalia event 
### Motivation
We want to setup a domain (sog.je) that redirects to this 

### Describe alternatives you've considered
Static html host that uses javascriot, but this would require using additional resources 


Discussion:
Okay I was confused thinking you meant an API endpoint. For a redirect view I see the appeal. But some things to consider: what if there are multiple events starting at the same time? If there's no future event i guess 404 would be fine as its unlikely to happen
Also note that you won't be able to accomplish your goal with a DNS redirect. You need a HTTP 301/302 redirect 
e.g. google domains will do that for you for free and with nearly no setup though

Resolving patch:
diff --git a/website/events/urls.py b/website/events/urls.py
--- a/website/events/urls.py
+++ b/website/events/urls.py
@@ -9,6 +9,7 @@
     EventIndex,
     EventRegisterView,
     MarkPresentView,
+    NextEventView,
     RegistrationView,
 )
 
@@ -20,6 +21,7 @@
         include(
             [
                 path("<int:pk>/", EventDetail.as_view(), name="event"),
+                path("next/", NextEventView.as_view(), name="next"),
                 path("<slug:slug>/", EventDetail.as_view(), name="event"),
                 path(
                     "<int:pk>/registration/register/",
diff --git a/website/events/views.py b/website/events/views.py
--- a/website/events/views.py
+++ b/website/events/views.py
@@ -2,6 +2,7 @@
 
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
+from django.http import Http404
 from django.shortcuts import get_object_or_404, redirect
 from django.utils import timezone
 from django.utils.decorators import method_decorator
@@ -225,3 +226,20 @@ def get(self, request, *args, **kwargs):
                 messages.success(request, _("You have been marked as present."))
 
         return redirect(event)
+
+
+class NextEventView(View):
+    def get(self, request, *args, **kwargs):
+        """HTTP redirect to the next event.
+
+        Checks if there is an upcoming event. Raise a 404 if none exists.
+        """
+        upcoming_activity = (
+            Event.objects.filter(published=True, end__gte=timezone.now())
+            .order_by("end")
+            .first()
+        )
+        if not upcoming_activity:
+            raise Http404("There is no upcoming event.")
+
+        return redirect(upcoming_activity)


Test patch:
Repository: svthalia/concrexit

Issue:
Make mailinglist sync case-insensitive
### Describe the bug
I was checking some google admin audit logs and saw that the gsuit sync keeps removing and re-adding some people with capitals in their email adress to the mailinglists they're in. 

For example, if I have Dirk@gmail.nl, google would normalize that to dirk@gmail.com. When the sync runs, it determines the deisred group to be `["Dirk@gmail.com"]`. It requests the current members of a group, getting `["dirk@gmail.com"]`, then remove `dirk@gmail.com` and add `Dirk@gmail.com`. This clearly has no effect, but is just inefficient and ugly. And it might cause some issues because the people in question leave their groups for a few seconds every hour.

### Expected behaviour
capitalized email addresses are treated the same as their lowercase version.

### Screenshots
<img width="470" alt="image" src="https://github.com/svthalia/concrexit/assets/41264528/f70713bf-3269-469e-b700-57e3a6ee9bc7">




Discussion:
Actually from the screenshot I guess it's not always true that google normalizes to lowercase. But the sync should just compare insensitively before changing anything
Might be nice for whoever does this to also look at #2492 

Resolving patch:
diff --git a/website/mailinglists/gsuite.py b/website/mailinglists/gsuite.py
--- a/website/mailinglists/gsuite.py
+++ b/website/mailinglists/gsuite.py
@@ -289,15 +289,15 @@ def _update_group_members(self, group):
                 members_list += members_response.get("members", [])
 
             existing_members = [
-                m["email"] for m in members_list if m["role"] == "MEMBER"
+                m["email"].lower() for m in members_list if m["role"] == "MEMBER"
             ]
             existing_managers = [
-                m["email"] for m in members_list if m["role"] == "MANAGER"
+                m["email"].lower() for m in members_list if m["role"] == "MANAGER"
             ]
         except HttpError:
             logger.exception(f"Could not obtain list member data for {group.name}")
             return  # the list does not exist or something else is wrong
-        new_members = group.addresses
+        new_members = [x.lower() for x in group.addresses]
 
         remove_list = [x for x in existing_members if x not in new_members]
         insert_list = [


Test patch:
Repository: svthalia/concrexit

Issue:
Add nice validation for event slug uniqueness
### Describe the bug
https://thalia.sentry.io/issues/4199123754/?project=1463433&query=is%3Aunresolved&referrer=issue-stream&stream_index=1

When saving an event with duplicate slug, you get a 500. The admin should have pretty validation for this.

### How to reproduce
Steps to reproduce the behaviour:
1. Create an event with a slug.
2. Create another event with the same slug.

### Expected behaviour
A validation message on the field.




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
@@ -349,6 +349,8 @@ def clean(self):
         # pylint: disable=too-many-branches
         super().clean()
         errors = {}
+        if Event.objects.exclude(id=self.id).filter(slug=self.slug).exists():
+            errors.update({"slug": _("Slug must be unique")})
         if self.start is None:
             errors.update({"start": _("Start cannot have an empty date or time field")})
         if self.end is None:


Test patch:
Repository: svthalia/concrexit

Issue:
Liked photos API returns duplicate photos on different api requests
### Describe the bug
Different pages of the liked photos API return the same photo

### How to reproduce
Difficult, this is using @JeeVee11's account as I could not reproduce it. 

https://staging.thalia.nu/api/v2/photos/photos/liked/?limit=10&offset=0
https://staging.thalia.nu/api/v2/photos/photos/liked/?limit=30&offset=10

Both have pk=70
### Expected behaviour
this should kinda not happen

### Additional context
This is likely related to photos not following order and https://github.com/svthalia/Reaxit/issues/359



Discussion:
As we've solved the order of photos, I imagine this is probably also solved? Seemed like the like count annotation messes with the db-default ordering (no ordering != pk ordering if some join is done). @JAicewizard could you verify, as I already couldn't reproduce?
> As we've solved the order of photos, I imagine this is probably also solved? Seemed like the like count annotation messes with the db-default ordering (no ordering != pk ordering if some join is done). @JAicewizard could you verify, as I already couldn't reproduce?

Still does not work :(

Resolving patch:
diff --git a/website/photos/api/v2/views.py b/website/photos/api/v2/views.py
--- a/website/photos/api/v2/views.py
+++ b/website/photos/api/v2/views.py
@@ -105,6 +105,8 @@ def get_queryset(self):
                 member_likes=Count("likes", filter=Q(likes__member=self.request.member))
             )
             .select_properties("num_likes")
+            # Fix select_properties dropping the default ordering.
+            .order_by("pk")
         )
 
 


Test patch:
Repository: svthalia/concrexit

Issue:
Thumbnail prefetching assumes all registrations have members/profiles
### Describe the bug
https://thalia.sentry.io/issues/4209443189/?project=1463433&query=is%3Aunresolved+issue.category%3Aerror&referrer=issue-stream&statsPeriod=14d&stream_index=0
and
https://thalia.sentry.io/issues/4209106388/?project=1463433&query=is%3Aunresolved+issue.category%3Aerror&referrer=issue-stream&statsPeriod=14d&stream_index=3

### How to reproduce
Steps to reproduce the behaviour:
1. Make a registration without member (but a name)
2. do some API/frontend requests.

### Expected behaviour
The registrations without members are ignored.




Discussion:

Resolving patch:
diff --git a/website/events/api/v2/views.py b/website/events/api/v2/views.py
--- a/website/events/api/v2/views.py
+++ b/website/events/api/v2/views.py
@@ -128,7 +128,9 @@ def get_queryset(self):
     def get_serializer(self, *args, **kwargs):
         if len(args) > 0:
             registrations = args[0]
-            fetch_thumbnails_db([r.member.profile.photo for r in registrations])
+            fetch_thumbnails_db(
+                [r.member.profile.photo for r in registrations if r.member]
+            )
         return super().get_serializer(*args, **kwargs)
 
     def initial(self, request, *args, **kwargs):
diff --git a/website/events/views.py b/website/events/views.py
--- a/website/events/views.py
+++ b/website/events/views.py
@@ -88,7 +88,9 @@ def get_context_data(self, **kwargs):
             "member", "member__profile"
         )
 
-        fetch_thumbnails_db([p.member.profile.photo for p in context["participants"]])
+        fetch_thumbnails_db(
+            [p.member.profile.photo for p in context["participants"] if p.member]
+        )
 
         return context
 


Test patch:
Repository: svthalia/concrexit

Issue:
Make the event location clickable
Make the location of an event clickable such that it links to the correct location on google maps



Discussion:
Consideration: maybe not for all events. For example, if a location is HG00.317, we might not want to have it clickable. Or do we?

We could simply solve this with a checkbox

Resolving patch:
diff --git a/website/events/admin/event.py b/website/events/admin/event.py
--- a/website/events/admin/event.py
+++ b/website/events/admin/event.py
@@ -79,6 +79,7 @@ class EventAdmin(DoNextModelAdmin):
                     "caption",
                     "location",
                     "map_location",
+                    "show_map_location",
                 ),
                 "classes": ("collapse", "start-open"),
             },
diff --git a/website/events/migrations/0067_event_show_map_location.py b/website/events/migrations/0067_event_show_map_location.py
new file mode 100644
--- /dev/null
+++ b/website/events/migrations/0067_event_show_map_location.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.1 on 2023-05-31 13:20
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("events", "0066_event_slug"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="event",
+            name="show_map_location",
+            field=models.BooleanField(
+                default=True, verbose_name="show url for location"
+            ),
+        ),
+    ]
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,6 +151,11 @@ class Event(models.Model):
         ),
     )
 
+    show_map_location = models.BooleanField(
+        _("show url for location"),
+        default=True,
+    )
+
     price = PaymentAmountField(
         verbose_name=_("price"),
         allow_zero=True,
@@ -319,6 +324,15 @@ def has_food_event(self):
         except ObjectDoesNotExist:
             return False
 
+    @property
+    def location_link(self):
+        """Return the link to the location on google maps."""
+        if self.show_map_location is False:
+            return None
+        return "https://www.google.com/maps/place/" + self.map_location.replace(
+            " ", "+"
+        )
+
     def clean_changes(self, changed_data):
         """Check if changes from `changed_data` are allowed.
 


Test patch:
Repository: svthalia/concrexit

Issue:
Add nice validation for event slug uniqueness
### Describe the bug
https://thalia.sentry.io/issues/4199123754/?project=1463433&query=is%3Aunresolved&referrer=issue-stream&stream_index=1

When saving an event with duplicate slug, you get a 500. The admin should have pretty validation for this.

### How to reproduce
Steps to reproduce the behaviour:
1. Create an event with a slug.
2. Create another event with the same slug.

### Expected behaviour
A validation message on the field.




Discussion:
I see now that the problem was actually a bit different. Due to https://github.com/svthalia/concrexit/blob/80ca7d9fdaf59466e28a8a28594d28dba49de040/website/events/admin/event.py#L121, the Event form.clean doesn't reuse ModelForm.clean, causing it to not run a bunch of validation. See https://docs.djangoproject.com/en/4.2/topics/forms/modelforms/#overriding-the-clean-method. So I think it would be best to solve that. Also, the SlugField has `unique=True, blank=True`, meaning that we can't create multiple (new, so with blank instead of null) events with blank slug. 

Resolving patch:
diff --git a/website/events/admin/event.py b/website/events/admin/event.py
--- a/website/events/admin/event.py
+++ b/website/events/admin/event.py
@@ -119,7 +119,6 @@ def get_queryset(self, request):
 
     def get_form(self, request, obj=None, change=False, **kwargs):
         form = super().get_form(request, obj, change, **kwargs)
-        form.clean = lambda form: form.instance.clean_changes(form.changed_data)
         form.request = request
         return form
 
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
@@ -363,8 +363,6 @@ def clean(self):
         # pylint: disable=too-many-branches
         super().clean()
         errors = {}
-        if Event.objects.exclude(id=self.id).filter(slug=self.slug).exists():
-            errors.update({"slug": _("Slug must be unique")})
         if self.start is None:
             errors.update({"start": _("Start cannot have an empty date or time field")})
         if self.end is None:


Test patch:
Repository: svthalia/concrexit

Issue:
ThaliaPay batch push throttled by Moneybird
Whenever we process a ThaliaPay batch, we are throttled by Moneybird on the amount of API requests we make.
This is most likely due to saving the bank accounts which are pushed to moneybird when the post_save signal is triggered.
This shouldn't be triggered when the last_used field of the bank account is updated, but it is.

Potential fix:
Make sure that updated_fields is passed (correctly) with the post_save signal for bank accounts.


Discussion:

Resolving patch:
diff --git a/website/payments/services.py b/website/payments/services.py
--- a/website/payments/services.py
+++ b/website/payments/services.py
@@ -152,12 +152,13 @@ def process_batch(batch):
     for payment in payments:
         bank_account = payment.paid_by.bank_accounts.last()
         if not bank_account:  # pragma: no cover
-            # This should not happen, cannot haver, does not happen (right... ;p), but if it does, we don't want to crash, but just remove the payment from the batch (make it unprocessed)
+            # This should not happen, cannot happen, does not happen (right... ;p)
+            # but if it does, we don't want to crash, but just remove the payment from the batch (make it unprocessed)
             payment.batch = None
             payment.save()
         else:
             bank_account.last_used = batch.withdrawal_date
-            bank_account.save()
+            bank_account.save(update_fields=["last_used"])
 
     batch.save()
     processed_batch.send(sender=None, instance=batch)


Test patch:
Repository: svthalia/concrexit

Issue:
Re-add rudamentary version of organisers to the event list API endpoint
<!--

Please add the appropriate label for what change should be made:
docs: changes to the documentation)
refactor: refactoring production code, eg. renaming a variable or rewriting a function
test: adding missing tests, refactoring tests; no production code change
chore: updating poetry etc; no production code change

-->

### Describe the change
Add a id-name based list of event organisers to the event list API endpoint.

### Motivation
Because it would make me and my Reaxit PR very happy.

### Current implementation
It got removed :(

### Suggested implementation
Just do it Kappa



Discussion:

Resolving patch:
diff --git a/website/activemembers/api/v2/serializers/member_group.py b/website/activemembers/api/v2/serializers/member_group.py
--- a/website/activemembers/api/v2/serializers/member_group.py
+++ b/website/activemembers/api/v2/serializers/member_group.py
@@ -72,3 +72,16 @@ class Meta:
             "contact_address",
             "photo",
         )
+
+
+class MemberGroupShortSerializer(MemberGroupListSerializer):
+    class Meta:
+        model = MemberGroup
+        fields = (
+            "pk",
+            "name",
+            "type",
+            "since",
+            "until",
+            "contact_address",
+        )
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
@@ -1,7 +1,10 @@
 from rest_framework import serializers
 from rest_framework.reverse import reverse
 
-from activemembers.api.v2.serializers.member_group import MemberGroupSerializer
+from activemembers.api.v2.serializers.member_group import (
+    MemberGroupSerializer,
+    MemberGroupShortSerializer,
+)
 from documents.api.v2.serializers.document import DocumentSerializer
 from events import services
 from events.api.v2.serializers.event_registration import EventRegistrationSerializer
@@ -123,34 +126,4 @@ def _maps_url(self, instance):
 
 
 class EventListSerializer(EventSerializer):
-    class Meta:
-        model = Event
-        fields = (
-            "pk",
-            "slug",
-            "url",
-            "title",
-            "description",
-            "caption",
-            "start",
-            "end",
-            "category",
-            "registration_start",
-            "registration_end",
-            "cancel_deadline",
-            "optional_registrations",
-            "location",
-            "price",
-            "fine",
-            "num_participants",
-            "max_participants",
-            "no_registration_message",
-            "registration_status",
-            "cancel_too_late_message",
-            "has_fields",
-            "food_event",
-            "maps_url",
-            "user_permissions",
-            "user_registration",
-            "documents",
-        )
+    organisers = MemberGroupShortSerializer(many=True)


Test patch: