Add user in front of default auth urls & add last_used

parent 8b06298d
payments.management.commands package
====================================
.. automodule:: payments.management.commands
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
payments.management.commands.revokeoldmandates module
-----------------------------------------------------
.. automodule:: payments.management.commands.revokeoldmandates
:members:
:undoc-members:
:show-inheritance:
payments.management package
===========================
.. automodule:: payments.management
:members:
:undoc-members:
:show-inheritance:
Subpackages
-----------
.. toctree::
payments.management.commands
......@@ -6,6 +6,13 @@ payments package
:undoc-members:
:show-inheritance:
Subpackages
-----------
.. toctree::
payments.management
Submodules
----------
......
......@@ -222,14 +222,16 @@ class ValidAccountFilter(admin.SimpleListFilter):
class BankAccountAdmin(admin.ModelAdmin):
"""Manage bank accounts"""
list_display = ('iban', 'initials', 'last_name',
'owner_link', 'valid_from', 'valid_until')
list_display = ('iban', 'owner_link', 'last_used',
'valid_from', 'valid_until')
list_filter = (ValidAccountFilter,)
fields = ('created_at', 'owner', 'iban', 'bic', 'initials', 'last_name',
'mandate_no', 'valid_from', 'valid_until', 'signature')
fields = ('created_at', 'last_used', 'owner', 'iban', 'bic', 'initials',
'last_name', 'mandate_no', 'valid_from', 'valid_until',
'signature')
readonly_fields = ('created_at',)
search_fields = ('owner__username', 'owner__first_name',
'owner__last_name', 'iban')
actions = ['set_last_used']
form = BankAccountAdminForm
def owner_link(self, obj: BankAccount) -> str:
......@@ -242,6 +244,18 @@ class BankAccountAdmin(admin.ModelAdmin):
owner_link.admin_order_field = 'owner'
owner_link.short_description = _('owner')
def set_last_used(self, request: HttpRequest, queryset: QuerySet) -> None:
"""Set the last used date of selected accounts"""
if request.user.has_perm('payments.change_bankaccount'):
updated = services.update_last_used(queryset)
_show_message(
self, request, updated,
message=_("Successfully updated %(count)d %(items)s."),
error=_('The selected account(s) could not be updated.')
)
set_last_used.short_description = _(
'Update the last used date')
def export_csv(self, request: HttpRequest,
queryset: QuerySet) -> HttpResponse:
response = HttpResponse(content_type='text/csv')
......
from django.core.management.base import BaseCommand
from payments import services
class Command(BaseCommand):
def handle(self, *args, **options):
services.revoke_old_mandates()
# Generated by Django 2.2 on 2019-04-27 19:41
from django.db import migrations, models
import django.db.models.deletion
import uuid
import django.utils.timezone
import localflavor.generic.models
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
......@@ -20,6 +20,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
('last_used', models.DateField(blank=True, null=True, verbose_name='last used')),
('initials', models.CharField(max_length=20, verbose_name='initials')),
('last_name', models.CharField(max_length=255, verbose_name='last name')),
('iban', localflavor.generic.models.IBANField(include_countries=('AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'SM'), max_length=34, use_nordea_extensions=False, verbose_name='IBAN')),
......
......@@ -107,6 +107,12 @@ class BankAccount(models.Model):
created_at = models.DateTimeField(_('created at'), default=timezone.now)
last_used = models.DateField(
verbose_name=_('last used'),
blank=True,
null=True,
)
owner = models.ForeignKey(
to='members.Member',
verbose_name=_('owner'),
......
"""The services defined by the payments package"""
from django.db.models import QuerySet
import datetime
from django.db.models import QuerySet, Q
from django.utils import timezone
from members.models import Member
from .models import Payment
from .models import Payment, BankAccount
def process_payment(queryset: QuerySet, processed_by: Member,
......@@ -32,3 +35,33 @@ def process_payment(queryset: QuerySet, processed_by: Member,
data.append(payment)
return data
def update_last_used(queryset: QuerySet,
date: datetime.date = None) -> int:
"""
Update the last used field of a BankAccount queryset
:param queryset: Queryset of BankAccounts
:param date: date to set last_used to
:return: number of affected rows
"""
if not date:
date = timezone.now().date()
result = (queryset
.filter((Q(valid_from__gte=timezone.now())
& Q(valid_until__lt=timezone.now()))
| Q(valid_until=None))
.update(last_used=date))
return result
def revoke_old_mandates() -> int:
"""
Revokes all mandates that have not been used for 36 months or more
:return: number of affected rows
"""
return BankAccount.objects.filter(
last_used__lte=(timezone.now() - timezone.timedelta(days=36*30))
).update(valid_until=timezone.now().date())
from unittest import mock
from django.test import TestCase
from freezegun import freeze_time
from payments.api.fields import PaymentTypeField
from payments.models import Payment
@freeze_time('2019-01-01')
class PaymentTypeFieldTest(TestCase):
"""
Test for the payment type field
"""
fixtures = ['members.json']
@mock.patch('rest_framework.serializers.ChoiceField.get_attribute')
def test_get_attribute(self, mock_super):
field = PaymentTypeField(choices=Payment.PAYMENT_TYPE)
obj = Payment()
obj.payment = False
self.assertEqual(field.get_attribute(obj), Payment.NONE)
obj.payment = True
field.get_attribute(obj)
mock_super.assert_called()
from unittest import mock
from unittest.mock import Mock
from unittest.mock import Mock, MagicMock
from django.contrib import messages
from django.contrib.admin import AdminSite
......@@ -40,6 +40,7 @@ class PaymentAdminTest(TestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.user = Member.objects.create(
pk=1,
username='test1',
first_name='Test1',
last_name='Example',
......@@ -300,7 +301,7 @@ class PaymentAdminTest(TestCase):
self.assertEqual(urls[0].name, 'payments_payment_process')
@freeze_time('2019-01-01')
def test_export_csv(self):
def test_export_csv(self) -> None:
"""
Test that the CSV export of payments is correct
"""
......@@ -324,13 +325,14 @@ class PaymentAdminTest(TestCase):
response = self.admin.export_csv(HttpRequest(), Payment.objects.all())
self.assertEqual(
b'Created,Processed,Amount,Type,Processor,Payer id,Payer name,'
b'Notes\r\n2019-01-01 00:00:00+00:00,2019-01-01 00:00:00+00:00,'
b'7.50,Card payment,Test1 Example,1,Test1 Example,\r\n2019-01-01 '
b'00:00:00+00:00,2019-01-01 00:00:00+00:00,17.50,Cash payment,'
b'Test1 Example,1,Test1 Example,\r\n2019-01-01 00:00:00+00:00,,'
b'9.00,No payment,-,-,-,This is a test\r\n',
response.content
f'Created,Processed,Amount,Type,Processor,Payer id,Payer name,'
f'Notes\r\n2019-01-01 00:00:00+00:00,2019-01-01 00:00:00+00:00,'
f'7.50,Card payment,Test1 Example,{self.user.pk},Test1 Example,'
f'\r\n2019-01-01 00:00:00+00:00,2019-01-01 00:00:00+00:00,17.50,'
f'Cash payment,Test1 Example,{self.user.pk},Test1 Example,'
f'\r\n2019-01-01 00:00:00+00:00,,9.00,No payment,-,-,-,This is a '
f'test\r\n',
response.content.decode('utf-8')
)
......@@ -377,7 +379,7 @@ class ValidAccountFilterTest(TestCase):
self.site = AdminSite()
self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site)
def test_lookups(self):
def test_lookups(self) -> None:
"""
Tests that the right options are implemented for lookups
"""
......@@ -394,7 +396,7 @@ class ValidAccountFilterTest(TestCase):
('none', 'None'),
), account_filter.lookups(None, None))
def test_queryset(self):
def test_queryset(self) -> None:
"""
Tests that the right results are returned
"""
......@@ -445,12 +447,14 @@ class BankAccountAdminTest(TestCase):
last_name='Example',
email='test1@example.org',
is_staff=True,
is_superuser=True
)
Profile.objects.create(user=cls.user)
def setUp(self) -> None:
self.site = AdminSite()
self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site)
self.rf = RequestFactory()
def test_owner_link(self) -> None:
"""
......@@ -476,7 +480,7 @@ class BankAccountAdminTest(TestCase):
self.assertEqual(self.admin.owner_link(bank_account2), '')
def test_export_csv(self):
def test_export_csv(self) -> None:
"""
Test that the CSV export of accounts is correct
"""
......@@ -527,3 +531,53 @@ class BankAccountAdminTest(TestCase):
b'DE12500105170648489890,NBBEBEBB,2019-01-01,2019-01-06,sig\r\n',
response.content
)
@mock.patch('django.contrib.admin.ModelAdmin.message_user')
@mock.patch('payments.services.update_last_used')
def test_set_last_used(self, update_last_used, message_user) -> None:
"""
Tests that the last used value is updated
"""
update_last_used.return_value = 1
request_noperms = self.rf.post(
'/',
{
'action': 'set_last_used',
'index': 1,
'_selected_action': ['bla']
}
)
request_noperms.user = MagicMock()
request_noperms.user.has_perm = lambda _: False
request_hasperms = self.rf.post(
'/',
{
'action': 'set_last_used',
'index': 1,
'_selected_action': ['bla']
}
)
request_hasperms.user = MagicMock()
request_hasperms.user.has_perm = lambda _: True
update_last_used.reset_mock()
message_user.reset_mock()
queryset_mock = MagicMock()
self.admin.set_last_used(request_noperms, queryset_mock)
update_last_used.assert_not_called()
self.admin.set_last_used(request_hasperms, queryset_mock)
update_last_used.assert_called_once_with(queryset_mock)
message_user.assert_called_once_with(
request_hasperms,
_('Successfully updated %(count)d %(items)s.')
% {
"count": 1,
"items": model_ngettext(BankAccount(), 1)
}, messages.SUCCESS
)
from unittest import mock
from django.test import TestCase
from freezegun import freeze_time
from payments.management.commands.revokeoldmandates import Command
@freeze_time('2019-01-01')
class RevokeOldMandatesCommandTest(TestCase):
"""
Test for the management command
"""
fixtures = ['members.json']
@mock.patch('payments.services.revoke_old_mandates')
def test_handle(self, revoke_old_mandates):
command = Command()
command.handle()
revoke_old_mandates.assert_called()
from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time
from members.models import Member
from payments import services
from payments.models import BankAccount
@freeze_time('2019-01-01')
class ServicesTest(TestCase):
"""
Test for the services
"""
fixtures = ['members.json']
@classmethod
def setUpTestData(cls):
cls.member = Member.objects.filter(last_name="Wiggers").first()
def test_update_last_used(self):
BankAccount.objects.create(
owner=self.member,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300',
mandate_no='11-1',
valid_from=timezone.now().date() - timezone.timedelta(days=2000),
valid_until=timezone.now().date() - timezone.timedelta(days=1500),
signature='base64,png'
)
BankAccount.objects.create(
owner=self.member,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300',
mandate_no='11-2',
valid_from=timezone.now().date() - timezone.timedelta(days=5),
last_used=timezone.now().date() - timezone.timedelta(days=5),
signature='base64,png'
)
self.assertEqual(
services.update_last_used(BankAccount.objects),
1
)
self.assertEqual(
BankAccount.objects.filter(mandate_no='11-2').first().last_used,
timezone.now().date()
)
self.assertEqual(
services.update_last_used(
BankAccount.objects,
timezone.datetime(year=2018, month=12, day=12)
),
1
)
self.assertEqual(
BankAccount.objects.filter(mandate_no='11-2').first().last_used,
timezone.datetime(year=2018, month=12, day=12).date()
)
def test_revoke_old_mandates(self):
BankAccount.objects.create(
owner=self.member,
initials='J',
last_name='Test1',
iban='NL91ABNA0417164300',
mandate_no='11-1',
valid_from=timezone.now().date() - timezone.timedelta(days=2000),
last_used=timezone.now().date() - timezone.timedelta(days=2000),
signature='base64,png'
)
BankAccount.objects.create(
owner=self.member,
initials='J',
last_name='Test2',
iban='NL91ABNA0417164300',
mandate_no='11-2',
valid_from=timezone.now().date() - timezone.timedelta(days=5),
last_used=timezone.now().date() - timezone.timedelta(days=5),
signature='base64,png'
)
self.assertEqual(
BankAccount.objects.filter(valid_until=None).count(),
2
)
services.revoke_old_mandates()
self.assertEqual(
BankAccount.objects.filter(valid_until=None).count(),
1
)
......@@ -112,7 +112,7 @@ urlpatterns = [ # pylint: disable=invalid-name
# Default login helpers
url(r'^login/$', LoginView.as_view(), {'authentication_form': AuthenticationForm},
name='login'),
url(r'^', include('django.contrib.auth.urls')),
url(r'^user/', include('django.contrib.auth.urls')),
url(r'^i18n/', include('django.conf.urls.i18n')),
# Sitemap
url(r'^sitemap\.xml$', sitemap, {'sitemaps': THALIA_SITEMAP},
......
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