0001-lingo-use-form-instead-of-json-input-for-backends-67.patch
combo/apps/lingo/forms.py | ||
---|---|---|
18 | 18 | |
19 | 19 | |
20 | 20 |
from django import forms |
21 |
from django.core.exceptions import ValidationError |
|
21 | 22 |
from django.utils.translation import ugettext_lazy as _ |
22 | 23 | |
23 |
from .models import Regie |
|
24 |
from .models import Regie, PaymentBackend |
|
25 | ||
26 |
TYPE_FIELD_MAPPING = { |
|
27 |
str: forms.CharField, |
|
28 |
bool: forms.BooleanField, |
|
29 |
int: forms.IntegerField, |
|
30 |
float: forms.FloatField, |
|
31 |
} |
|
32 | ||
33 | ||
34 |
def get_validator(func, err_msg): |
|
35 |
def validate(value): |
|
36 |
if not func(value): |
|
37 |
message = err_msg or _('Invalid value.') |
|
38 |
raise ValidationError(message) |
|
39 |
return validate |
|
24 | 40 | |
25 | 41 | |
26 | 42 |
def create_form_fields(parameters, json_field): |
27 | 43 |
fields, initial = {}, {} |
28 | 44 |
for param in parameters: |
29 | 45 |
field_name = param['name'] |
30 |
if param['type'] is bool: |
|
31 |
fields[field_name] = forms.BooleanField(label=param['caption'], required=False) |
|
32 |
initial[field_name] = param['default'] |
|
33 |
if field_name in json_field: |
|
34 |
initial[field_name] = json_field[field_name] |
|
46 |
if field_name in ('normal_return_url', 'automatic_return_url') or param.get('deprecated'): |
|
47 |
continue |
|
48 | ||
49 |
field_params = { |
|
50 |
'label': param.get('caption') or field_name, |
|
51 |
'required': param.get('required', False) |
|
52 |
} |
|
53 |
if 'validation' in param: |
|
54 |
field_params['validators'] = [ |
|
55 |
get_validator(param['validation'], param.get('validation_err_msg')) |
|
56 |
] |
|
57 | ||
58 |
_type = param.get('type', str) |
|
59 |
choices = param.get('choices') |
|
60 |
if choices is not None: |
|
61 |
field_class = forms.MultipleChoiceField if _type is list else forms.ChoiceField |
|
62 |
if choices and not isinstance(choices[0], tuple): |
|
63 |
choices = [(choice, choice) for choice in choices] |
|
64 |
field_params['choices'] = choices |
|
35 | 65 |
else: |
36 |
raise NotImplementedError() |
|
66 |
field_class = TYPE_FIELD_MAPPING[_type] |
|
67 | ||
68 |
fields[field_name] = field_class(**field_params) |
|
69 |
initial_value = json_field.get(field_name, param.get('default')) |
|
70 |
if initial_value: |
|
71 |
initial[field_name] = initial_value |
|
72 | ||
37 | 73 |
return fields, initial |
38 | 74 | |
39 | 75 | |
... | ... | |
72 | 108 |
return instance |
73 | 109 | |
74 | 110 | |
111 |
class PaymentBackendForm(forms.ModelForm): |
|
112 | ||
113 |
class Meta: |
|
114 |
model = PaymentBackend |
|
115 |
fields = ['label', 'slug', 'service'] |
|
116 | ||
117 |
def __init__(self, *args, **kwargs): |
|
118 |
super(PaymentBackendForm, self).__init__(*args, **kwargs) |
|
119 |
fields, initial = create_form_fields( |
|
120 |
self.instance.get_payment().get_parameters(scope='global'), |
|
121 |
self.instance.service_options |
|
122 |
) |
|
123 |
self.fields.update(fields) |
|
124 |
self.initial.update(initial) |
|
125 |
if self.fields['service']: |
|
126 |
self.fields['service'].disabled = True |
|
127 | ||
128 |
def save(self): |
|
129 |
instance = super(PaymentBackendForm, self).save() |
|
130 |
instance.service_options = compute_json_field( |
|
131 |
self.instance.get_payment().get_parameters(scope='global'), |
|
132 |
self.cleaned_data |
|
133 |
) |
|
134 |
instance.save() |
|
135 |
return instance |
|
136 | ||
137 | ||
75 | 138 |
class TransactionExportForm(forms.Form): |
76 | 139 |
start_date = forms.DateField( |
77 | 140 |
label=_('Start date'), |
combo/apps/lingo/manager_views.py | ||
---|---|---|
20 | 20 | |
21 | 21 |
from django.urls import reverse |
22 | 22 |
from django.urls import reverse_lazy |
23 |
from django.contrib import messages |
|
23 | 24 |
from django.db.models import Q, Prefetch |
24 | 25 |
from django.db.models.expressions import RawSQL |
25 | 26 |
from django.utils import six |
26 | 27 |
from django.utils.timezone import make_aware, now |
28 |
from django.utils.translation import ugettext_lazy as _ |
|
27 | 29 |
from django.views.generic import CreateView, UpdateView, ListView, DeleteView, View |
28 | 30 |
from django.http import HttpResponse |
29 | 31 |
from django.http import HttpResponseRedirect |
... | ... | |
32 | 34 | |
33 | 35 |
import eopayment |
34 | 36 | |
35 |
from .forms import RegieForm |
|
37 |
from .forms import RegieForm, PaymentBackendForm
|
|
36 | 38 |
from .forms import TransactionExportForm |
37 | 39 |
from .models import BasketItem, PaymentBackend, Regie, Transaction |
38 | 40 | |
... | ... | |
64 | 66 | |
65 | 67 |
class PaymentBackendCreateView(CreateView): |
66 | 68 |
model = PaymentBackend |
67 |
fields = '__all__' |
|
68 |
success_url = reverse_lazy('lingo-manager-paymentbackend-list') |
|
69 |
fields = ['label', 'slug', 'service'] |
|
70 | ||
71 |
def get_success_url(self): |
|
72 |
messages.info(self.request, _('Please fill additional backend parameters.')) |
|
73 |
return reverse_lazy('lingo-manager-paymentbackend-edit', kwargs={'pk': self.object.pk}) |
|
69 | 74 | |
70 | 75 | |
71 | 76 |
class PaymentBackendUpdateView(UpdateView): |
72 | 77 |
model = PaymentBackend |
73 |
fields = '__all__'
|
|
78 |
form_class = PaymentBackendForm
|
|
74 | 79 |
success_url = reverse_lazy('lingo-manager-paymentbackend-list') |
75 | 80 | |
76 | 81 |
combo/apps/lingo/models.py | ||
---|---|---|
90 | 90 |
no_online_payment_reason=data.get('no_online_payment_reason')) |
91 | 91 | |
92 | 92 | |
93 |
def validate_dict(value): |
|
94 |
if not isinstance(value, dict): |
|
95 |
raise ValidationError(_('Value must be a JSON object')) |
|
96 | ||
97 | ||
98 | 93 |
@python_2_unicode_compatible |
99 | 94 |
class PaymentBackend(models.Model): |
100 | 95 |
label = models.CharField(verbose_name=_('Label'), max_length=64) |
... | ... | |
103 | 98 |
help_text=_('The identifier is used in webservice calls.')) |
104 | 99 |
service = models.CharField( |
105 | 100 |
verbose_name=_('Payment Service'), max_length=64, choices=SERVICES) |
106 |
service_options = JSONField( |
|
107 |
blank=True, |
|
108 |
verbose_name=_('Payment Service Options'), |
|
109 |
validators=[validate_dict]) |
|
101 |
service_options = JSONField(blank=True, verbose_name=_('Payment Service Options')) |
|
110 | 102 | |
111 | 103 |
def __str__(self): |
112 | 104 |
return self.label |
tests/test_lingo_manager.py | ||
---|---|---|
604 | 604 |
resp = app.get('/manage/lingo/paymentbackends/add/', status=200) |
605 | 605 |
assert '/manage/lingo/paymentbackends/' in resp.text |
606 | 606 | |
607 |
resp.forms[0]['label'] = 'Test' |
|
608 |
resp.forms[0]['slug'] = 'test-add' |
|
609 |
resp.forms[0]['service'] = 'dummy' |
|
610 |
resp.forms[0]['service_options'] = '{"siret": "1234"}' |
|
607 |
resp.form['label'] = 'Test' |
|
608 |
resp.form['slug'] = 'test-add' |
|
609 |
resp.form['service'] = 'dummy' |
|
611 | 610 |
resp = resp.forms[0].submit() |
612 | 611 | |
613 | 612 |
assert PaymentBackend.objects.count() == 1 |
614 | 613 |
payment_backend = PaymentBackend.objects.get(slug='test-add') |
615 | 614 |
assert payment_backend.label == 'Test' |
616 |
assert payment_backend.service_options == {'siret': '1234'}
|
|
615 |
assert payment_backend.service_options == {} |
|
617 | 616 | |
618 |
assert resp.location.endswith('/manage/lingo/paymentbackends/') |
|
617 |
backend = PaymentBackend.objects.first() |
|
618 |
assert resp.location.endswith('/manage/lingo/paymentbackends/%s/edit' % backend.pk) |
|
619 |
resp = resp.follow() |
|
620 |
assert 'fill additional backend parameters' in resp.text |
|
619 | 621 | |
620 | 622 | |
621 |
def test_add_payment_backend_validate_options(app, admin_user): |
|
623 |
def test_edit_payment_backend(app, admin_user): |
|
624 |
payment_backend = PaymentBackend.objects.create(label='label1', slug='slug1', service='dummy') |
|
622 | 625 |
app = login(app) |
623 |
resp = app.get('/manage/lingo/paymentbackends/add/', status=200)
|
|
626 |
resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk, status=200)
|
|
624 | 627 |
assert '/manage/lingo/paymentbackends/' in resp.text |
625 | 628 | |
626 |
resp.forms[0]['label'] = 'Test' |
|
627 |
resp.forms[0]['slug'] = 'test-add' |
|
628 |
resp.forms[0]['service'] = 'dummy' |
|
629 |
resp.forms[0]['service_options'] = 'xx' |
|
630 |
resp2 = resp.forms[0].submit() |
|
631 |
assert not PaymentBackend.objects.count() |
|
629 |
# service cannot be changed |
|
630 |
assert 'disabled' in resp.form['service'].attrs |
|
632 | 631 | |
633 |
resp.forms[0]['service_options'] = '"xx"'
|
|
634 |
resp2 = resp.forms[0].submit()
|
|
635 |
assert not PaymentBackend.objects.count()
|
|
632 |
# deprecated parameters are not shown
|
|
633 |
assert not 'next_url' in resp.form.fields
|
|
634 |
assert not 'direct_notification_url' in resp.form.fields
|
|
636 | 635 | |
637 |
resp.forms[0]['service_options'] = '[1]' |
|
638 |
resp2 = resp.forms[0].submit() |
|
639 |
assert not PaymentBackend.objects.count() |
|
636 |
# default values become initial values |
|
637 |
assert resp.form['siret']._value == '1234' |
|
640 | 638 | |
641 |
resp.forms[0]['service_options'] = '{"a": 1}' |
|
642 |
resp2 = resp.forms[0].submit() |
|
643 |
assert PaymentBackend.objects.count() |
|
644 |
assert PaymentBackend.objects.get().service_options == {'a': 1} |
|
639 |
resp.form['label'] = 'label1-modified' |
|
640 |
resp.form['siret'] = '12345' |
|
641 |
resp.form['consider_all_response_signed'] = True |
|
642 |
resp.form['number'] = 12 |
|
643 |
resp.form['choice'] = 'b' |
|
644 |
resp.form['choices'] = ['a', 'b'] |
|
645 |
resp = resp.form.submit() |
|
645 | 646 | |
647 |
assert resp.location.endswith('/manage/lingo/paymentbackends/') |
|
648 |
payment_backend = PaymentBackend.objects.get(slug='slug1') |
|
649 |
assert payment_backend.label == 'label1-modified' |
|
650 |
assert payment_backend.service_options['siret'] == '12345' |
|
651 |
assert payment_backend.service_options['consider_all_response_signed'] == True |
|
652 |
assert payment_backend.service_options['number'] == 12 |
|
653 |
assert payment_backend.service_options['choice'] == 'b' |
|
654 |
assert payment_backend.service_options['choices'] == ['a', 'b'] |
|
646 | 655 | |
647 |
@pytest.mark.xfail |
|
648 |
def test_jsonfield_null_bug(app, admin_user): |
|
649 |
app = login(app) |
|
650 |
resp = app.get('/manage/lingo/paymentbackends/add/', status=200) |
|
651 |
assert '/manage/lingo/paymentbackends/' in resp.text |
|
652 | 656 | |
653 |
resp.forms[0]['label'] = 'Test' |
|
654 |
resp.forms[0]['slug'] = 'test-add' |
|
655 |
resp.forms[0]['service'] = 'dummy' |
|
656 |
resp.forms[0]['service_options'] = 'null' |
|
657 |
resp2 = resp.forms[0].submit() |
|
658 |
assert PaymentBackend.objects.count() |
|
659 |
assert PaymentBackend.objects.get().service_options == {'a': 1} |
|
657 |
def test_edit_payment_backend_validation(app, admin_user): |
|
658 |
payment_backend = PaymentBackend.objects.create(label='label1', slug='slug1', service='dummy') |
|
659 |
app = login(app) |
|
660 |
resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk) |
|
660 | 661 | |
662 |
# required field |
|
663 |
resp.form['siret'] = '' |
|
664 |
resp = resp.form.submit() |
|
665 |
assert 'This field is required.' in resp.text |
|
666 |
assert resp.form['siret']._value == None |
|
661 | 667 | |
662 |
def test_edit_payment_backend(app, admin_user): |
|
663 |
payment_backend = PaymentBackend.objects.create(label='label1', slug='slug1') |
|
664 |
app = login(app) |
|
665 |
resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk, status=200) |
|
666 |
assert '/manage/lingo/paymentbackends/' in resp.text |
|
668 |
# validation function |
|
669 |
resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk) |
|
670 |
resp.form['siret'] = 'a' |
|
671 |
resp = resp.form.submit() |
|
672 |
assert 'must be a number' in resp.text |
|
673 |
assert resp.form['siret']._value == 'a' |
|
667 | 674 | |
668 |
resp.forms[0]['label'] = 'label1-modified' |
|
669 |
resp.forms[0]['slug'] = 'slug1' |
|
670 |
resp.forms[0]['service'] = 'dummy' |
|
671 |
resp.forms[0]['service_options'] = '{"siret": "1234"}' |
|
672 |
resp = resp.forms[0].submit() |
|
675 |
# wrong type |
|
676 |
resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk) |
|
677 |
resp.form['number'] = 'a' |
|
678 |
resp = resp.form.submit() |
|
679 |
assert 'Enter a whole number.' in resp.text |
|
680 |
assert resp.form['number']._value == 'a' |
|
673 | 681 | |
674 |
assert resp.location.endswith('/manage/lingo/paymentbackends/') |
|
675 |
payment_backend = PaymentBackend.objects.get(slug='slug1') |
|
676 |
assert payment_backend.label == 'label1-modified' |
|
682 |
# not in choices |
|
683 |
resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk) |
|
684 |
resp.form['choice'].force_value('c') |
|
685 |
resp = resp.form.submit() |
|
686 |
assert 'c is not one of the available choices' in resp.text |
|
677 | 687 | |
678 | 688 | |
679 | 689 |
def test_use_old_service_options_safely(app, admin_user): |
680 |
- |