Commit 6db3db12 authored by Luko van der Maas's avatar Luko van der Maas
Browse files

Merge branch 'feature/payments-admin' into 'master'

Add more functionality to payments admin (search, export, fields)

Closes #815

See merge request !1154
parents bf5056d1 330f2ced
"""Registers admin interfaces for the payments module"""
import csv
from django.contrib import admin, messages
from django.contrib.admin.utils import model_ngettext
from django.http import HttpResponse
from django.urls import path
from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
from payments import services, admin_views
......@@ -22,17 +27,39 @@ def _show_message(admin, request, n, message, error):
class PaymentAdmin(admin.ModelAdmin):
"""Manage the payments"""
list_display = ('created_at', 'amount', 'processing_date', 'type')
list_filter = ('type', 'amount',)
list_display = ('created_at', 'amount', 'processing_date', 'type',
'paid_by_link', 'processed_by_link', 'notes')
list_filter = ('type',)
list_select_related = ('paid_by', 'processed_by',)
date_hierarchy = 'created_at'
fields = ('created_at', 'amount', 'type', 'processing_date',
'paid_by', 'processed_by', 'notes')
readonly_fields = ('created_at', 'amount', 'type',
'processing_date', 'paid_by', 'processed_by',
'notes')
search_fields = ('notes', 'paid_by__username', 'paid_by__first_name',
'paid_by__last_name', 'processed_by__username',
'processed_by__first_name', 'processed_by__last_name',
'amount')
ordering = ('-created_at', 'processing_date')
autocomplete_fields = ('paid_by', 'processed_by')
actions = ['process_cash_selected', 'process_card_selected']
actions = ['process_cash_selected', 'process_card_selected',
'process_wire_selected', 'export_csv']
@staticmethod
def _member_link(member):
return format_html("<a href='{}'>{}</a>", member.get_absolute_url(),
member.get_full_name())
def paid_by_link(self, obj):
return self._member_link(obj.paid_by)
paid_by_link.admin_order_field = 'paid_by'
paid_by_link.short_description = 'paid_by'
def processed_by_link(self, obj):
return self._member_link(obj.processed_by)
processed_by_link.admin_order_field = 'processed_by'
processed_by_link.short_description = 'paid_by'
def changeform_view(self, request, object_id=None, form_url='',
extra_context=None):
......@@ -51,7 +78,7 @@ class PaymentAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if not obj:
return ('created_at', 'type', 'processing_date', 'processed_by')
return 'created_at', 'type', 'processing_date', 'processed_by'
return super().get_readonly_fields(request, obj)
def get_actions(self, request):
......@@ -61,6 +88,7 @@ class PaymentAdmin(admin.ModelAdmin):
if not request.user.has_perm('payments.process_payments'):
del(actions['process_cash_selected'])
del(actions['process_card_selected'])
del(actions['process_wire_selected'])
return actions
def process_cash_selected(self, request, queryset):
......@@ -83,6 +111,16 @@ class PaymentAdmin(admin.ModelAdmin):
process_card_selected.short_description = _(
'Process selected payments (card)')
def process_wire_selected(self, request, queryset):
"""Process the selected payment as wire"""
if request.user.has_perm('payments.process_payments'):
updated_payments = services.process_payment(
queryset, request.member, Payment.WIRE
)
self._process_feedback(request, updated_payments)
process_wire_selected.short_description = _(
'Process selected payments (wire)')
def _process_feedback(self, request, updated_payments):
"""Show a feedback message for the processing result"""
rows_updated = len(updated_payments)
......@@ -101,3 +139,26 @@ class PaymentAdmin(admin.ModelAdmin):
name='payments_payment_process'),
]
return custom_urls + urls
def export_csv(self, request, queryset):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment;\
filename="payments.csv"'
writer = csv.writer(response)
headers = [_('created'), _('processed'), _('amount'), _('type'),
_('processor'), _('payer id'), _('payer name'),
_('notes')]
writer.writerow([capfirst(x) for x in headers])
for payment in queryset:
writer.writerow([
payment.created_at,
payment.processing_date,
payment.amount,
payment.get_type_display(),
payment.processed_by.get_full_name(),
payment.paid_by.pk,
payment.paid_by.get_full_name(),
payment.notes
])
return response
export_csv.short_description = _('Export')
......@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-11-27 18:25+0100\n"
"PO-Revision-Date: 2018-11-27 18:26+0100\n"
"POT-Creation-Date: 2019-02-08 11:51+0100\n"
"PO-Revision-Date: 2019-02-08 11:52+0100\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: \n"
"Language: nl\n"
......@@ -26,6 +26,10 @@ msgstr "Verwerk geselecteerde betalingen (contant)"
msgid "Process selected payments (card)"
msgstr "Verwerk geselecteerde betalingen (pin)"
#: admin.py
msgid "Process selected payments (wire)"
msgstr "Verwerk geselecteerde betalingen (overboeking)"
#: admin.py tests/test_admin.py
#, python-format
msgid "Successfully processed %(count)d %(items)s."
......@@ -35,6 +39,42 @@ msgstr "%(count)d %(items)s succesvol verwerkt."
msgid "The selected payment(s) could not be processed."
msgstr "De geselecteerde betaling(en) konden niet worden verwerkt."
#: admin.py
msgid "created"
msgstr "aangemaakt"
#: admin.py
msgid "processed"
msgstr "verwerkt"
#: admin.py
msgid "amount"
msgstr "bedrag"
#: admin.py models.py
msgid "type"
msgstr "type"
#: admin.py
msgid "processor"
msgstr "verwerker"
#: admin.py
msgid "payer id"
msgstr "id betaler"
#: admin.py
msgid "payer name"
msgstr "naam betaler"
#: admin.py
msgid "notes"
msgstr "notities"
#: admin.py
msgid "Export"
msgstr "Export"
#: admin_views.py tests/test_admin_views.py
#, python-format
msgid "Successfully processed %s."
......@@ -66,8 +106,8 @@ msgid "Card payment"
msgstr "Pin betaling"
#: models.py
msgid "type"
msgstr "type"
msgid "Wire payment"
msgstr "Pin betaling"
#: models.py
msgid "processing date"
......@@ -93,6 +133,10 @@ msgstr "Verwerk (contant)"
msgid "Process (card payment)"
msgstr "Verwerk (pin)"
#: templates/admin/payments/change_form.html templates/payments/widget.html
msgid "Process (wire payment)"
msgstr "Verwerk (overboeking)"
#: templates/payments/widget.html
msgid "Unprocessed"
msgstr "Onverwerkt"
......
......@@ -105,6 +105,26 @@ class PaymentAdminTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['payment'], None)
def test_paid_by_link(self):
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5,
paid_by=self.user)
self.assertEqual(self.admin.paid_by_link(payment),
f"<a href='/members/profile/{self.user.pk}'>"
f"Test1 Example</a>")
def test_processed_by_link(self):
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5,
processed_by=self.user)
self.assertEqual(self.admin.processed_by_link(payment),
f"<a href='/members/profile/{self.user.pk}'>"
f"Test1 Example</a>")
@mock.patch('django.contrib.admin.ModelAdmin.message_user')
@mock.patch('payments.services.process_payment')
def test_process_cash(self, process_payment, message_user):
......@@ -185,12 +205,52 @@ class PaymentAdminTest(TestCase):
}, messages.SUCCESS
)
@mock.patch('django.contrib.admin.ModelAdmin.message_user')
@mock.patch('payments.services.process_payment')
def test_process_wire(self, process_payment, message_user):
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5)
queryset = Payment.objects.filter(pk=object_id)
process_payment.return_value = [payment]
change_url = reverse('admin:payments_payment_changelist')
request_noperms = self.client.post(
change_url,
{'action': 'process_wire_selected',
'index': 1,
'_selected_action': [object_id]}).wsgi_request
self._give_user_permissions()
request_hasperms = self.client.post(
change_url,
{'action': 'process_wire_selected',
'index': 1,
'_selected_action': [object_id]}).wsgi_request
process_payment.reset_mock()
message_user.reset_mock()
self.admin.process_wire_selected(request_noperms, queryset)
process_payment.assert_not_called()
self.admin.process_wire_selected(request_hasperms, queryset)
process_payment.assert_called_once_with(queryset,
self.user, Payment.WIRE)
message_user.assert_called_once_with(
request_hasperms,
_('Successfully processed %(count)d %(items)s.')
% {
"count": 1,
"items": model_ngettext(Payment(), 1)
}, messages.SUCCESS
)
def test_get_actions(self):
response = self.client.get(
reverse('admin:payments_payment_changelist'))
actions = self.admin.get_actions(response.wsgi_request)
self.assertCountEqual(actions, ['delete_selected'])
self.assertCountEqual(actions, ['delete_selected', 'export_csv'])
self._give_user_permissions()
response = self.client.get(
......@@ -199,7 +259,9 @@ class PaymentAdminTest(TestCase):
actions = self.admin.get_actions(response.wsgi_request)
self.assertCountEqual(actions, ['delete_selected',
'process_cash_selected',
'process_card_selected'])
'process_card_selected',
'process_wire_selected',
'export_csv'])
def test_get_urls(self):
urls = self.admin.get_urls()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment