Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
thalia
concrexit
Commits
9f55bb1a
Commit
9f55bb1a
authored
Nov 28, 2018
by
Sébastiaan Versteeg
Committed by
Luko van der Maas
Nov 28, 2018
Browse files
Payments package additions and changes
parent
0520c415
Changes
26
Hide whitespace changes
Inline
Side-by-side
docs/payments.rst
View file @
9f55bb1a
...
...
@@ -17,6 +17,14 @@ payments.admin module
:undoc-members:
:show-inheritance:
payments.admin\_views module
----------------------------
.. automodule:: payments.admin_views
:members:
:undoc-members:
:show-inheritance:
payments.apps module
--------------------
...
...
@@ -41,22 +49,6 @@ payments.services module
:undoc-members:
:show-inheritance:
payments.urls module
--------------------
.. automodule:: payments.urls
:members:
:undoc-members:
:show-inheritance:
payments.views module
---------------------
.. automodule:: payments.views
:members:
:undoc-members:
:show-inheritance:
payments.widgets module
-----------------------
...
...
website/payments/admin.py
View file @
9f55bb1a
"""Registers admin interfaces for the payments module"""
from
django.contrib
import
admin
,
messages
from
django.contrib.admin.utils
import
model_ngettext
from
django.urls
import
path
from
django.utils.translation
import
ugettext_lazy
as
_
from
payments
import
services
from
payments
import
services
,
admin_views
from
.models
import
Payment
...
...
@@ -21,14 +22,16 @@ def _show_message(admin, request, n, message, error):
class
PaymentAdmin
(
admin
.
ModelAdmin
):
"""Manage the payments"""
list_display
=
(
'created_at'
,
'amount'
,
'processed'
,
'processing_date'
,
'type'
)
list_filter
=
(
'processed'
,
'amount'
,)
list_display
=
(
'created_at'
,
'amount'
,
'processing_date'
,
'type'
)
list_filter
=
(
'type'
,
'amount'
,)
date_hierarchy
=
'created_at'
fields
=
(
'created_at'
,
'amount'
,
'type'
,
'processed'
,
'processing_date'
)
readonly_fields
=
(
'created_at'
,
'amount'
,
'processed'
,
'type'
,
'processing_date'
)
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'
)
ordering
=
(
'-created_at'
,
'processing_date'
)
autocomplete_fields
=
(
'paid_by'
,
'processed_by'
)
actions
=
[
'process_cash_selected'
,
'process_card_selected'
]
def
changeform_view
(
self
,
request
,
object_id
=
None
,
form_url
=
''
,
...
...
@@ -46,6 +49,11 @@ class PaymentAdmin(admin.ModelAdmin):
return
super
().
changeform_view
(
request
,
object_id
,
form_url
,
{
'payment'
:
obj
})
def
get_readonly_fields
(
self
,
request
,
obj
=
None
):
if
not
obj
:
return
(
'created_at'
,
'type'
,
'processing_date'
,
'processed_by'
)
return
super
().
get_readonly_fields
(
request
,
obj
)
def
get_actions
(
self
,
request
):
"""Get the actions for the payments"""
"""Hide the processing actions if the right permissions are missing"""
...
...
@@ -59,7 +67,7 @@ class PaymentAdmin(admin.ModelAdmin):
"""Process the selected payment as cash"""
if
request
.
user
.
has_perm
(
'payments.process_payments'
):
updated_payments
=
services
.
process_payment
(
queryset
,
Payment
.
CASH
queryset
,
request
.
member
,
Payment
.
CASH
)
self
.
_process_feedback
(
request
,
updated_payments
)
process_cash_selected
.
short_description
=
_
(
...
...
@@ -69,7 +77,7 @@ class PaymentAdmin(admin.ModelAdmin):
"""Process the selected payment as card"""
if
request
.
user
.
has_perm
(
'payments.process_payments'
):
updated_payments
=
services
.
process_payment
(
queryset
,
Payment
.
CARD
queryset
,
request
.
member
,
Payment
.
CARD
)
self
.
_process_feedback
(
request
,
updated_payments
)
process_card_selected
.
short_description
=
_
(
...
...
@@ -83,3 +91,13 @@ class PaymentAdmin(admin.ModelAdmin):
message
=
_
(
"Successfully processed %(count)d %(items)s."
),
error
=
_
(
'The selected payment(s) could not be processed.'
)
)
def
get_urls
(
self
):
urls
=
super
().
get_urls
()
custom_urls
=
[
path
(
'<uuid:pk>/process/'
,
self
.
admin_site
.
admin_view
(
admin_views
.
PaymentAdminView
.
as_view
()),
name
=
'payments_payment_process'
),
]
return
custom_urls
+
urls
website/payments/views.py
→
website/payments/
admin_
views.py
View file @
9f55bb1a
"""
V
iews provided by the payments package"""
"""
Admin v
iews provided by the payments package"""
from
django.contrib
import
messages
from
django.contrib.admin.utils
import
model_ngettext
from
django.contrib.admin.views.decorators
import
staff_member_required
...
...
@@ -26,7 +26,7 @@ class PaymentAdminView(View):
return
redirect
(
'admin:payments_payment_change'
,
kwargs
[
'pk'
])
result
=
services
.
process_payment
(
payment
,
request
.
POST
[
'type'
]
payment
,
request
.
member
,
request
.
POST
[
'type'
]
)
if
len
(
result
)
>
0
:
...
...
@@ -36,4 +36,7 @@ class PaymentAdminView(View):
messages
.
error
(
request
,
_
(
'Could not process %s.'
)
%
model_ngettext
(
payment
,
1
))
if
'next'
in
request
.
POST
:
return
redirect
(
request
.
POST
[
'next'
])
return
redirect
(
'admin:payments_payment_change'
,
kwargs
[
'pk'
])
website/payments/locale/nl/LC_MESSAGES/django.mo
View file @
9f55bb1a
No preview for this file type
website/payments/locale/nl/LC_MESSAGES/django.po
View file @
9f55bb1a
...
...
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-
07-11 20:07
+0
2
00\n"
"PO-Revision-Date: 2018-
02-12 14:08
+0100\n"
"POT-Creation-Date: 2018-
11-27 18:25
+0
1
00\n"
"PO-Revision-Date: 2018-
11-27 18:26
+0100\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: \n"
"Language: nl\n"
...
...
@@ -16,79 +16,87 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.
0.6
\n"
"X-Generator: Poedit 2.
2
\n"
#:
payments/
admin.py
#: admin.py
msgid "Process selected payments (cash)"
msgstr "Verwerk geselecteerde betalingen (contant)"
#:
payments/
admin.py
#: admin.py
msgid "Process selected payments (card)"
msgstr "Verwerk geselecteerde betalingen (pin)"
#:
payments/admin.py payments/
tests/test_admin.py
#:
admin.py
tests/test_admin.py
#, python-format
msgid "Successfully processed %(count)d %(items)s."
msgstr "%(count)d %(items)s succesvol verwerkt."
#:
payments/
admin.py
#: admin.py
msgid "The selected payment(s) could not be processed."
msgstr "De geselecteerde betaling(en) konden niet worden verwerkt."
#: payments/apps.py
#: admin_views.py tests/test_admin_views.py
#, python-format
msgid "Successfully processed %s."
msgstr "%s succesvol verwerkt."
#: admin_views.py tests/test_admin_views.py
#, python-format
msgid "Could not process %s."
msgstr "%s kon niet worden verwerkt."
#: apps.py
msgid "Payments"
msgstr "Betalingen"
#:
payments/
models.py
#: models.py
msgid "created at"
msgstr "aangemaakt op"
#: payments/models.py
#: models.py
msgid "No payment"
msgstr "Geen betaling"
#: models.py
msgid "Cash payment"
msgstr "Contante betaling"
#:
payments/
models.py
#: models.py
msgid "Card payment"
msgstr "Pin betaling"
#:
payments/
models.py
#: models.py
msgid "type"
msgstr "type"
#: payments/models.py
msgid "processed"
msgstr "verwerkt"
#: payments/models.py
#: models.py
msgid "processing date"
msgstr "verwerkingsdatum"
#:
payments/
models.py
#: models.py
msgid "payment"
msgstr "betaling"
#:
payments/
models.py
#: models.py
msgid "payments"
msgstr "betalingen"
#:
payments/
models.py
#: models.py
msgid "Process payments"
msgstr "Verwerk betalingen"
#:
payments/
templates/admin/payments/change_form.html
#: templates/admin/payments/change_form.html
templates/payments/widget.html
msgid "Process (cash payment)"
msgstr "Verwerk (contant)"
#:
payments/
templates/admin/payments/change_form.html
#: templates/admin/payments/change_form.html
templates/payments/widget.html
msgid "Process (card payment)"
msgstr "Verwerk (pin)"
#: payments/tests/test_views.py payments/views.py
#, python-format
msgid "Successfully processed %s."
msgstr "%s succesvol verwerkt."
#: templates/payments/widget.html
msgid "Unprocessed"
msgstr "Onverwerkt"
#: payments/tests/test_views.py payments/views.py
#, python-format
msgid "Could not process %s."
msgstr "%s kon niet worden verwerkt."
#: templates/payments/widget.html
msgid "Processed"
msgstr "Verwerkt"
website/payments/migrations/0002_auto_20181127_1819.py
0 → 100644
View file @
9f55bb1a
# Generated by Django 2.1.3 on 2018-11-27 17:19
from
django.db
import
migrations
,
models
import
django.db.models.deletion
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'members'
,
'0027_auto_20181024_2000'
),
(
'payments'
,
'0001_initial'
),
]
operations
=
[
migrations
.
RemoveField
(
model_name
=
'payment'
,
name
=
'processed'
,
),
migrations
.
AddField
(
model_name
=
'payment'
,
name
=
'notes'
,
field
=
models
.
TextField
(
blank
=
True
,
null
=
True
),
),
migrations
.
AddField
(
model_name
=
'payment'
,
name
=
'paid_by'
,
field
=
models
.
ForeignKey
(
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
related_name
=
'paid_payment_set'
,
to
=
'members.Member'
),
),
migrations
.
AddField
(
model_name
=
'payment'
,
name
=
'processed_by'
,
field
=
models
.
ForeignKey
(
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
related_name
=
'processed_payment_set'
,
to
=
'members.Member'
),
),
migrations
.
AlterField
(
model_name
=
'payment'
,
name
=
'type'
,
field
=
models
.
CharField
(
choices
=
[(
'no_payment'
,
'No payment'
),
(
'cash_payment'
,
'Cash payment'
),
(
'card_payment'
,
'Card payment'
)],
default
=
'no_payment'
,
max_length
=
20
,
verbose_name
=
'type'
),
),
]
website/payments/models.py
View file @
9f55bb1a
...
...
@@ -17,10 +17,12 @@ class Payment(models.Model):
created_at
=
models
.
DateTimeField
(
_
(
'created at'
),
default
=
timezone
.
now
)
NONE
=
'no_payment'
CASH
=
'cash_payment'
CARD
=
'card_payment'
PAYMENT_TYPE
=
(
(
NONE
,
_
(
'No payment'
)),
(
CASH
,
_
(
'Cash payment'
)),
(
CARD
,
_
(
'Card payment'
)),
)
...
...
@@ -29,8 +31,7 @@ class Payment(models.Model):
choices
=
PAYMENT_TYPE
,
verbose_name
=
_
(
'type'
),
max_length
=
20
,
blank
=
True
,
null
=
True
,
default
=
NONE
)
amount
=
models
.
DecimalField
(
...
...
@@ -40,21 +41,40 @@ class Payment(models.Model):
decimal_places
=
2
)
processed
=
models
.
BooleanField
(
_
(
'processed'
),
default
=
False
,
)
processing_date
=
models
.
DateTimeField
(
_
(
'processing date'
),
blank
=
True
,
null
=
True
,
)
paid_by
=
models
.
ForeignKey
(
'members.Member'
,
models
.
CASCADE
,
related_name
=
'paid_payment_set'
,
blank
=
False
,
null
=
True
,
)
processed_by
=
models
.
ForeignKey
(
'members.Member'
,
models
.
CASCADE
,
related_name
=
'processed_payment_set'
,
blank
=
False
,
null
=
True
,
)
notes
=
models
.
TextField
(
blank
=
True
,
null
=
True
)
@
property
def
processed
(
self
):
return
self
.
type
!=
self
.
NONE
def
save
(
self
,
force_insert
=
False
,
force_update
=
False
,
using
=
None
,
update_fields
=
None
):
if
self
.
processed
and
not
self
.
processing_date
:
if
self
.
type
!=
self
.
NONE
and
not
self
.
processing_date
:
self
.
processing_date
=
timezone
.
now
()
elif
self
.
type
==
self
.
NONE
:
self
.
processing_date
=
None
super
().
save
(
force_insert
,
force_update
,
using
,
update_fields
)
def
get_admin_url
(
self
):
...
...
website/payments/services.py
View file @
9f55bb1a
...
...
@@ -2,25 +2,27 @@
from
.models
import
Payment
def
process_payment
(
queryset
,
pay_type
=
Payment
.
CARD
):
def
process_payment
(
queryset
,
processed_by
,
pay_type
=
Payment
.
CARD
):
"""
Process the payment
:param queryset: Queryset of payments that should be processed
:type queryset: QuerySet[Payment]
:param processed_by: Member that processed this payment
:type processed_by: Member
:param pay_type: Type of the payment
:type pay_type: String
"""
queryset
=
queryset
.
filter
(
processed
=
False
)
queryset
=
queryset
.
filter
(
type
=
Payment
.
NONE
)
data
=
[]
# This should trigger post_save signals, thus a queryset update
# is not appropriate, moreover save() automatically sets
# the processing date
for
payment
in
queryset
:
payment
.
processed
=
True
payment
.
type
=
pay_type
payment
.
processed_by
=
processed_by
payment
.
save
()
data
.
append
(
payment
)
...
...
website/payments/static/admin/payments/js/payments.js
View file @
9f55bb1a
django
.
jQuery
(
function
()
{
var
$
=
django
.
jQuery
;
$
(
"
.payments-row a
"
).
click
(
function
(
e
)
{
$
(
"
.payments-row a
.process
"
).
click
(
function
(
e
)
{
e
.
preventDefault
();
var
type
=
$
(
e
.
target
).
data
(
'
type
'
);
var
next
=
$
(
e
.
target
).
data
(
'
next
'
);
var
href
=
$
(
e
.
target
).
data
(
'
href
'
);
var
form
=
$
(
'
<form></form>
'
);
form
.
attr
(
"
method
"
,
"
post
"
);
...
...
@@ -14,6 +15,14 @@ django.jQuery(function () {
field
.
attr
(
"
value
"
,
type
);
form
.
append
(
field
);
if
(
next
)
{
var
redirect
=
$
(
'
<input/>
'
);
redirect
.
attr
(
"
type
"
,
"
hidden
"
);
redirect
.
attr
(
"
name
"
,
'
next
'
);
redirect
.
attr
(
"
value
"
,
window
.
location
);
form
.
append
(
redirect
);
}
var
csrf
=
$
(
'
<input/>
'
);
csrf
.
attr
(
"
type
"
,
"
hidden
"
);
csrf
.
attr
(
"
name
"
,
'
csrfmiddlewaretoken
'
);
...
...
website/payments/templates/admin/payments/change_form.html
View file @
9f55bb1a
...
...
@@ -14,8 +14,8 @@
{% block submit_buttons_bottom %}
{% if payment %}
<div
class=
"submit-row payments-row"
>
<a
data-href=
"{% url 'payments
:admin-
process' pk=payment.pk %}"
data-type=
"cash_payment"
class=
"button process"
>
{% trans "Process (cash payment)" %}
</a>
<a
data-href=
"{% url 'payments
:admin-
process' pk=payment.pk %}"
data-type=
"card_payment"
class=
"button process"
>
{% trans "Process (card payment)" %}
</a>
<a
data-href=
"{% url '
admin:
payments
_payment_
process' pk=payment.pk %}"
data-type=
"cash_payment"
class=
"button process"
>
{% trans "Process (cash payment)" %}
</a>
<a
data-href=
"{% url '
admin:
payments
_payment_
process' pk=payment.pk %}"
data-type=
"card_payment"
class=
"button process"
>
{% trans "Process (card payment)" %}
</a>
</div>
{% endif %}
...
...
website/payments/templates/payments/widget.html
View file @
9f55bb1a
{% load i18n %}
<div
class=
"readonly"
>
{% if widget.value %}
<a
href=
"{{ url }}"
>
{% if processed %}
{% trans "Processed" %}
{% else %}
{% trans "Unprocessed" %}
{% endif %}
</a>
{% else %}
-
{% endif %}
<div
class=
"readonly payments-row"
>
{% if widget.value %}
{% if not payment.processed %}
<a
href=
"{{ url }}"
>
{% trans "Unprocessed" %}
</a>
-
€ {{ payment.amount }}
-
<a
data-href=
"{% url 'admin:payments_payment_process' pk=payment.pk %}"
data-next=
"true"
data-type=
"cash_payment"
class=
"button process"
>
{% trans "Process (cash payment)" %}
</a>
<a
data-href=
"{% url 'admin:payments_payment_process' pk=payment.pk %}"
data-next=
"true"
data-type=
"card_payment"
class=
"button process"
>
{% trans "Process (card payment)" %}
</a>
{% else %}
<a
href=
"{{ url }}"
>
{% trans "Processed" %}
</a>
-
{{ payment.processing_date }} - {{ payment.get_type_display }}
{% endif %}
{% else %}
-
{% endif %}
</div>
website/payments/tests/test_admin.py
View file @
9f55bb1a
...
...
@@ -4,7 +4,6 @@ from unittest.mock import Mock
from
django.contrib
import
messages
from
django.contrib.admin
import
AdminSite
from
django.contrib.admin.utils
import
model_ngettext
from
django.contrib.auth
import
get_user_model
from
django.contrib.auth.models
import
Permission
from
django.contrib.contenttypes.models
import
ContentType
from
django.http
import
HttpRequest
...
...
@@ -12,6 +11,7 @@ from django.test import TestCase, SimpleTestCase, Client, RequestFactory
from
django.urls
import
reverse
from
django.utils.translation
import
ugettext_lazy
as
_
from
members.models
import
Member
,
Profile
from
payments
import
admin
from
payments.models
import
Payment
...
...
@@ -36,10 +36,14 @@ class PaymentAdminTest(TestCase):
@
classmethod
def
setUpTestData
(
cls
):
cls
.
user
=
get_user_model
().
objects
.
create_user
(
username
=
'username'
,
is_staff
=
True
,
)
cls
.
user
=
Member
.
objects
.
create
(
username
=
'test1'
,
first_name
=
'Test1'
,
last_name
=
'Example'
,
email
=
'test1@example.org'
,
is_staff
=
True
,
)
Profile
.
objects
.
create
(
user
=
cls
.
user
)
def
setUp
(
self
):
self
.
client
=
Client
()
...
...
@@ -72,7 +76,6 @@ class PaymentAdminTest(TestCase):
def
test_changeform_view
(
self
,
payment_get
):
object_id
=
'c85ea333-3508-46f1-8cbb-254f8c138020'
payment
=
Payment
.
objects
.
create
(
pk
=
object_id
,
processed
=
False
,
amount
=
7.5
)
payment_get
.
return_value
=
payment
...
...
@@ -94,7 +97,7 @@ class PaymentAdminTest(TestCase):
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
context
[
'payment'
],
payment
)
payment
.
processed
=
True
payment
.
type
=
Payment
.
CARD
response
=
self
.
client
.
get
(
'/admin/payments/payment/{}/change/'
.
format
(
object_id
))
...
...
@@ -107,7 +110,6 @@ class PaymentAdminTest(TestCase):
def
test_process_cash
(
self
,
process_payment
,
message_user
):
object_id
=
'c85ea333-3508-46f1-8cbb-254f8c138020'
payment
=
Payment
.
objects
.
create
(
pk
=
object_id
,
processed
=
False
,
amount
=
7.5
)
queryset
=
Payment
.
objects
.
filter
(
pk
=
object_id
)
process_payment
.
return_value
=
[
payment
]
...
...
@@ -132,7 +134,8 @@ class PaymentAdminTest(TestCase):
process_payment
.
assert_not_called
()
self
.
admin
.
process_cash_selected
(
request_hasperms
,
queryset
)
process_payment
.
assert_called_once_with
(
queryset
,
Payment
.
CASH
)
process_payment
.
assert_called_once_with
(
queryset
,
self
.
user
,
Payment
.
CASH
)
message_user
.
assert_called_once_with
(
request_hasperms
,
_
(
'Successfully processed %(count)d %(items)s.'
)
...
...
@@ -147,7 +150,6 @@ class PaymentAdminTest(TestCase):
def
test_process_card
(
self
,
process_payment
,
message_user
):
object_id
=
'c85ea333-3508-46f1-8cbb-254f8c138020'
payment
=
Payment
.
objects
.
create
(
pk
=
object_id
,
processed
=
False
,
amount
=
7.5
)
queryset
=
Payment
.
objects
.
filter
(
pk
=
object_id
)
process_payment
.
return_value
=
[
payment
]
...
...
@@ -172,7 +174,8 @@ class PaymentAdminTest(TestCase):
process_payment
.
assert_not_called
()
self
.
admin
.
process_card_selected
(
request_hasperms
,
queryset
)
process_payment
.
assert_called_once_with
(
queryset
,
Payment
.
CARD
)
process_payment
.
assert_called_once_with
(
queryset
,
self
.
user
,
Payment
.
CARD
)
message_user
.
assert_called_once_with
(
request_hasperms
,
_
(
'Successfully processed %(count)d %(items)s.'
)
...
...
@@ -197,3 +200,7 @@ class PaymentAdminTest(TestCase):
self
.
assertCountEqual
(
actions
,
[
'delete_selected'
,
'process_cash_selected'
,
'process_card_selected'
])
def
test_get_urls
(
self
):
urls
=
self
.
admin
.
get_urls
()
self
.
assertEqual
(
urls
[
0
].
name
,
'payments_payment_process'
)
website/payments/tests/test_views.py
→
website/payments/tests/test_
admin_
views.py
View file @
9f55bb1a
...
...
@@ -2,13 +2,13 @@ from unittest import mock
from
unittest.mock
import
Mock
from
django.contrib.admin.utils
import
model_ngettext
from
django.contrib.auth
import
get_user_model
from
django.contrib.auth.models
import
Permission
from
django.contrib.contenttypes.models
import
ContentType
from
django.test
import
Client
,
TestCase
from
django.utils.translation
import
ugettext_lazy
as
_
from
payments
import
views
from
members.models
import
Member
,
Profile
from
payments
import
admin_views
from
payments.models
import
Payment
...
...
@@ -17,15 +17,23 @@ class PaymentAdminViewTest(TestCase):
@
classmethod
def
setUpTestData
(
cls
):
cls
.
payment
=
Payment
.
objects
.
create
(
processed
=
False
,
amount
=
7.5
)
cls
.
user
=
get_user_model
().
objects
.
create_user
(
username
=
'username'
)
cls
.
user
=
Member
.
objects
.
create
(
username
=
'test1'
,
first_name
=
'Test1'
,
last_name
=
'Example'
,
email
=
'test1@example.org'
)
Profile
.
objects
.
create
(
user
=
cls
.
user
,
language
=
'nl'
,
)
def
setUp
(
self
):
self
.
client
=
Client
()
self
.
client
.
force_login
(
self
.
user
)
self
.
view
=
views
.
PaymentAdminView
()
self
.
view
=