Coverage for website/payments/tests/test_admin.py: 99.41%
485 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1import datetime
2from decimal import Decimal
3from unittest import mock
4from unittest.mock import MagicMock, Mock, PropertyMock, patch
6from django.contrib import messages
7from django.contrib.admin import AdminSite
8from django.contrib.admin.utils import model_ngettext
9from django.contrib.auth.models import Permission
10from django.contrib.contenttypes.models import ContentType
11from django.core.exceptions import ObjectDoesNotExist, ValidationError
12from django.http import HttpRequest
13from django.test import (
14 Client,
15 RequestFactory,
16 SimpleTestCase,
17 TestCase,
18 override_settings,
19)
20from django.urls import reverse
21from django.utils import timezone
22from django.utils.translation import gettext_lazy as _
24from freezegun import freeze_time
26from events.models import Event, EventRegistration
27from members.models import Member, Profile
28from payments import admin, services
29from payments.admin import BankAccountInline, PaymentInline, ValidAccountFilter
30from payments.forms import BatchPaymentInlineAdminForm
31from payments.models import BankAccount, Batch, Payment, PaymentUser
34class GlobalAdminTest(SimpleTestCase):
35 @mock.patch("registrations.admin.RegistrationAdmin")
36 def test_show_message(self, admin_mock) -> None:
37 admin_mock.return_value = admin_mock
38 request = Mock(spec=HttpRequest)
40 admin._show_message(admin_mock, request, 0, "message", "error")
41 admin_mock.message_user.assert_called_once_with(
42 request, "error", messages.ERROR
43 )
44 admin_mock.message_user.reset_mock()
45 admin._show_message(admin_mock, request, 1, "message", "error")
46 admin_mock.message_user.assert_called_once_with(
47 request, "message", messages.SUCCESS
48 )
51@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True)
52@patch("payments.models.PaymentUser.tpay_allowed", PropertyMock, True)
53class PaymentAdminTest(TestCase):
54 fixtures = ["members.json", "bank_accounts.json"]
56 @classmethod
57 def setUpTestData(cls) -> None:
58 cls.user = PaymentUser.objects.get(pk=2)
60 def setUp(self) -> None:
61 self.client = Client()
62 self.client.force_login(self.user)
63 self.factory = RequestFactory()
64 self.site = AdminSite()
65 self.admin = admin.PaymentAdmin(Payment, admin_site=self.site)
67 self._give_user_permissions()
68 process_perm_batch = Permission.objects.get(
69 content_type__model="batch", codename="process_batches"
70 )
71 self.user.user_permissions.remove(process_perm_batch)
72 self.client.logout()
73 self.client.force_login(self.user)
75 def _give_user_permissions(self, batch_permissions=True) -> None:
76 """Helper to give the user permissions."""
77 content_type = ContentType.objects.get_for_model(Payment)
78 permissions_p = content_type.permission_set.all()
79 content_type = ContentType.objects.get_for_model(Batch)
80 permissions_b = content_type.permission_set.all()
81 for p in permissions_p:
82 self.user.user_permissions.add(p)
83 if batch_permissions:
84 for p in permissions_b:
85 self.user.user_permissions.add(p)
87 self.user.save()
89 self.client.logout()
90 self.client.force_login(self.user)
92 def test_paid_by_link(self) -> None:
93 """Tests that the right link for the paying user is returned."""
94 payment = Payment.objects.create(
95 amount=7.5, paid_by=self.user, processed_by=self.user, type=Payment.CASH
96 )
98 self.assertEqual(
99 self.admin.paid_by_link(payment),
100 f"<a href='/members/profile/{self.user.pk}'>Sébastiaan Versteeg</a>",
101 )
103 def test_processed_by_link(self) -> None:
104 """Tests that the right link for the processing user is returned."""
105 payment1 = Payment.objects.create(
106 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.CASH
107 )
109 self.assertEqual(
110 self.admin.processed_by_link(payment1),
111 f"<a href='/members/profile/{self.user.pk}'>Sébastiaan Versteeg</a>",
112 )
114 def test_delete_model_succeed(self) -> None:
115 batch = Batch.objects.create()
116 payment = Payment.objects.create(
117 amount=1,
118 paid_by=self.user,
119 processed_by=self.user,
120 type=Payment.TPAY,
121 batch=batch,
122 )
123 self.client.post(
124 reverse("admin:payments_payment_delete", args=(payment.id,)),
125 {"post": "yes"}, # Add data to confirm deletion in admin
126 )
127 self.assertFalse(Payment.objects.filter(id=payment.id).exists())
129 def test_delete_model_fail(self) -> None:
130 batch = Batch.objects.create()
131 payment = Payment.objects.create(
132 amount=1,
133 paid_by=self.user,
134 processed_by=self.user,
135 type=Payment.TPAY,
136 batch=batch,
137 )
138 batch.processed = True
139 batch.save()
140 response = self.client.post(
141 reverse("admin:payments_payment_delete", args=(payment.id,)),
142 {"post": "yes"}, # Add data to confirm deletion in admin
143 )
144 self.assertEqual(response.status_code, 403)
145 self.assertTrue(Payment.objects.filter(id=payment.id).exists())
147 def test_delete_action_fail(self) -> None:
148 batch = Batch.objects.create()
149 batch_proc = Batch.objects.create()
150 Payment.objects.create(
151 amount=1,
152 paid_by=self.user,
153 processed_by=self.user,
154 type=Payment.TPAY,
155 batch=batch,
156 )
157 payment2 = Payment.objects.create(
158 amount=1,
159 paid_by=self.user,
160 processed_by=self.user,
161 type=Payment.TPAY,
162 batch=batch_proc,
163 )
164 batch_proc.processed = True
165 batch_proc.save()
166 self.client.post(
167 reverse("admin:payments_payment_changelist"),
168 {
169 "action": "delete_selected",
170 "_selected_action": Payment.objects.values_list("id", flat=True),
171 "post": "yes",
172 },
173 )
174 self.assertTrue(Payment.objects.filter(id=payment2.id).exists())
176 def test_delete_action_success(self) -> None:
177 batch = Batch.objects.create()
178 payment1 = Payment.objects.create(
179 amount=1,
180 processed_by=self.user,
181 paid_by=self.user,
182 type=Payment.TPAY,
183 batch=batch,
184 )
185 payment2 = Payment.objects.create(
186 amount=1,
187 processed_by=self.user,
188 paid_by=self.user,
189 type=Payment.TPAY,
190 batch=batch,
191 )
192 self.client.post(
193 reverse("admin:payments_payment_changelist"),
194 {
195 "action": "delete_selected",
196 "_selected_action": Payment.objects.values_list("id", flat=True),
197 "post": "yes",
198 },
199 )
200 self.assertFalse(
201 Payment.objects.filter(id__in=[payment1.id, payment2.id]).exists()
202 )
204 def test_has_delete_permission_get(self) -> None:
205 payment = Payment.objects.create(
206 amount=10, paid_by=self.user, processed_by=self.user, type=Payment.CASH
207 )
208 request = self.factory.get(
209 reverse("admin:payments_payment_delete", args=(payment.id,))
210 )
211 request.user = self.user
212 self.admin.has_delete_permission(request)
213 self.assertTrue(Payment.objects.filter(id=payment.id).exists())
215 @freeze_time("2020-01-01")
216 def test_batch_link(self) -> None:
217 batch = Batch.objects.create(id=1)
218 payment1 = Payment.objects.create(
219 amount=7.5,
220 processed_by=self.user,
221 paid_by=self.user,
222 type=Payment.TPAY,
223 batch=batch,
224 )
225 payment2 = Payment.objects.create(
226 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.TPAY
227 )
228 payment3 = Payment.objects.create(
229 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.WIRE
230 )
231 self.assertEqual(
232 "<a href='/admin/payments/batch/1/change/'>Thalia Pay payments for 2020-1 (not processed)</a>",
233 str(self.admin.batch_link(payment1)),
234 )
235 self.assertEqual("No batch attached", self.admin.batch_link(payment2))
236 self.assertEqual("", self.admin.batch_link(payment3))
238 def test_add_to_new_batch(self) -> None:
239 p1 = Payment.objects.create(
240 amount=1, processed_by=self.user, paid_by=self.user, type=Payment.CARD
241 )
242 p2 = Payment.objects.create(
243 amount=2, processed_by=self.user, paid_by=self.user, type=Payment.CASH
244 )
245 p3 = Payment.objects.create(
246 amount=3, processed_by=self.user, paid_by=self.user, type=Payment.TPAY
247 )
249 change_url = reverse("admin:payments_payment_changelist")
251 self._give_user_permissions(batch_permissions=False)
252 self.client.post(
253 change_url,
254 {
255 "action": "add_to_new_batch",
256 "index": 1,
257 "_selected_action": [x.id for x in [p1, p2, p3]],
258 },
259 )
261 for p in Payment.objects.all():
262 self.assertIsNone(p.batch)
264 self._give_user_permissions()
265 self.client.post(
266 change_url,
267 {
268 "action": "add_to_new_batch",
269 "index": 1,
270 "_selected_action": [x.id for x in [p1, p2, p3]],
271 },
272 )
274 for p in Payment.objects.filter(id__in=[p1.id, p2.id]):
275 self.assertIsNone(p.batch)
277 self.assertIsNotNone(Payment.objects.get(id=p3.id).batch.id)
279 self._give_user_permissions()
280 self.client.post(
281 change_url,
282 {
283 "action": "add_to_new_batch",
284 "index": 1,
285 "_selected_action": [x.id for x in [p1, p2]],
286 },
287 )
289 def test_add_to_last_batch(self) -> None:
290 b = Batch.objects.create()
291 p1 = Payment.objects.create(
292 amount=1, processed_by=self.user, paid_by=self.user, type=Payment.CARD
293 )
294 p2 = Payment.objects.create(
295 amount=2, processed_by=self.user, paid_by=self.user, type=Payment.CASH
296 )
297 p3 = Payment.objects.create(
298 amount=3, processed_by=self.user, paid_by=self.user, type=Payment.TPAY
299 )
301 change_url = reverse("admin:payments_payment_changelist")
303 self._give_user_permissions(batch_permissions=False)
304 self.client.post(
305 change_url,
306 {
307 "action": "add_to_last_batch",
308 "index": 1,
309 "_selected_action": [x.id for x in [p1, p2, p3]],
310 },
311 )
313 for p in Payment.objects.all():
314 self.assertIsNone(p.batch)
316 self._give_user_permissions()
317 self.client.post(
318 change_url,
319 {
320 "action": "add_to_last_batch",
321 "index": 1,
322 "_selected_action": [x.id for x in [p1, p2, p3]],
323 },
324 )
326 for p in Payment.objects.filter(id__in=[p1.id, p2.id]):
327 self.assertIsNone(p.batch)
329 self.assertEqual(Payment.objects.get(id=p3.id).batch.id, b.id)
331 self.client.post(
332 change_url,
333 {
334 "action": "add_to_last_batch",
335 "index": 1,
336 "_selected_action": [x.id for x in [p1, p2]],
337 },
338 )
340 b.processed = True
341 b.save()
343 self.client.post(
344 change_url,
345 {
346 "action": "add_to_last_batch",
347 "index": 1,
348 "_selected_action": [p3.id],
349 },
350 )
352 def test_add_to_last_batch_no_batch(self):
353 p3 = Payment.objects.create(
354 amount=3, processed_by=self.user, paid_by=self.user, type=Payment.TPAY
355 )
357 change_url = reverse("admin:payments_payment_changelist")
359 self._give_user_permissions()
361 try:
362 self.client.post(
363 change_url,
364 {
365 "action": "add_to_last_batch",
366 "index": 1,
367 "_selected_action": [p3.id],
368 },
369 )
370 except ObjectDoesNotExist:
371 self.fail("Add to last batch should work without a batch")
373 def test_get_actions(self) -> None:
374 """Test that the actions are added to the admin."""
375 response = self.client.get(reverse("admin:payments_payment_changelist"))
377 actions = self.admin.get_actions(response.wsgi_request)
378 self.assertCountEqual(actions, ["delete_selected", "export_csv"])
380 self._give_user_permissions()
381 response = self.client.get(reverse("admin:payments_payment_changelist"))
383 actions = self.admin.get_actions(response.wsgi_request)
384 self.assertCountEqual(
385 actions,
386 [
387 "delete_selected",
388 "add_to_new_batch",
389 "add_to_last_batch",
390 "export_csv",
391 ],
392 )
394 def test_get_readonly_fields(self) -> None:
395 """Test that the custom urls are added to the admin."""
396 with self.subTest("No object"):
397 urls = self.admin.get_readonly_fields(HttpRequest(), None)
398 self.assertEqual(
399 urls, ("created_at", "processed_by", "payable_object", "batch")
400 )
402 with self.subTest("With object"):
403 urls = self.admin.get_readonly_fields(HttpRequest(), Payment())
404 self.assertEqual(
405 urls,
406 (
407 "created_at",
408 "processed_by",
409 "payable_object",
410 "amount",
411 "paid_by",
412 "type",
413 "topic",
414 "notes",
415 "batch",
416 ),
417 )
419 def test_get_urls(self) -> None:
420 """Test that the custom urls are added to the admin."""
421 urls = self.admin.get_urls()
422 self.assertEqual(urls[0].name, "payments_payment_create")
424 @freeze_time("2019-01-01")
425 def test_export_csv(self) -> None:
426 """Test that the CSV export of payments is correct."""
427 Payment.objects.create(
428 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.CARD
429 )
430 Payment.objects.create(
431 amount=17.5, processed_by=self.user, paid_by=self.user, type=Payment.CASH
432 )
434 response = self.admin.export_csv(HttpRequest(), Payment.objects.all())
436 self.assertEqual(
437 f"Created,Amount,Type,Processor,Payer id,Payer name,"
438 f"Notes\r\n2019-01-01 00:00:00+00:00,"
439 f"7.50,Card payment,Sébastiaan Versteeg,{self.user.pk},Sébastiaan Versteeg,"
440 f"\r\n2019-01-01 00:00:00+00:00,17.50,"
441 f"Cash payment,Sébastiaan Versteeg,{self.user.pk},Sébastiaan Versteeg,"
442 f"\r\n",
443 response.content.decode("utf-8"),
444 )
446 def test_get_field_queryset(self) -> None:
447 b1 = Batch.objects.create(id=1)
448 Batch.objects.create(id=2, processed=True)
449 p1 = Payment.objects.create(
450 amount=5, paid_by=self.user, processed_by=self.user, type=Payment.TPAY
451 )
452 response = self.client.get(
453 reverse("admin:payments_payment_change", args=(p1.id,))
454 )
455 self.assertCountEqual(
456 [
457 int(x.id)
458 for x in response.context_data["adminform"]
459 .form.fields["batch"]
460 .choices.queryset
461 ],
462 [b1.id],
463 )
465 self.client.get(reverse("admin:payments_payment_add"))
467 def test_payable_model_filter(self):
468 # Payment with no payable object.
469 p1 = Payment.objects.create(
470 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.CARD
471 )
473 event = Event.objects.create(
474 title="testevent",
475 description="desc",
476 start=timezone.now(),
477 end=(timezone.now() + datetime.timedelta(hours=1)),
478 location="test location",
479 map_location="test map location",
480 price=1.00,
481 fine=0.00,
482 )
484 self.user.is_superuser = True
485 self.user.save()
487 registration = EventRegistration.objects.create(event=event, member=self.user)
488 services.create_payment(registration, self.user, Payment.WIRE)
489 registration.refresh_from_db()
490 p2 = registration.payment
492 with self.subTest("EventRegistration"):
493 response = self.client.get(
494 reverse("admin:payments_payment_changelist"),
495 {"payable_model": "events_registration"},
496 )
497 self.assertNotContains(response, f"/admin/payments/payment/{p1.id}/change/")
498 self.assertContains(response, f"/admin/payments/payment/{p2.id}/change/")
500 with self.subTest("None"):
501 response = self.client.get(
502 reverse("admin:payments_payment_changelist"),
503 {"payable_model": "none"},
504 )
505 self.assertContains(response, f"/admin/payments/payment/{p1.id}/change/")
506 self.assertNotContains(response, f"/admin/payments/payment/{p2.id}/change/")
508 with self.subTest("FoodOrder"):
509 response = self.client.get(
510 reverse("admin:payments_payment_changelist"),
511 {"payable_model": "food_order"},
512 )
513 self.assertNotContains(response, f"/admin/payments/payment/{p1.id}/change/")
514 self.assertNotContains(response, f"/admin/payments/payment/{p2.id}/change/")
516 with self.subTest("Invalid"):
517 response = self.client.get(
518 reverse("admin:payments_payment_changelist"),
519 {"payable_model": "foo_bar_baz"},
520 follow=1,
521 )
522 self.assertContains(response, f"/admin/payments/payment/{p1.id}/change/")
523 self.assertContains(response, f"/admin/payments/payment/{p2.id}/change/")
525 with self.subTest("Changeview payable object"):
526 response = self.client.get(
527 reverse("admin:payments_payment_change", args=(p2.id,)),
528 )
529 self.assertContains(
530 response, f"/admin/events/eventregistration/{registration.id}/change/"
531 )
534@freeze_time("2019-01-01")
535@override_settings(SUSPEND_SIGNALS=True)
536class ValidAccountFilterTest(TestCase):
537 @classmethod
538 def setUpTestData(cls) -> None:
539 cls.member = PaymentUser.objects.create(
540 username="test1",
541 first_name="Test1",
542 last_name="Example",
543 email="test1@example.org",
544 is_staff=True,
545 )
546 Profile.objects.create(user=cls.member)
548 cls.member = PaymentUser.objects.get(pk=cls.member.pk)
550 cls.no_mandate = BankAccount.objects.create(
551 owner=cls.member, initials="J", last_name="Test", iban="NL91ABNA0417164300"
552 )
553 cls.valid_mandate = BankAccount.objects.create(
554 owner=cls.member,
555 initials="J",
556 last_name="Test",
557 iban="NL91ABNA0417164300",
558 mandate_no="11-1",
559 valid_from=timezone.now().date() - timezone.timedelta(days=5),
560 signature="base64,png",
561 )
562 cls.invalid_mandate = BankAccount.objects.create(
563 owner=cls.member,
564 initials="J",
565 last_name="Test",
566 iban="NL91ABNA0417164300",
567 mandate_no="11-2",
568 valid_from=timezone.now().date() - timezone.timedelta(days=5),
569 valid_until=timezone.now().date(),
570 signature="base64,png",
571 )
573 def setUp(self) -> None:
574 self.site = AdminSite()
575 self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site)
577 def test_lookups(self) -> None:
578 """Tests that the right options are implemented for lookups."""
579 account_filter = ValidAccountFilter(
580 model=BankAccount, model_admin=self.admin, params={}, request=None
581 )
583 self.assertEqual(
584 (
585 ("valid", "Valid"),
586 ("invalid", "Invalid"),
587 ("none", "None"),
588 ),
589 account_filter.lookups(None, None),
590 )
592 def test_queryset(self) -> None:
593 """Tests that the right results are returned."""
594 for param, item in [
595 ("valid", self.valid_mandate),
596 ("invalid", self.invalid_mandate),
597 ("none", self.no_mandate),
598 ]:
599 with self.subTest(f"Status {param}"):
600 account_filter = ValidAccountFilter(
601 model=BankAccount,
602 model_admin=self.admin,
603 params={"active": [param]},
604 request=None,
605 )
607 result = account_filter.queryset(None, BankAccount.objects.all())
609 self.assertEqual(result.count(), 1)
610 self.assertEqual(result.first().pk, item.pk)
612 with self.subTest("No known param"):
613 account_filter = ValidAccountFilter(
614 model=BankAccount,
615 model_admin=self.admin,
616 params={"active": ["bla"]},
617 request=None,
618 )
620 result = account_filter.queryset(None, BankAccount.objects.all())
622 self.assertEqual(result.count(), 3)
625@freeze_time("2019-01-01")
626@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True)
627@patch("payments.models.PaymentUser.tpay_allowed", PropertyMock, True)
628class BatchAdminTest(TestCase):
629 fixtures = ["members.json", "bank_accounts.json"]
631 @classmethod
632 def setUpTestData(cls) -> None:
633 cls.user = PaymentUser.objects.get(pk=2)
635 def setUp(self) -> None:
636 self.client = Client()
637 self.client.force_login(self.user)
638 self.site = AdminSite()
639 self.admin = admin.BatchAdmin(Batch, admin_site=self.site)
640 self.rf = RequestFactory()
642 self.user.refresh_from_db()
644 self._give_user_permissions()
645 process_perm_batch = Permission.objects.get(
646 content_type__model="batch", codename="process_batches"
647 )
648 self.user.user_permissions.remove(process_perm_batch)
649 self.client.logout()
650 self.client.force_login(self.user)
652 def _give_user_permissions(self, batch_permissions=True) -> None:
653 """Helper to give the user permissions."""
654 content_type = ContentType.objects.get_for_model(Payment)
655 permissions_p = content_type.permission_set.all()
656 content_type = ContentType.objects.get_for_model(Batch)
657 permissions_b = content_type.permission_set.all()
658 for p in permissions_p:
659 self.user.user_permissions.add(p)
660 if batch_permissions: 660 ↛ 664line 660 didn't jump to line 664 because the condition on line 660 was always true
661 for p in permissions_b:
662 self.user.user_permissions.add(p)
664 self.user.save()
666 self.client.logout()
667 self.client.force_login(self.user)
669 def test_delete_model_succeed(self) -> None:
670 batch = Batch.objects.create()
671 self.client.post(
672 reverse("admin:payments_batch_delete", args=(batch.id,)),
673 {"post": "yes"}, # Add data to confirm deletion in admin
674 )
675 self.assertFalse(Batch.objects.filter(id=batch.id).exists())
677 def test_delete_model_fail(self) -> None:
678 batch = Batch.objects.create(processed=True)
679 response = self.client.post(
680 reverse("admin:payments_batch_delete", args=(batch.id,)),
681 {"post": "yes"},
682 )
683 self.assertEqual(response.status_code, 403)
684 self.assertTrue(Batch.objects.filter(id=batch.id).exists())
686 def test_delete_action_fail(self) -> None:
687 Batch.objects.create()
688 batch_proc = Batch.objects.create(processed=True)
689 self.client.post(
690 reverse("admin:payments_batch_changelist"),
691 {
692 "action": "delete_selected",
693 "_selected_action": Batch.objects.values_list("id", flat=True),
694 "post": "yes",
695 },
696 )
697 self.assertTrue(Batch.objects.filter(id=batch_proc.id).exists())
699 def test_delete_action_success(self) -> None:
700 batch = Batch.objects.create()
701 batch_proc = Batch.objects.create()
702 self.client.post(
703 reverse("admin:payments_batch_changelist"),
704 {
705 "action": "delete_selected",
706 "_selected_action": Batch.objects.values_list("id", flat=True),
707 "post": "yes",
708 },
709 )
710 self.assertFalse(
711 Batch.objects.filter(id__in=[batch_proc.id, batch.id]).exists()
712 )
714 def test_has_delete_permission_get(self) -> None:
715 batch = Batch.objects.create()
716 request = self.rf.get(reverse("admin:payments_batch_delete", args=(batch.id,)))
717 request.user = self.user
718 self.admin.has_delete_permission(request)
719 self.assertTrue(Batch.objects.filter(id=batch.id).exists())
721 def test_get_readonly_fields(self) -> None:
722 b = Batch.objects.create()
723 self.assertCountEqual(
724 self.admin.get_readonly_fields(None, b),
725 [
726 "id",
727 "processed",
728 "processing_date",
729 "total_amount",
730 ],
731 )
733 b.processed = True
734 b.save()
735 self.assertCountEqual(
736 self.admin.get_readonly_fields(None, b),
737 [
738 "id",
739 "description",
740 "processed",
741 "processing_date",
742 "total_amount",
743 "withdrawal_date",
744 ],
745 )
747 def test_save_formset(self) -> None:
748 batch = Batch.objects.create()
749 batch_processed = Batch.objects.create()
750 Payment.objects.create(
751 amount=1,
752 paid_by=self.user,
753 processed_by=self.user,
754 type=Payment.TPAY,
755 batch=batch,
756 )
757 Payment.objects.create(
758 amount=1,
759 paid_by=self.user,
760 processed_by=self.user,
761 type=Payment.TPAY,
762 batch=batch_processed,
763 )
764 batch_processed.processed = True
765 batch_processed.save()
767 formset = BatchPaymentInlineAdminForm()
768 formset.save = MagicMock(return_value=Payment.objects.all())
769 formset.save_m2m = MagicMock()
771 with self.assertRaises(ValidationError):
772 self.admin.save_formset(None, None, formset, None)
774 batch_processed.processed = False
775 batch_processed.save()
777 formset.save.return_value = Payment.objects.all()
778 self.admin.save_formset(None, None, formset, None)
780 @mock.patch("django.contrib.admin.ModelAdmin.changeform_view")
781 def test_change_form_view(self, changeform_view_mock) -> None:
782 self._give_user_permissions()
784 b = Batch.objects.create(processed=True)
785 request = self.rf.get(f"/admin/payments/batch/{b.id}/change/")
786 request.user = self.user
787 self.admin.changeform_view(request, b.id)
789 changeform_view_mock.assert_called_once_with(request, b.id, "", {"batch": b})
791 changeform_view_mock.reset_mock()
793 self.admin.changeform_view(request)
795 changeform_view_mock.assert_called_once_with(request, None, "", {"batch": None})
798@freeze_time("2019-01-01")
799@override_settings(SUSPEND_SIGNALS=True)
800class BankAccountAdminTest(TestCase):
801 @classmethod
802 def setUpTestData(cls) -> None:
803 cls.user = Member.objects.create(
804 username="test1",
805 first_name="Test1",
806 last_name="Example",
807 email="test1@example.org",
808 is_staff=True,
809 is_superuser=True,
810 )
811 Profile.objects.create(user=cls.user)
813 cls.user = PaymentUser.objects.get(pk=cls.user.pk)
815 def setUp(self) -> None:
816 self.site = AdminSite()
817 self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site)
818 self.rf = RequestFactory()
820 def test_owner_link(self) -> None:
821 """Test that the link to a member profile is correct."""
822 bank_account1 = BankAccount.objects.create(
823 owner=self.user, initials="J", last_name="Test", iban="NL91ABNA0417164300"
824 )
826 self.assertEqual(
827 self.admin.owner_link(bank_account1),
828 f"<a href='/admin/auth/user/{self.user.pk}/change/'>Test1 Example</a>",
829 )
831 bank_account2 = BankAccount.objects.create(
832 owner=None, initials="J2", last_name="Test", iban="NL91ABNA0417164300"
833 )
835 self.assertEqual(self.admin.owner_link(bank_account2), "")
837 def test_can_be_revoked(self) -> None:
838 """Test that the revocation value of a bank account is correct."""
839 bank_account1 = BankAccount.objects.create(
840 owner=self.user, initials="J", last_name="Test", iban="NL91ABNA0417164300"
841 )
843 with patch(
844 "payments.models.BankAccount.can_be_revoked", new_callable=PropertyMock
845 ) as mock:
846 mock.return_value = True
847 self.assertTrue(self.admin.can_be_revoked(bank_account1))
848 mock.return_value = False
849 self.assertFalse(
850 self.admin.can_be_revoked(bank_account1),
851 )
853 def test_export_csv(self) -> None:
854 """Test that the CSV export of accounts is correct."""
855 BankAccount.objects.create(
856 owner=self.user, initials="J", last_name="Test", iban="NL91ABNA0417164300"
857 )
858 BankAccount.objects.create(
859 owner=self.user, initials="J2", last_name="Test", iban="NL91ABNA0417164300"
860 )
861 BankAccount.objects.create(
862 owner=self.user,
863 initials="J3",
864 last_name="Test",
865 iban="NL91ABNA0417164300",
866 mandate_no="12-1",
867 valid_from=timezone.now().date() - timezone.timedelta(days=5),
868 valid_until=timezone.now().date(),
869 signature="sig",
870 )
871 BankAccount.objects.create(
872 owner=self.user,
873 initials="J4",
874 last_name="Test",
875 iban="DE12500105170648489890",
876 bic="NBBEBEBB",
877 mandate_no="11-1",
878 valid_from=timezone.now().date(),
879 valid_until=timezone.now().date() + timezone.timedelta(days=5),
880 signature="sig",
881 )
883 response = self.admin.export_csv(HttpRequest(), BankAccount.objects.all())
885 self.assertEqual(
886 b"Created,Name,Reference,IBAN,BIC,Valid from,Valid until,"
887 b"Signature\r\n2019-01-01 00:00:00+00:00,J Test,,"
888 b"NL91ABNA0417164300,,,,\r\n2019-01-01 00:00:00+00:00,J2 Test,,"
889 b"NL91ABNA0417164300,,,,\r\n2019-01-01 00:00:00+00:00,J3 Test,"
890 b"12-1,NL91ABNA0417164300,,2018-12-27,2019-01-01,"
891 b"sig\r\n2019-01-01 00:00:00+00:00,J4 Test,11-1,"
892 b"DE12500105170648489890,NBBEBEBB,2019-01-01,2019-01-06,sig\r\n",
893 response.content,
894 )
896 @mock.patch("django.contrib.admin.ModelAdmin.message_user")
897 @mock.patch("payments.services.update_last_used")
898 def test_set_last_used(self, update_last_used, message_user) -> None:
899 """Tests that the last used value is updated."""
900 update_last_used.return_value = 1
902 request_noperms = self.rf.post(
903 "/", {"action": "set_last_used", "index": 1, "_selected_action": ["bla"]}
904 )
905 request_noperms.user = MagicMock()
906 request_noperms.user.has_perm = lambda _: False
908 request_hasperms = self.rf.post(
909 "/", {"action": "set_last_used", "index": 1, "_selected_action": ["bla"]}
910 )
911 request_hasperms.user = MagicMock()
912 request_hasperms.user.has_perm = lambda _: True
914 update_last_used.reset_mock()
915 message_user.reset_mock()
917 queryset_mock = MagicMock()
919 self.admin.set_last_used(request_noperms, queryset_mock)
920 update_last_used.assert_not_called()
922 self.admin.set_last_used(request_hasperms, queryset_mock)
923 update_last_used.assert_called_once_with(queryset_mock)
925 message_user.assert_called_once_with(
926 request_hasperms,
927 _("Successfully updated %(count)d %(items)s.")
928 % {"count": 1, "items": model_ngettext(BankAccount(), 1)},
929 messages.SUCCESS,
930 )
933@freeze_time("2019-01-01")
934@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True)
935class PaymentUserAdminTest(TestCase):
936 fixtures = ["members.json", "bank_accounts.json"]
938 @classmethod
939 def setUpTestData(cls) -> None:
940 cls.user = Member.objects.get(pk=2)
942 def setUp(self) -> None:
943 self.client = Client()
944 self.client.force_login(self.user)
945 self.site = AdminSite()
946 self.admin = admin.PaymentUserAdmin(PaymentUser, admin_site=self.site)
947 self.rf = RequestFactory()
948 self.user = PaymentUser.objects.first()
950 def test_has_add_permissions(self):
951 request = self.rf.get(reverse("admin:payments_paymentuser_add"))
952 request.user = self.user
953 self.assertFalse(self.admin.has_add_permission(request))
955 def test_has_delete_permissions(self):
956 request = self.rf.get(
957 reverse("admin:payments_paymentuser_delete", args=[self.user.pk])
958 )
959 request.user = self.user
960 self.assertFalse(self.admin.has_delete_permission(request))
962 @mock.patch("payments.models.PaymentUser.tpay_balance", new_callable=PropertyMock)
963 @mock.patch("payments.models.PaymentUser.tpay_enabled", new_callable=PropertyMock)
964 def test_get_tpay_balance(self, tpay_enabled, tpay_balance):
965 tpay_balance.return_value = Decimal(-10)
966 tpay_enabled.return_value = True
967 self.assertEqual(self.admin.get_tpay_balance(self.user), "€ -10.00")
969 @mock.patch("payments.models.PaymentUser.tpay_enabled", new_callable=PropertyMock)
970 def test_get_tpay_enabled(self, tpay_enabled):
971 tpay_enabled.return_value = True
972 self.assertTrue(self.admin.get_tpay_enabled(self.user))
974 @mock.patch("payments.models.PaymentUser.tpay_allowed", new_callable=PropertyMock)
975 def test_get_tpay_allowed(self, tpay_allowed):
976 tpay_allowed.return_value = True
977 self.assertTrue(self.admin.get_tpay_allowed(self.user))
979 def test_get_queryset(self):
980 self.assertQuerySetEqual(
981 self.admin.get_queryset(None).all(),
982 PaymentUser.objects.all(),
983 ordered=False,
984 )
986 def test_tpay_allowed_filter(self):
987 filter_all = admin.ThaliaPayAllowedFilter(
988 None, {}, PaymentUser, admin.PaymentUserAdmin
989 )
990 self.assertQuerySetEqual(
991 filter_all.queryset(
992 None, PaymentUser.objects.select_properties("tpay_allowed")
993 ),
994 PaymentUser.objects.select_properties("tpay_allowed"),
995 ordered=False,
996 )
998 filter_true = admin.ThaliaPayAllowedFilter(
999 None, {"tpay_allowed": "1"}, PaymentUser, admin.PaymentUserAdmin
1000 )
1001 self.assertQuerySetEqual(
1002 filter_true.queryset(
1003 None, PaymentUser.objects.select_properties("tpay_allowed")
1004 )
1005 .values_list("pk", flat=True)
1006 .all(),
1007 [1, 2, 3, 4, 5],
1008 ordered=False,
1009 )
1010 filter_false = admin.ThaliaPayAllowedFilter(
1011 None, {"tpay_allowed": "0"}, PaymentUser, admin.PaymentUserAdmin
1012 )
1013 self.assertQuerySetEqual(
1014 filter_false.queryset(
1015 None, PaymentUser.objects.select_properties("tpay_allowed")
1016 )
1017 .values_list("pk", flat=True)
1018 .all(),
1019 [],
1020 ordered=False,
1021 )
1023 def test_tpay_enabled_filter(self):
1024 filter_all = admin.ThaliaPayEnabledFilter(
1025 None, {}, PaymentUser, admin.PaymentUserAdmin
1026 )
1027 self.assertEqual(
1028 filter_all.queryset(None, PaymentUser.objects), PaymentUser.objects
1029 )
1031 filter_true = admin.ThaliaPayEnabledFilter(
1032 None, {"tpay_enabled": "1"}, PaymentUser, admin.PaymentUserAdmin
1033 )
1034 self.assertQuerySetEqual(
1035 filter_true.queryset(None, PaymentUser.objects)
1036 .values_list("pk", flat=True)
1037 .all(),
1038 [2, 1],
1039 ordered=False,
1040 )
1041 filter_false = admin.ThaliaPayEnabledFilter(
1042 None, {"tpay_enabled": "0"}, PaymentUser, admin.PaymentUserAdmin
1043 )
1044 self.assertQuerySetEqual(
1045 filter_false.queryset(None, PaymentUser.objects)
1046 .values_list("pk", flat=True)
1047 .all(),
1048 [3, 4, 5],
1049 ordered=False,
1050 )
1052 def test_tpay_balance_filter(self):
1053 filter_all = admin.ThaliaPayBalanceFilter(
1054 None, {}, PaymentUser, admin.PaymentUserAdmin
1055 )
1056 self.assertQuerySetEqual(
1057 filter_all.queryset(
1058 None, PaymentUser.objects.select_properties("tpay_balance")
1059 ),
1060 PaymentUser.objects.select_properties("tpay_balance"),
1061 ordered=False,
1062 )
1064 filter_true = admin.ThaliaPayBalanceFilter(
1065 None, {"tpay_balance": "0"}, PaymentUser, admin.PaymentUserAdmin
1066 )
1067 self.assertQuerySetEqual(
1068 filter_true.queryset(
1069 None, PaymentUser.objects.select_properties("tpay_balance")
1070 )
1071 .values_list("pk", flat=True)
1072 .all(),
1073 [1, 2, 3, 4, 5],
1074 ordered=False,
1075 )
1076 filter_false = admin.ThaliaPayBalanceFilter(
1077 None, {"tpay_balance": "1"}, PaymentUser, admin.PaymentUserAdmin
1078 )
1079 self.assertQuerySetEqual(
1080 filter_false.queryset(
1081 None, PaymentUser.objects.select_properties("tpay_balance")
1082 )
1083 .values_list("pk", flat=True)
1084 .all(),
1085 [],
1086 ordered=False,
1087 )
1088 filter_true = admin.ThaliaPayBalanceFilter(
1089 None, {"tpay_balance": "0"}, PaymentUser, admin.PaymentUserAdmin
1090 )
1091 self.assertQuerySetEqual(
1092 filter_true.queryset(
1093 None, PaymentUser.objects.select_properties("tpay_balance")
1094 )
1095 .values_list("pk", flat=True)
1096 .all(),
1097 [1, 2, 3, 4, 5],
1098 ordered=False,
1099 )
1100 filter_false = admin.ThaliaPayBalanceFilter(
1101 None, {"tpay_balance": "1"}, PaymentUser, admin.PaymentUserAdmin
1102 )
1103 self.assertQuerySetEqual(
1104 filter_false.queryset(
1105 None, PaymentUser.objects.select_properties("tpay_balance")
1106 )
1107 .values_list("pk", flat=True)
1108 .all(),
1109 [],
1110 ordered=False,
1111 )
1113 def test_user_link(self):
1114 self.assertEqual(
1115 self.admin.user_link(self.user),
1116 f"<a href='/admin/auth/user/{self.user.pk}/change/'>{self.user.get_full_name()}</a>",
1117 )
1119 def test_bankaccount_inline_permissions(self):
1120 request = self.rf.get(reverse("admin:payments_paymentuser_add"))
1121 request.user = self.user
1122 self.assertFalse(
1123 BankAccountInline(BankAccount, self.admin.admin_site).has_add_permission(
1124 request
1125 )
1126 )
1127 self.assertFalse(
1128 BankAccountInline(BankAccount, self.admin.admin_site).has_change_permission(
1129 request
1130 )
1131 )
1132 self.assertFalse(
1133 BankAccountInline(BankAccount, self.admin.admin_site).has_delete_permission(
1134 request
1135 )
1136 )
1138 def test_payment_inline_permissions(self):
1139 request = self.rf.get(reverse("admin:payments_paymentuser_add"))
1140 request.user = self.user
1141 self.assertFalse(
1142 PaymentInline(Payment, self.admin.admin_site).has_add_permission(request)
1143 )
1144 self.assertFalse(
1145 PaymentInline(Payment, self.admin.admin_site).has_change_permission(request)
1146 )
1147 self.assertFalse(
1148 PaymentInline(Payment, self.admin.admin_site).has_delete_permission(request)
1149 )
1151 def test_disallow_tpay_action(self):
1152 request = self.rf.get(reverse("admin:payments_paymentuser_changelist"))
1153 request.user = self.user
1154 with patch("payments.models.PaymentUser.disallow_tpay") as mock:
1155 request._messages = Mock()
1156 self.admin.disallow_thalia_pay(request, PaymentUser.objects.all())
1157 mock.assert_called()
1159 def test_allow_tpay_action(self):
1160 request = self.rf.get(reverse("admin:payments_paymentuser_changelist"))
1161 request.user = self.user
1162 with patch("payments.models.PaymentUser.allow_tpay") as mock:
1163 request._messages = Mock()
1164 self.admin.allow_thalia_pay(request, PaymentUser.objects.all())
1165 mock.assert_called()
1167 def test_paymentuser_two_bankaccounts(self):
1168 p = PaymentUser.objects.get(pk=self.user.pk)
1169 self.user.is_superuser = True
1170 self.user.save()
1171 self.client = Client()
1172 self.client.force_login(self.user)
1173 response = self.client.get(f"/admin/payments/paymentuser/{p.pk}/change/")
1174 self.assertEqual(response.status_code, 200)
1175 b1 = BankAccount.objects.create(
1176 owner=p,
1177 initials="J",
1178 last_name="Test2",
1179 iban="NL91ABNA0417164300",
1180 mandate_no="test1",
1181 valid_from=timezone.now().date() - timezone.timedelta(days=5),
1182 valid_until=timezone.now().date() - timezone.timedelta(days=3),
1183 last_used=timezone.now().date() - timezone.timedelta(days=4),
1184 signature="base64,png",
1185 )
1186 b2 = BankAccount.objects.create(
1187 owner=p,
1188 initials="J",
1189 last_name="Test2",
1190 iban="NL91ABNA0417164300",
1191 mandate_no="test2",
1192 valid_from=timezone.now().date() - timezone.timedelta(days=3),
1193 last_used=timezone.now().date() - timezone.timedelta(days=2),
1194 signature="base64,png",
1195 )
1196 response = self.client.get(f"/admin/payments/paymentuser/{p.pk}/change/")
1197 self.assertEqual(response.status_code, 200)