Projet

Général

Profil

0001-lingo-use-form-instead-of-json-input-for-backends-67.patch

Valentin Deniaud, 03 juin 2020 14:15

Télécharger (12,6 ko)

Voir les différences:

Subject: [PATCH] lingo: use form instead of json input for backends (#6710)

 combo/apps/lingo/forms.py         |  77 +++++++++++++++++++--
 combo/apps/lingo/manager_views.py |  13 ++--
 combo/apps/lingo/models.py        |  10 +--
 tests/test_lingo_manager.py       | 108 ++++++++++++++++--------------
 4 files changed, 139 insertions(+), 69 deletions(-)
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
-