We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Admin PaymentWidget can crash if payable.payment_payer returns None
### Describe the bug
After #1320, the PaymentWidget (`payments/widgets.py`) can crash if the `payment_payer` is `None`, for example for `registrations`. 

### How to reproduce
Steps to reproduce the behaviour:
1. Make a registration
2. Accept it (so the payment widget will appear)
3. Crash

### Expected behaviour
Not crash (we can just feed `None` to the html template)

Admin PaymentWidget can crash if payable.payment_payer returns None
### Describe the bug
After #1320, the PaymentWidget (`payments/widgets.py`) can crash if the `payment_payer` is `None`, for example for `registrations`. 

### How to reproduce
Steps to reproduce the behaviour:
1. Make a registration
2. Accept it (so the payment widget will appear)
3. Crash

### Expected behaviour
Not crash (we can just feed `None` to the html template)


--- 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/widgets.py`
Content:
```
1 """Widgets provided by the payments package"""
2 from django.contrib.contenttypes.models import ContentType
3 from django.forms import Widget
4 
5 from payments.models import Payment, PaymentUser
6 
7 
8 class PaymentWidget(Widget):
9     """
10     Custom widget for the Payment object, used in registrations
11     """
12 
13     template_name = "payments/widgets/payment.html"
14 
15     def __init__(self, attrs=None, obj=None):
16         super().__init__(attrs)
17         self.obj = obj
18 
19     def get_context(self, name, value, attrs) -> dict:
20         context = super().get_context(name, value, attrs)
21         if self.obj and not value:
22             context["obj"] = self.obj
23             context["payable_payer"] = (
24                 PaymentUser.objects.get(pk=self.obj.payment_payer.pk)
25                 if hasattr(self.obj, "payment_payer")
26                 else self.obj.paid_by
27             )
28             context["content_type"] = ContentType.objects.get_for_model(self.obj)
29         elif value:
30             payment = Payment.objects.get(pk=value)
31             context["url"] = payment.get_admin_url()
32             context["payment"] = payment
33         return context
34 
35     class Media:
36         js = ("admin/payments/js/payments.js",)
37 
38 
39 class SignatureWidget(Widget):
40     """
41     Widget for signature image
42     """
43 
44     template_name = "payments/widgets/signature.html"
45 
46     class Media:
47         js = (
48             "payments/js/signature_pad.min.js",
49             "payments/js/main.js",
50         )
51         css = {"all": ("admin/payments/css/signature.css",)}
52
```

Path: `website/payments/admin_views.py`
Content:
```
1 """Admin views provided by the payments package"""
2 import csv
3 
4 from django.apps import apps
5 from django.contrib import messages
6 from django.contrib.admin.utils import model_ngettext
7 from django.contrib.admin.views.decorators import staff_member_required
8 from django.contrib.auth.decorators import permission_required
9 from django.db.models import Sum, Count, Min, Max
10 from django.http import HttpResponse
11 from django.core.exceptions import SuspiciousOperation, DisallowedRedirect
12 from django.shortcuts import redirect, get_object_or_404, render
13 from django.utils import timezone
14 from django.utils.text import capfirst
15 from django.utils.decorators import method_decorator
16 from django.utils.http import url_has_allowed_host_and_scheme
17 from django.utils.translation import gettext_lazy as _
18 from django.views import View
19 
20 from payments import services
21 from .models import Payment, Batch, PaymentUser
22 
23 
24 @method_decorator(staff_member_required, name="dispatch")
25 @method_decorator(
26     permission_required("payments.process_payments"), name="dispatch",
27 )
28 class PaymentAdminView(View):
29     """
30     View that creates a payment
31     """
32 
33     def post(self, request, *args, app_label, model_name, payable, **kwargs):
34         if "type" not in request.POST:
35             raise SuspiciousOperation("Missing POST parameters")
36 
37         if "next" in request.POST and not url_has_allowed_host_and_scheme(
38             request.POST.get("next"), allowed_hosts={request.get_host()}
39         ):
40             raise DisallowedRedirect
41 
42         payable_model = apps.get_model(app_label=app_label, model_name=model_name)
43         payable_obj = payable_model.objects.get(pk=payable)
44 
45         result = services.create_payment(
46             payable_obj,
47             PaymentUser.objects.get(pk=self.request.member.pk),
48             request.POST["type"],
49         )
50         payable_obj.save()
51 
52         if result:
53             messages.success(
54                 request, _("Successfully paid %s.") % model_ngettext(payable_obj, 1),
55             )
56         else:
57             messages.error(
58                 request, _("Could not pay %s.") % model_ngettext(payable_obj, 1),
59             )
60             return redirect(f"admin:{app_label}_{model_name}_change", payable_obj.pk)
61 
62         if "next" in request.POST:
63             return redirect(request.POST["next"])
64 
65         return redirect("admin:payments_payment_change", result.pk)
66 
67 
68 @method_decorator(staff_member_required, name="dispatch")
69 @method_decorator(
70     permission_required("payments.process_batches"), name="dispatch",
71 )
72 class BatchProcessAdminView(View):
73     """
74     View that processes a batch
75     """
76 
77     def post(self, request, *args, **kwargs):
78         batch = Batch.objects.get(pk=kwargs["pk"])
79 
80         if "next" in request.POST and not url_has_allowed_host_and_scheme(
81             request.POST.get("next"), allowed_hosts={request.get_host()}
82         ):
83             raise DisallowedRedirect
84 
85         if batch.processed:
86             messages.error(
87                 request, _("{} already processed.").format(model_ngettext(batch, 1))
88             )
89         else:
90             batch.processed = True
91             payments = batch.payments_set.select_related("paid_by")
92             for payment in payments:
93                 bank_account = payment.paid_by.bank_accounts.last()
94                 bank_account.last_used = timezone.now()
95                 bank_account.save()
96 
97             batch.save()
98 
99             services.send_tpay_batch_processing_emails(batch)
100 
101             messages.success(
102                 request,
103                 _("Successfully processed {}.").format(model_ngettext(batch, 1)),
104             )
105 
106         if "next" in request.POST:
107             return redirect(request.POST["next"])
108 
109         return redirect("admin:payments_batch_change", kwargs["pk"])
110 
111 
112 @method_decorator(staff_member_required, name="dispatch")
113 @method_decorator(
114     permission_required("payments.process_batches"), name="dispatch",
115 )
116 class BatchExportAdminView(View):
117     """
118     View that exports a batch
119     """
120 
121     def post(self, request, *args, **kwargs):
122         batch = Batch.objects.get(pk=kwargs["pk"])
123 
124         response = HttpResponse(content_type="text/csv")
125         response["Content-Disposition"] = 'attachment;filename="batch.csv"'
126         writer = csv.writer(response)
127         headers = [
128             _("Account holder"),
129             _("IBAN"),
130             _("Mandate Reference"),
131             _("Amount"),
132             _("Description"),
133             _("Mandate Date"),
134         ]
135         writer.writerow([capfirst(x) for x in headers])
136 
137         member_rows = batch.payments_set.values("paid_by").annotate(total=Sum("amount"))
138 
139         for row in member_rows:
140             member = PaymentUser.objects.get(id=row["paid_by"])
141             bankaccount = member.bank_accounts.last()
142             writer.writerow(
143                 [
144                     member.get_full_name(),
145                     bankaccount.iban,
146                     bankaccount.mandate_no,
147                     f"{row['total']:.2f}",
148                     batch.description,
149                     bankaccount.valid_from,
150                 ]
151             )
152         return response
153 
154 
155 @method_decorator(staff_member_required, name="dispatch")
156 @method_decorator(
157     permission_required("payments.process_batches"), name="dispatch",
158 )
159 class BatchTopicExportAdminView(View):
160     """
161     View that exports a batch per topic
162     """
163 
164     def post(self, request, *args, **kwargs):
165         batch = Batch.objects.get(pk=kwargs["pk"])
166 
167         response = HttpResponse(content_type="text/csv")
168         response["Content-Disposition"] = 'attachment;filename="batch-topic.csv"'
169         writer = csv.writer(response)
170         headers = [
171             _("Topic"),
172             _("No. of payments"),
173             _("First payment"),
174             _("Last payment"),
175             _("Total amount"),
176         ]
177         writer.writerow([capfirst(x) for x in headers])
178 
179         topic_rows = (
180             batch.payments_set.values("topic")
181             .annotate(
182                 total=Sum("amount"),
183                 count=Count("paid_by"),
184                 min_date=Min("created_at"),
185                 max_date=Max("created_at"),
186             )
187             .order_by("topic")
188         )
189 
190         for row in topic_rows:
191             writer.writerow(
192                 [
193                     row["topic"],
194                     row["count"],
195                     timezone.localtime(row["min_date"]).date(),
196                     timezone.localtime(row["max_date"]).date(),
197                     f"{row['total']:.2f}",
198                 ]
199             )
200         return response
201 
202 
203 @method_decorator(staff_member_required, name="dispatch")
204 @method_decorator(
205     permission_required("payments.process_batches"), name="dispatch",
206 )
207 class BatchTopicDescriptionAdminView(View):
208     """
209     Shows the topic export as plain text
210     """
211 
212     template_name = "admin/payments/batch_topic.html"
213 
214     def post(self, request, *args, **kwargs):
215         context = {}
216         batch = get_object_or_404(Batch, pk=kwargs["pk"])
217         topic_rows = (
218             batch.payments_set.values("topic")
219             .annotate(
220                 total=Sum("amount"),
221                 count=Count("paid_by"),
222                 min_date=Min("created_at"),
223                 max_date=Max("created_at"),
224             )
225             .order_by("topic")
226         )
227 
228         description = f"Batch {batch.id} - {batch.processing_date if batch.processing_date else timezone.now().date()}:\n"
229         for row in topic_rows:
230             description += f"- {row['topic']} ({row['count']}x) [{timezone.localtime(row['min_date']).date()} -- {timezone.localtime(row['max_date']).date()}], total €{row['total']:.2f}\n"
231         description += f"\n{batch.description}"
232 
233         context["batch"] = batch
234         context["description"] = description
235         return render(request, self.template_name, context)
236 
237 
238 @method_decorator(staff_member_required, name="dispatch")
239 @method_decorator(
240     permission_required("payments.process_batches"), name="dispatch",
241 )
242 class BatchNewFilledAdminView(View):
243     """
244     View that adds a new batch filled with all payments that where not already in a batch.
245     """
246 
247     def get(self, request, *args, **kwargs):
248         batch = Batch()
249         batch.save()
250 
251         payments = Payment.objects.filter(type=Payment.TPAY, batch=None,)
252 
253         payments.update(batch=batch)
254 
255         return redirect("admin:payments_batch_change", object_id=batch.id)
256
```
--- 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()

```

diff --git a/website/payments/admin_views.py b/website/payments/admin_views.py
--- a/website/payments/admin_views.py
+++ b/website/payments/admin_views.py
@@ -43,9 +43,7 @@
         payable_obj = payable_model.objects.get(pk=payable)
 
         result = services.create_payment(
-            payable_obj,
-            PaymentUser.objects.get(pk=self.request.member.pk),
-            request.POST["type"],
+            payable_obj, self.request.member, request.POST["type"],
         )
         payable_obj.save()
 
diff --git a/website/payments/widgets.py b/website/payments/widgets.py
--- a/website/payments/widgets.py
+++ b/website/payments/widgets.py
@@ -22,8 +22,8 @@
             context["obj"] = self.obj
             context["payable_payer"] = (
                 PaymentUser.objects.get(pk=self.obj.payment_payer.pk)
-                if hasattr(self.obj, "payment_payer")
-                else self.obj.paid_by
+                if getattr(self.obj, "payment_payer", None) is not None
+                else None
             )
             context["content_type"] = ContentType.objects.get_for_model(self.obj)
         elif value:

{"golden_diff": "diff --git a/website/payments/admin_views.py b/website/payments/admin_views.py\n--- a/website/payments/admin_views.py\n+++ b/website/payments/admin_views.py\n@@ -43,9 +43,7 @@\n         payable_obj = payable_model.objects.get(pk=payable)\n \n         result = services.create_payment(\n-            payable_obj,\n-            PaymentUser.objects.get(pk=self.request.member.pk),\n-            request.POST[\"type\"],\n+            payable_obj, self.request.member, request.POST[\"type\"],\n         )\n         payable_obj.save()\n \ndiff --git a/website/payments/widgets.py b/website/payments/widgets.py\n--- a/website/payments/widgets.py\n+++ b/website/payments/widgets.py\n@@ -22,8 +22,8 @@\n             context[\"obj\"] = self.obj\n             context[\"payable_payer\"] = (\n                 PaymentUser.objects.get(pk=self.obj.payment_payer.pk)\n-                if hasattr(self.obj, \"payment_payer\")\n-                else self.obj.paid_by\n+                if getattr(self.obj, \"payment_payer\", None) is not None\n+                else None\n             )\n             context[\"content_type\"] = ContentType.objects.get_for_model(self.obj)\n         elif value:\n", "issue": "Admin PaymentWidget can crash if payable.payment_payer returns None\n### Describe the bug\r\nAfter #1320, the PaymentWidget (`payments/widgets.py`) can crash if the `payment_payer` is `None`, for example for `registrations`. \r\n\r\n### How to reproduce\r\nSteps to reproduce the behaviour:\r\n1. Make a registration\r\n2. Accept it (so the payment widget will appear)\r\n3. Crash\r\n\r\n### Expected behaviour\r\nNot crash (we can just feed `None` to the html template)\r\n\nAdmin PaymentWidget can crash if payable.payment_payer returns None\n### Describe the bug\r\nAfter #1320, the PaymentWidget (`payments/widgets.py`) can crash if the `payment_payer` is `None`, for example for `registrations`. \r\n\r\n### How to reproduce\r\nSteps to reproduce the behaviour:\r\n1. Make a registration\r\n2. Accept it (so the payment widget will appear)\r\n3. Crash\r\n\r\n### Expected behaviour\r\nNot crash (we can just feed `None` to the html template)\r\n\n", "before_files": [{"content": "\"\"\"Widgets provided by the payments package\"\"\"\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.forms import Widget\n\nfrom payments.models import Payment, PaymentUser\n\n\nclass PaymentWidget(Widget):\n    \"\"\"\n    Custom widget for the Payment object, used in registrations\n    \"\"\"\n\n    template_name = \"payments/widgets/payment.html\"\n\n    def __init__(self, attrs=None, obj=None):\n        super().__init__(attrs)\n        self.obj = obj\n\n    def get_context(self, name, value, attrs) -> dict:\n        context = super().get_context(name, value, attrs)\n        if self.obj and not value:\n            context[\"obj\"] = self.obj\n            context[\"payable_payer\"] = (\n                PaymentUser.objects.get(pk=self.obj.payment_payer.pk)\n                if hasattr(self.obj, \"payment_payer\")\n                else self.obj.paid_by\n            )\n            context[\"content_type\"] = ContentType.objects.get_for_model(self.obj)\n        elif value:\n            payment = Payment.objects.get(pk=value)\n            context[\"url\"] = payment.get_admin_url()\n            context[\"payment\"] = payment\n        return context\n\n    class Media:\n        js = (\"admin/payments/js/payments.js\",)\n\n\nclass SignatureWidget(Widget):\n    \"\"\"\n    Widget for signature image\n    \"\"\"\n\n    template_name = \"payments/widgets/signature.html\"\n\n    class Media:\n        js = (\n            \"payments/js/signature_pad.min.js\",\n            \"payments/js/main.js\",\n        )\n        css = {\"all\": (\"admin/payments/css/signature.css\",)}\n", "path": "website/payments/widgets.py"}, {"content": "\"\"\"Admin views provided by the payments package\"\"\"\nimport csv\n\nfrom django.apps import apps\nfrom django.contrib import messages\nfrom django.contrib.admin.utils import model_ngettext\nfrom django.contrib.admin.views.decorators import staff_member_required\nfrom django.contrib.auth.decorators import permission_required\nfrom django.db.models import Sum, Count, Min, Max\nfrom django.http import HttpResponse\nfrom django.core.exceptions import SuspiciousOperation, DisallowedRedirect\nfrom django.shortcuts import redirect, get_object_or_404, render\nfrom django.utils import timezone\nfrom django.utils.text import capfirst\nfrom django.utils.decorators import method_decorator\nfrom django.utils.http import url_has_allowed_host_and_scheme\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views import View\n\nfrom payments import services\nfrom .models import Payment, Batch, PaymentUser\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_payments\"), name=\"dispatch\",\n)\nclass PaymentAdminView(View):\n    \"\"\"\n    View that creates a payment\n    \"\"\"\n\n    def post(self, request, *args, app_label, model_name, payable, **kwargs):\n        if \"type\" not in request.POST:\n            raise SuspiciousOperation(\"Missing POST parameters\")\n\n        if \"next\" in request.POST and not url_has_allowed_host_and_scheme(\n            request.POST.get(\"next\"), allowed_hosts={request.get_host()}\n        ):\n            raise DisallowedRedirect\n\n        payable_model = apps.get_model(app_label=app_label, model_name=model_name)\n        payable_obj = payable_model.objects.get(pk=payable)\n\n        result = services.create_payment(\n            payable_obj,\n            PaymentUser.objects.get(pk=self.request.member.pk),\n            request.POST[\"type\"],\n        )\n        payable_obj.save()\n\n        if result:\n            messages.success(\n                request, _(\"Successfully paid %s.\") % model_ngettext(payable_obj, 1),\n            )\n        else:\n            messages.error(\n                request, _(\"Could not pay %s.\") % model_ngettext(payable_obj, 1),\n            )\n            return redirect(f\"admin:{app_label}_{model_name}_change\", payable_obj.pk)\n\n        if \"next\" in request.POST:\n            return redirect(request.POST[\"next\"])\n\n        return redirect(\"admin:payments_payment_change\", result.pk)\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchProcessAdminView(View):\n    \"\"\"\n    View that processes a batch\n    \"\"\"\n\n    def post(self, request, *args, **kwargs):\n        batch = Batch.objects.get(pk=kwargs[\"pk\"])\n\n        if \"next\" in request.POST and not url_has_allowed_host_and_scheme(\n            request.POST.get(\"next\"), allowed_hosts={request.get_host()}\n        ):\n            raise DisallowedRedirect\n\n        if batch.processed:\n            messages.error(\n                request, _(\"{} already processed.\").format(model_ngettext(batch, 1))\n            )\n        else:\n            batch.processed = True\n            payments = batch.payments_set.select_related(\"paid_by\")\n            for payment in payments:\n                bank_account = payment.paid_by.bank_accounts.last()\n                bank_account.last_used = timezone.now()\n                bank_account.save()\n\n            batch.save()\n\n            services.send_tpay_batch_processing_emails(batch)\n\n            messages.success(\n                request,\n                _(\"Successfully processed {}.\").format(model_ngettext(batch, 1)),\n            )\n\n        if \"next\" in request.POST:\n            return redirect(request.POST[\"next\"])\n\n        return redirect(\"admin:payments_batch_change\", kwargs[\"pk\"])\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchExportAdminView(View):\n    \"\"\"\n    View that exports a batch\n    \"\"\"\n\n    def post(self, request, *args, **kwargs):\n        batch = Batch.objects.get(pk=kwargs[\"pk\"])\n\n        response = HttpResponse(content_type=\"text/csv\")\n        response[\"Content-Disposition\"] = 'attachment;filename=\"batch.csv\"'\n        writer = csv.writer(response)\n        headers = [\n            _(\"Account holder\"),\n            _(\"IBAN\"),\n            _(\"Mandate Reference\"),\n            _(\"Amount\"),\n            _(\"Description\"),\n            _(\"Mandate Date\"),\n        ]\n        writer.writerow([capfirst(x) for x in headers])\n\n        member_rows = batch.payments_set.values(\"paid_by\").annotate(total=Sum(\"amount\"))\n\n        for row in member_rows:\n            member = PaymentUser.objects.get(id=row[\"paid_by\"])\n            bankaccount = member.bank_accounts.last()\n            writer.writerow(\n                [\n                    member.get_full_name(),\n                    bankaccount.iban,\n                    bankaccount.mandate_no,\n                    f\"{row['total']:.2f}\",\n                    batch.description,\n                    bankaccount.valid_from,\n                ]\n            )\n        return response\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchTopicExportAdminView(View):\n    \"\"\"\n    View that exports a batch per topic\n    \"\"\"\n\n    def post(self, request, *args, **kwargs):\n        batch = Batch.objects.get(pk=kwargs[\"pk\"])\n\n        response = HttpResponse(content_type=\"text/csv\")\n        response[\"Content-Disposition\"] = 'attachment;filename=\"batch-topic.csv\"'\n        writer = csv.writer(response)\n        headers = [\n            _(\"Topic\"),\n            _(\"No. of payments\"),\n            _(\"First payment\"),\n            _(\"Last payment\"),\n            _(\"Total amount\"),\n        ]\n        writer.writerow([capfirst(x) for x in headers])\n\n        topic_rows = (\n            batch.payments_set.values(\"topic\")\n            .annotate(\n                total=Sum(\"amount\"),\n                count=Count(\"paid_by\"),\n                min_date=Min(\"created_at\"),\n                max_date=Max(\"created_at\"),\n            )\n            .order_by(\"topic\")\n        )\n\n        for row in topic_rows:\n            writer.writerow(\n                [\n                    row[\"topic\"],\n                    row[\"count\"],\n                    timezone.localtime(row[\"min_date\"]).date(),\n                    timezone.localtime(row[\"max_date\"]).date(),\n                    f\"{row['total']:.2f}\",\n                ]\n            )\n        return response\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchTopicDescriptionAdminView(View):\n    \"\"\"\n    Shows the topic export as plain text\n    \"\"\"\n\n    template_name = \"admin/payments/batch_topic.html\"\n\n    def post(self, request, *args, **kwargs):\n        context = {}\n        batch = get_object_or_404(Batch, pk=kwargs[\"pk\"])\n        topic_rows = (\n            batch.payments_set.values(\"topic\")\n            .annotate(\n                total=Sum(\"amount\"),\n                count=Count(\"paid_by\"),\n                min_date=Min(\"created_at\"),\n                max_date=Max(\"created_at\"),\n            )\n            .order_by(\"topic\")\n        )\n\n        description = f\"Batch {batch.id} - {batch.processing_date if batch.processing_date else timezone.now().date()}:\\n\"\n        for row in topic_rows:\n            description += f\"- {row['topic']} ({row['count']}x) [{timezone.localtime(row['min_date']).date()} -- {timezone.localtime(row['max_date']).date()}], total \u20ac{row['total']:.2f}\\n\"\n        description += f\"\\n{batch.description}\"\n\n        context[\"batch\"] = batch\n        context[\"description\"] = description\n        return render(request, self.template_name, context)\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchNewFilledAdminView(View):\n    \"\"\"\n    View that adds a new batch filled with all payments that where not already in a batch.\n    \"\"\"\n\n    def get(self, request, *args, **kwargs):\n        batch = Batch()\n        batch.save()\n\n        payments = Payment.objects.filter(type=Payment.TPAY, batch=None,)\n\n        payments.update(batch=batch)\n\n        return redirect(\"admin:payments_batch_change\", object_id=batch.id)\n", "path": "website/payments/admin_views.py"}], "after_files": [{"content": "\"\"\"Widgets provided by the payments package\"\"\"\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.forms import Widget\n\nfrom payments.models import Payment, PaymentUser\n\n\nclass PaymentWidget(Widget):\n    \"\"\"\n    Custom widget for the Payment object, used in registrations\n    \"\"\"\n\n    template_name = \"payments/widgets/payment.html\"\n\n    def __init__(self, attrs=None, obj=None):\n        super().__init__(attrs)\n        self.obj = obj\n\n    def get_context(self, name, value, attrs) -> dict:\n        context = super().get_context(name, value, attrs)\n        if self.obj and not value:\n            context[\"obj\"] = self.obj\n            context[\"payable_payer\"] = (\n                PaymentUser.objects.get(pk=self.obj.payment_payer.pk)\n                if getattr(self.obj, \"payment_payer\", None) is not None\n                else None\n            )\n            context[\"content_type\"] = ContentType.objects.get_for_model(self.obj)\n        elif value:\n            payment = Payment.objects.get(pk=value)\n            context[\"url\"] = payment.get_admin_url()\n            context[\"payment\"] = payment\n        return context\n\n    class Media:\n        js = (\"admin/payments/js/payments.js\",)\n\n\nclass SignatureWidget(Widget):\n    \"\"\"\n    Widget for signature image\n    \"\"\"\n\n    template_name = \"payments/widgets/signature.html\"\n\n    class Media:\n        js = (\n            \"payments/js/signature_pad.min.js\",\n            \"payments/js/main.js\",\n        )\n        css = {\"all\": (\"admin/payments/css/signature.css\",)}\n", "path": "website/payments/widgets.py"}, {"content": "\"\"\"Admin views provided by the payments package\"\"\"\nimport csv\n\nfrom django.apps import apps\nfrom django.contrib import messages\nfrom django.contrib.admin.utils import model_ngettext\nfrom django.contrib.admin.views.decorators import staff_member_required\nfrom django.contrib.auth.decorators import permission_required\nfrom django.db.models import Sum, Count, Min, Max\nfrom django.http import HttpResponse\nfrom django.core.exceptions import SuspiciousOperation, DisallowedRedirect\nfrom django.shortcuts import redirect, get_object_or_404, render\nfrom django.utils import timezone\nfrom django.utils.text import capfirst\nfrom django.utils.decorators import method_decorator\nfrom django.utils.http import url_has_allowed_host_and_scheme\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views import View\n\nfrom payments import services\nfrom .models import Payment, Batch, PaymentUser\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_payments\"), name=\"dispatch\",\n)\nclass PaymentAdminView(View):\n    \"\"\"\n    View that creates a payment\n    \"\"\"\n\n    def post(self, request, *args, app_label, model_name, payable, **kwargs):\n        if \"type\" not in request.POST:\n            raise SuspiciousOperation(\"Missing POST parameters\")\n\n        if \"next\" in request.POST and not url_has_allowed_host_and_scheme(\n            request.POST.get(\"next\"), allowed_hosts={request.get_host()}\n        ):\n            raise DisallowedRedirect\n\n        payable_model = apps.get_model(app_label=app_label, model_name=model_name)\n        payable_obj = payable_model.objects.get(pk=payable)\n\n        result = services.create_payment(\n            payable_obj, self.request.member, request.POST[\"type\"],\n        )\n        payable_obj.save()\n\n        if result:\n            messages.success(\n                request, _(\"Successfully paid %s.\") % model_ngettext(payable_obj, 1),\n            )\n        else:\n            messages.error(\n                request, _(\"Could not pay %s.\") % model_ngettext(payable_obj, 1),\n            )\n            return redirect(f\"admin:{app_label}_{model_name}_change\", payable_obj.pk)\n\n        if \"next\" in request.POST:\n            return redirect(request.POST[\"next\"])\n\n        return redirect(\"admin:payments_payment_change\", result.pk)\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchProcessAdminView(View):\n    \"\"\"\n    View that processes a batch\n    \"\"\"\n\n    def post(self, request, *args, **kwargs):\n        batch = Batch.objects.get(pk=kwargs[\"pk\"])\n\n        if \"next\" in request.POST and not url_has_allowed_host_and_scheme(\n            request.POST.get(\"next\"), allowed_hosts={request.get_host()}\n        ):\n            raise DisallowedRedirect\n\n        if batch.processed:\n            messages.error(\n                request, _(\"{} already processed.\").format(model_ngettext(batch, 1))\n            )\n        else:\n            batch.processed = True\n            payments = batch.payments_set.select_related(\"paid_by\")\n            for payment in payments:\n                bank_account = payment.paid_by.bank_accounts.last()\n                bank_account.last_used = timezone.now()\n                bank_account.save()\n\n            batch.save()\n\n            services.send_tpay_batch_processing_emails(batch)\n\n            messages.success(\n                request,\n                _(\"Successfully processed {}.\").format(model_ngettext(batch, 1)),\n            )\n\n        if \"next\" in request.POST:\n            return redirect(request.POST[\"next\"])\n\n        return redirect(\"admin:payments_batch_change\", kwargs[\"pk\"])\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchExportAdminView(View):\n    \"\"\"\n    View that exports a batch\n    \"\"\"\n\n    def post(self, request, *args, **kwargs):\n        batch = Batch.objects.get(pk=kwargs[\"pk\"])\n\n        response = HttpResponse(content_type=\"text/csv\")\n        response[\"Content-Disposition\"] = 'attachment;filename=\"batch.csv\"'\n        writer = csv.writer(response)\n        headers = [\n            _(\"Account holder\"),\n            _(\"IBAN\"),\n            _(\"Mandate Reference\"),\n            _(\"Amount\"),\n            _(\"Description\"),\n            _(\"Mandate Date\"),\n        ]\n        writer.writerow([capfirst(x) for x in headers])\n\n        member_rows = batch.payments_set.values(\"paid_by\").annotate(total=Sum(\"amount\"))\n\n        for row in member_rows:\n            member = PaymentUser.objects.get(id=row[\"paid_by\"])\n            bankaccount = member.bank_accounts.last()\n            writer.writerow(\n                [\n                    member.get_full_name(),\n                    bankaccount.iban,\n                    bankaccount.mandate_no,\n                    f\"{row['total']:.2f}\",\n                    batch.description,\n                    bankaccount.valid_from,\n                ]\n            )\n        return response\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchTopicExportAdminView(View):\n    \"\"\"\n    View that exports a batch per topic\n    \"\"\"\n\n    def post(self, request, *args, **kwargs):\n        batch = Batch.objects.get(pk=kwargs[\"pk\"])\n\n        response = HttpResponse(content_type=\"text/csv\")\n        response[\"Content-Disposition\"] = 'attachment;filename=\"batch-topic.csv\"'\n        writer = csv.writer(response)\n        headers = [\n            _(\"Topic\"),\n            _(\"No. of payments\"),\n            _(\"First payment\"),\n            _(\"Last payment\"),\n            _(\"Total amount\"),\n        ]\n        writer.writerow([capfirst(x) for x in headers])\n\n        topic_rows = (\n            batch.payments_set.values(\"topic\")\n            .annotate(\n                total=Sum(\"amount\"),\n                count=Count(\"paid_by\"),\n                min_date=Min(\"created_at\"),\n                max_date=Max(\"created_at\"),\n            )\n            .order_by(\"topic\")\n        )\n\n        for row in topic_rows:\n            writer.writerow(\n                [\n                    row[\"topic\"],\n                    row[\"count\"],\n                    timezone.localtime(row[\"min_date\"]).date(),\n                    timezone.localtime(row[\"max_date\"]).date(),\n                    f\"{row['total']:.2f}\",\n                ]\n            )\n        return response\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchTopicDescriptionAdminView(View):\n    \"\"\"\n    Shows the topic export as plain text\n    \"\"\"\n\n    template_name = \"admin/payments/batch_topic.html\"\n\n    def post(self, request, *args, **kwargs):\n        context = {}\n        batch = get_object_or_404(Batch, pk=kwargs[\"pk\"])\n        topic_rows = (\n            batch.payments_set.values(\"topic\")\n            .annotate(\n                total=Sum(\"amount\"),\n                count=Count(\"paid_by\"),\n                min_date=Min(\"created_at\"),\n                max_date=Max(\"created_at\"),\n            )\n            .order_by(\"topic\")\n        )\n\n        description = f\"Batch {batch.id} - {batch.processing_date if batch.processing_date else timezone.now().date()}:\\n\"\n        for row in topic_rows:\n            description += f\"- {row['topic']} ({row['count']}x) [{timezone.localtime(row['min_date']).date()} -- {timezone.localtime(row['max_date']).date()}], total \u20ac{row['total']:.2f}\\n\"\n        description += f\"\\n{batch.description}\"\n\n        context[\"batch\"] = batch\n        context[\"description\"] = description\n        return render(request, self.template_name, context)\n\n\n@method_decorator(staff_member_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"payments.process_batches\"), name=\"dispatch\",\n)\nclass BatchNewFilledAdminView(View):\n    \"\"\"\n    View that adds a new batch filled with all payments that where not already in a batch.\n    \"\"\"\n\n    def get(self, request, *args, **kwargs):\n        batch = Batch()\n        batch.save()\n\n        payments = Payment.objects.filter(type=Payment.TPAY, batch=None,)\n\n        payments.update(batch=batch)\n\n        return redirect(\"admin:payments_batch_change\", object_id=batch.id)\n", "path": "website/payments/admin_views.py"}]}