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

Issue:
Merchandise page redesign
### Is your feature request related to a problem? Please describe.

The current merchandise page is a long list with lot's of text. Part of this problem is the board not hiding the merchandise items that are sold out, but I think some other layout than a list would help to improve the look.

### Describe the solution you'd like

View more images side by side, and make the images larger. The text is not very important for the merch at all, so it can be pushed to the background.

### Motivation

The board is getting new merch and would like the page to look better to get people interested in the merch.

### Describe alternatives you've considered

Keep the page as is, because people will buy merch anyway through whatsapp promotion etc.



Discussion:

Resolving patch:
diff --git a/website/merchandise/urls.py b/website/merchandise/urls.py
--- a/website/merchandise/urls.py
+++ b/website/merchandise/urls.py
@@ -15,5 +15,6 @@
                 path("", views.index, name="index"),
             ]
         ),
-    )
+    ),
+    path("association/merchandise/<int:id>/", views.product_page, name="product"),
 ]
diff --git a/website/merchandise/views.py b/website/merchandise/views.py
--- a/website/merchandise/views.py
+++ b/website/merchandise/views.py
@@ -1,4 +1,5 @@
 """The views for the merchandise package."""
+from django.http import Http404
 from django.shortcuts import render
 
 from merchandise.models import MerchandiseItem
@@ -13,3 +14,14 @@ def index(request):
     items = MerchandiseItem.objects.all()
 
     return render(request, "merchandise/index.html", {"items": items})
+
+
+def product_page(request, id):
+    try:
+        product = MerchandiseItem.objects.get(pk=id)
+    except MerchandiseItem.DoesNotExist:
+        raise Http404(
+            "This item may not exists, or is removed. Please check if the link is correct!"
+        )
+
+    return render(request, "merchandise/product_page.html", {"product": product})


Test patch:
Repository: svthalia/concrexit

Issue:
Increase profile retention period
<!-- Please add the appropriate label for what change should be made -->

### What?
Currently, Profiles are dataminimised 1 month after the member's last membership ends.
We should increase that period to 3 months. I have discussed this with @JeeVee11.

### Why?
It happens quite often that people forget to renew their membership, and decide to do it later than the first of october. In those cases, currently, their profile will already have been wiped. To make it easier for those people to still renew, we should extend the retention period a bit longer.

### How?
Change a constant somewhere in `members/services.py`, and update the privacy policy. 



Discussion:

Resolving patch:
diff --git a/website/members/services.py b/website/members/services.py
--- a/website/members/services.py
+++ b/website/members/services.py
@@ -218,7 +218,7 @@ def execute_data_minimisation(dry_run=False, members=None) -> list[Member]:
         .distinct()
         .prefetch_related("membership_set", "profile")
     )
-    deletion_period = timezone.now().date() - timezone.timedelta(days=31)
+    deletion_period = timezone.now().date() - timezone.timedelta(days=90)
     processed_members = []
     for member in members:
         if (


Test patch:
diff --git a/website/members/tests/test_services.py b/website/members/tests/test_services.py
--- a/website/members/tests/test_services.py
+++ b/website/members/tests/test_services.py
@@ -81,7 +81,7 @@ def test_process_email_change(self, send_message_mock):
             send_message_mock.assert_called_once_with(change_request)
 
 
-@freeze_time("2018-10-2")
+@freeze_time("2018-12-2")
 @override_settings(SUSPEND_SIGNALS=True)
 class DataMinimisationTest(TestCase):
     @classmethod
@@ -114,12 +114,12 @@ def setUpTestData(cls):
         )
 
     def test_removes_after_31_days_or_no_membership(self):
-        with self.subTest("Deletes after 31 days"):
+        with self.subTest("Deletes after 90 days"):
             processed = services.execute_data_minimisation(True)
             self.assertEqual(len(processed), 2)
             self.assertEqual(processed[0], self.m1)
 
-        with self.subTest("Deletes after 31 days"):
+        with self.subTest("Deletes after 90 days"):
             self.s1.until = timezone.now().replace(year=2018, month=11, day=1)
             self.s1.save()
             processed = services.execute_data_minimisation(True)
Repository: svthalia/concrexit

Issue:
EventRegistrations not linked in MoneyBird
### Describe the bug
In MoneyBird, a large amount of registrations for the Sinterklaas Cantus dit not have an external invoice created for them. Only me Jord and Joris (who all were registered through the back-end before registrations were open) had an external invoice. This also occured to the registrations of the Drink and Drive event. The problem happens for pin registrations as well as TPay registrations.

### How to reproduce
<!-- Steps to reproduce the behaviour -->
1. Log in to 'MoneyBird'
2. Click on 'Bank -> 'Pin (CCV)' or 'ThaliaPay (concrexit)'
3. Scroll down to 'the transactions from the Sinterklaas cantus'
4. See error


Discussion:
I tried running https://github.com/svthalia/concrexit/blob/c1d1827533d816bdc4f1ccb9c9d2bfeec632e3e2/website/moneybirdsynchronization/services.py#L367 in the shell on production, and it does return a bunch of registrations (I pushed all 72 right now). So for some reaon they aren't getting pushed. But from the logs, https://github.com/svthalia/concrexit/blob/c1d1827533d816bdc4f1ccb9c9d2bfeec632e3e2/website/moneybirdsynchronization/services.py#L407 is being run, so it's not because of it crashing on some unhandled exception.


Resolving patch:
diff --git a/website/moneybirdsynchronization/models.py b/website/moneybirdsynchronization/models.py
--- a/website/moneybirdsynchronization/models.py
+++ b/website/moneybirdsynchronization/models.py
@@ -156,13 +156,13 @@ def to_moneybird(self):
             }
         }
         bank_account = BankAccount.objects.filter(owner=self.member).last()
-        if bank_account and bank_account.valid_from < timezone.now().date():
+        if bank_account:
             data["contact"]["sepa_iban"] = bank_account.iban
             data["contact"]["sepa_bic"] = bank_account.bic or ""
             data["contact"][
                 "sepa_iban_account_name"
             ] = f"{bank_account.initials} {bank_account.last_name}"
-            if bank_account.valid:
+            if bank_account.valid and bank_account.valid_from < timezone.now().date():
                 data["contact"]["sepa_active"] = True
                 data["contact"]["sepa_mandate_id"] = bank_account.mandate_no
                 data["contact"]["sepa_mandate_date"] = bank_account.valid_from.strftime(


Test patch:
Repository: svthalia/concrexit

Issue:
Albums pagination doesn't maintain search terms
### Describe the bug
If you open https://thalia.nu/members/photos/?keywords=borrel#photos-albums, then go to the second page using the pagination buttons, the search term is dropped.

### Expected behaviour
<!-- A clear and concise description of what you expected to happen. -->
The search term remains

### Additional context
<!-- Add any other context about the problem here. -->
Could be since we introduced the shared paginated view template? So it's quite likely this occurs for other paginated filterable/searchable views as well.


Discussion:

Resolving patch:
diff --git a/website/thaliawebsite/views.py b/website/thaliawebsite/views.py
--- a/website/thaliawebsite/views.py
+++ b/website/thaliawebsite/views.py
@@ -31,6 +31,7 @@ class PagedView(ListView):
 
     def get_context_data(self, **kwargs) -> dict:
         context = super().get_context_data(**kwargs)
+        print(kwargs)
         page = context["page_obj"].number
         paginator = context["paginator"]
 
@@ -48,9 +49,17 @@ def get_context_data(self, **kwargs) -> dict:
 
         page_range = range(page_range_start, page_range_stop)
 
+        querydict = self.request.GET.copy()
+
+        if "page" in querydict:
+            del querydict["page"]
+
         context.update(
             {
                 "page_range": page_range,
+                "base_url": f"{self.request.path}?{querydict.urlencode()}&"
+                if querydict
+                else f"{self.request.path}?",
             }
         )
 


Test patch:
Repository: svthalia/concrexit

Issue:
Prevent invalid BIC
### Describe the bug
See https://thalia.sentry.io/issues/4580775428/events/2a88b45a07b645a6b87ae1f9442f74ef/. 

We sometimes get people creating international bank accounts without setting a (valid) BIC. We should validate against that.

### How to reproduce
Create a bank account with an non-dutch IBAN, but no BIC. That's possible but results in moneybird not accepting the contact. 

### Expected behaviour
A validation error forcing you to fill an IBAN.




Discussion:

Resolving patch:
diff --git a/website/payments/models.py b/website/payments/models.py
--- a/website/payments/models.py
+++ b/website/payments/models.py
@@ -425,13 +425,11 @@ def clean(self):
         super().clean()
         errors = {}
 
-        if self.bic is None and self.iban[0:2] != "NL":
-            errors.update(
-                {"bic": _("This field is required for foreign bank accounts.")}
-            )
+        if self.iban[0:2] != "NL" and not self.bic:
+            errors["bic"] = _("This field is required for foreign bank accounts.")
 
         if not self.owner:
-            errors.update({"owner": _("This field is required.")})
+            errors["owner"] = _("This field is required.")
 
         mandate_fields = [
             ("valid_from", self.valid_from),


Test patch:
Repository: svthalia/concrexit

Issue:
Separate promotion permissions in eventadmin inline from the main promotion perms
### What?
Currently, people need add/change_promotionrequest permission to make promotionrequests for their events. But with this permission they also get full access to all other promotionrequests. So we should make the inline in the eventadmin bypass that check. For all I care, anyone who can change an event can make a promorquest from the eventadmin (by virtue of their 'change_event' permission and being an organizer of the event), without having the add/change_promotionrequest permission, and thus without seeing the main Promotion Requests changelist page.

### Why?
<!-- A clear and concise motivation why we should consider implementing this. -->
Least privilege principle: many people should be allowed to request promotion for their own events, but don't need to be able to edit unrelated requests. And this way we can have promocie be able to bypass the requirements in #3529, without normal organizers being able to do the same.

### How?
Override has_xxx_permission() on the inline class. Read the inlinemodeladmin docs for guidance.



Discussion:

Resolving patch:
diff --git a/website/events/admin/inlines.py b/website/events/admin/inlines.py
--- a/website/events/admin/inlines.py
+++ b/website/events/admin/inlines.py
@@ -39,5 +39,19 @@ class PromotionRequestInline(admin.StackedInline):
         "assigned_to",
         "status",
         "drive_folder",
+        "status_updated",
     )
+
+    def has_add_permission(self, request, obj=None):
+        return True
+
+    def has_view_permission(self, request, obj=None):
+        return True
+
+    def has_change_permission(self, request, obj=None):
+        return True
+
+    def has_delete_permission(self, request, obj=None):
+        return True
+
     extra = 0


Test patch:
Repository: svthalia/concrexit

Issue:
Fix moneybird 'send_invoices_to_email includes a domain which cannot receive emails'
### Describe the bug
See https://thalia.sentry.io/issues/4723515444/events/60375e50a89442058907d1bb34b109cb/.

We should workaround or prevent this. I think a good option may be to just retry without 'send_invoices_to_email' field, as it's not required.

### How to reproduce
Specify an email address with a domain where no email server exists.



Discussion:

Resolving patch:
diff --git a/website/moneybirdsynchronization/services.py b/website/moneybirdsynchronization/services.py
--- a/website/moneybirdsynchronization/services.py
+++ b/website/moneybirdsynchronization/services.py
@@ -37,14 +37,29 @@ def create_or_update_contact(member: Member):
     moneybird = get_moneybird_api_service()
 
     if moneybird_contact.moneybird_id is None:
-        # Push the contact to Moneybird.
-        response = moneybird.create_contact(moneybird_contact.to_moneybird())
+        # Push contact to moneybird. This may fail with 422 when moneybird rejects an
+        # email address. In that case, we try once more leaving out the email address,
+        # as moneybird does not require that we set one at all.
+        try:
+            response = moneybird.create_contact(moneybird_contact.to_moneybird())
+        except Administration.InvalidData:
+            logger.info("Retrying to create contact without email...")
+            contact = moneybird_contact.to_moneybird()
+            del contact["send_invoices_to_email"]
+            response = moneybird.create_contact(contact)
+
         moneybird_contact.moneybird_id = response["id"]
     else:
-        # Update the contact data (right now we always do this, but we could use the version to check if it's needed)
-        response = moneybird.update_contact(
-            moneybird_contact.moneybird_id, moneybird_contact.to_moneybird()
-        )
+        # Update the contact data (right now we always do this, but we could use the version to check if it's needed).
+        try:
+            response = moneybird.update_contact(
+                moneybird_contact.moneybird_id, moneybird_contact.to_moneybird()
+            )
+        except Administration.InvalidData:
+            logger.info("Retrying to update contact without email...")
+            contact = moneybird_contact.to_moneybird()
+            del contact["send_invoices_to_email"]
+            response = moneybird.update_contact(moneybird_contact.moneybird_id, contact)
 
     moneybird_contact.moneybird_sepa_mandate_id = response["sepa_mandate_id"] or None
     moneybird_contact.needs_synchronization = False


Test patch:
Repository: svthalia/concrexit

Issue:
Prevent full disk
### Describe the bug
Sometimes the server's storage gets full, because for some reason filepond uploads aren't being deleted. Today this caused the server to crash (because the full server disk broke redis). We should prevent this from happening in multiple ways:

- Make old uploads be deleted. Would be nice to find out why the uploads aren't being deleted already. But we should also (additionally) periodically remove old files from the media volume.
- Maybe limit the volume size such that it getting full does not influence the rest of the server. But docker doesn't really support that nicely. We could make a separate volume for it on the host and bind-mount it I guess.

### How to reproduce
<!-- Steps to reproduce the behaviour -->
1. Upload lots of albums to a docker deployment
2. See the media volume get larger.

### Expected behaviour
Stuff is cleaned up once it's processed and periodically.





Discussion:

Resolving patch:
diff --git a/website/photos/tasks.py b/website/photos/tasks.py
--- a/website/photos/tasks.py
+++ b/website/photos/tasks.py
@@ -3,7 +3,6 @@
 
 from celery import shared_task
 from django_drf_filepond.models import TemporaryUpload
-from django_filepond_widget.fields import FilePondFile
 
 from photos.models import Album
 
@@ -19,7 +18,8 @@ def process_album_upload(archive_upload_id: str, album_id: int):
     except Album.DoesNotExist:
         return
 
-    archive = TemporaryUpload.objects.get(upload_id=archive_upload_id).file
+    upload = TemporaryUpload.objects.get(upload_id=archive_upload_id)
+    archive = upload.file
     try:
         with transaction.atomic():
             # We make the upload atomic separately, so we can keep using the db if it fails.
@@ -32,5 +32,5 @@ def process_album_upload(archive_upload_id: str, album_id: int):
             # by facedetection, and possibly in the future to notify the uploader.
             album_uploaded.send(sender=None, album=album)
     finally:
-        if isinstance(archive, FilePondFile):
-            archive.remove()
+        archive.delete()
+        upload.delete()


Test patch:
Repository: svthalia/concrexit

Issue:
Fix newsletter logo sizing in mail clients
### Describe the bug
![image](https://github.com/svthalia/concrexit/assets/41264528/90e24098-bdf5-4f67-98b2-d665d25f7ae8)

### How to reproduce
Send a newsletter with large partner logos. See that in iOS mail, the layout is messed up. But in the browser, it's fine.

### Expected behaviour
See browser version of the newsletter.


Discussion:

Resolving patch:
diff --git a/website/newsletters/services.py b/website/newsletters/services.py
--- a/website/newsletters/services.py
+++ b/website/newsletters/services.py
@@ -1,6 +1,5 @@
 import base64
 import logging
-import math
 from io import BytesIO
 
 from django.core.files.base import ContentFile
@@ -92,10 +91,12 @@ def send_newsletter(newsletter):
 
 
 def split_local_partners():
-    all_local_partners = Partner.objects.filter(is_local_partner=True).order_by("?")
+    all_local_partners = Partner.objects.filter(
+        is_local_partner=True, is_active=True
+    ).order_by("?")
     local_partner_count = len(all_local_partners)
     local_partners = []
-    for i in range(math.floor(local_partner_count / 2)):
+    for i in range(local_partner_count // 2):
         local_partners.append(
             [all_local_partners[i * 2], all_local_partners[i * 2 + 1]]
         )
diff --git a/website/thaliawebsite/settings.py b/website/thaliawebsite/settings.py
--- a/website/thaliawebsite/settings.py
+++ b/website/thaliawebsite/settings.py
@@ -1043,6 +1043,16 @@ def show_toolbar(request):
                 },
             ],
         },
+        "fit_small_pad": {
+            "FORMAT": "webp",
+            "PROCESSORS": [
+                {
+                    "PATH": "utils.media.processors.thumbnail",
+                    "size": (360, 150),
+                    "mode": "pad",
+                },
+            ],
+        },
         "fit_large": {
             "FORMAT": "webp",
             "PROCESSORS": [


Test patch:
Repository: svthalia/concrexit

Issue:
Moneybird contacts are not made for alumni people
### Describe the bug
<!-- A clear and concise description of what the bug is. -->
People who are alumni don't have a membership anymore even if they still have an account and thus don't get a moneybird contact which is bad.

### Expected behaviour
<!-- A clear and concise description of what you expected to happen. -->
If someone has an account and makes a payment at a thalia event, they should get a moneybird contact.




Discussion:
I discussed with Roel that we can go with an easier/cleaner solution: when a payer exists for an invoice, but we don't have a moneybirdcontact for whatever reason, we can just fall back to using the unknown payer contact id, like we do for invoices without a payer set at all. 
The project then remains the same, so as far as I know this doesn't really matter for the bookkeeping.

This is more robust and clean because:
- it also works if we somehow can't succeed in making a contact
- it means we don't run into trouble when creating a contact of a minimized profile (which has no address etc.)
- it also means we don't have to make the logic to determine which contacts can be deleted/archived more complex, and don't have to decide on the privacy implications of having contacts of non-members in the site.

@nvoers just to be sure, what do you think about this?

Resolving patch:
diff --git a/website/moneybirdsynchronization/models.py b/website/moneybirdsynchronization/models.py
--- a/website/moneybirdsynchronization/models.py
+++ b/website/moneybirdsynchronization/models.py
@@ -258,13 +258,16 @@ def get_for_object(cls, obj):
     def to_moneybird(self):
         moneybird = get_moneybird_api_service()
 
-        if self.payable.payment_payer is None:
-            contact_id = settings.MONEYBIRD_UNKNOWN_PAYER_CONTACT_ID
-        else:
-            moneybird_contact = MoneybirdContact.objects.get(
-                member=self.payable.payment_payer
-            )
-            contact_id = moneybird_contact.moneybird_id
+        contact_id = settings.MONEYBIRD_UNKNOWN_PAYER_CONTACT_ID
+
+        if self.payable.payment_payer is not None:
+            try:
+                moneybird_contact = MoneybirdContact.objects.get(
+                    member=self.payable.payment_payer
+                )
+                contact_id = moneybird_contact.moneybird_id
+            except MoneybirdContact.DoesNotExist:
+                pass
 
         invoice_date = date_for_payable_model(self.payable_object).strftime("%Y-%m-%d")
 


Test patch:
Repository: svthalia/concrexit

Issue:
Disable Sentry cron monitoring


### What?
We need to disable the sentry 'cron' monitoring of periodic tasks.

### Why?
Sentry is making cron monitors paid after the beta.

### How?
I think it's a single line in settings.py, and maybe some cleanup on sentry to remove the existing monitors.



Discussion:

Resolving patch:
diff --git a/website/thaliawebsite/settings.py b/website/thaliawebsite/settings.py
--- a/website/thaliawebsite/settings.py
+++ b/website/thaliawebsite/settings.py
@@ -555,9 +555,7 @@ def show_toolbar(request):
         dsn=os.environ.get("SENTRY_DSN"),
         integrations=[
             DjangoIntegration(),
-            CeleryIntegration(
-                monitor_beat_tasks=True,
-            ),
+            CeleryIntegration(),
         ],
         release=SOURCE_COMMIT,
         send_default_pii=True,


Test patch:
Repository: svthalia/concrexit

Issue:
No registration message no longer accepts links
### Describe the bug
Since #2458 the event registration status (specifically no_registration_message) doesn't support URLs any more on the website (app is unaltered I think). I aslo think the formatting that happens now might be a bit risky.

### How to reproduce
Make an event with URL in the no_registration message and view it.

### Expected behaviour
A nice link.

### Screenshots
<img width="627" alt="afbeelding" src="https://github.com/svthalia/concrexit/assets/41264528/66d61a15-0ceb-43bf-aa5a-a6536a02739d">





Discussion:

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
@@ -98,7 +98,26 @@ class EventAdmin(DoNextModelAdmin):
                     "cancel_deadline",
                     "send_cancel_email",
                     "optional_registrations",
+                ),
+                "classes": ("collapse",),
+            },
+        ),
+        (
+            _("Registration status messages"),
+            {
+                "fields": (
                     "no_registration_message",
+                    "registration_msg_optional",
+                    "registration_msg_optional_registered",
+                    "registration_msg_registered",
+                    "registration_msg_open",
+                    "registration_msg_full",
+                    "registration_msg_waitinglist",
+                    "registration_msg_will_open",
+                    "registration_msg_expired",
+                    "registration_msg_cancelled",
+                    "registration_msg_cancelled_late",
+                    "registration_msg_cancelled_final",
                 ),
                 "classes": ("collapse",),
             },


Test patch: