Verified Commit 330f2ced authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

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

parent ba5fa057
"""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