Projet

Général

Profil

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

Valentin Deniaud, 19 mai 2020 15:42

Télécharger (12,4 ko)

Voir les différences:

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

 combo/apps/lingo/forms.py         |  75 ++++++++++++++++++++--
 combo/apps/lingo/manager_views.py |  13 ++--
 combo/apps/lingo/models.py        |  10 +--
 tests/test_lingo_manager.py       | 103 ++++++++++++++++--------------
 4 files changed, 132 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
        _typ = param.get('type', str)
59
        choices = param.get('choices')
60
        if choices is not None:
61
            field_class = forms.MultipleChoiceField if _typ 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[_typ]
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

  
126
    def save(self):
127
        instance = super(PaymentBackendForm, self).save()
128
        instance.service_options = compute_json_field(
129
            self.instance.get_payment().get_parameters(scope='global'),
130
            self.cleaned_data
131
        )
132
        instance.save()
133
        return instance
134

  
135

  
75 136
class TransactionExportForm(forms.Form):
76 137
    start_date = forms.DateField(
77 138
        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 619

  
620 620

  
621
def test_add_payment_backend_validate_options(app, admin_user):
621
def test_edit_payment_backend(app, admin_user):
622
    payment_backend = PaymentBackend.objects.create(label='label1', slug='slug1', service='dummy')
622 623
    app = login(app)
623
    resp = app.get('/manage/lingo/paymentbackends/add/', status=200)
624
    resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk, status=200)
624 625
    assert '/manage/lingo/paymentbackends/' in resp.text
625 626

  
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()
627
    # deprecated parameters are not shown
628
    assert not 'next_url' in resp.form.fields
629
    assert not 'direct_notification_url' in resp.form.fields
632 630

  
633
    resp.forms[0]['service_options'] = '"xx"'
634
    resp2 = resp.forms[0].submit()
635
    assert not PaymentBackend.objects.count()
631
    # default values become initial values
632
    assert resp.form['siret']._value == '1234'
636 633

  
637
    resp.forms[0]['service_options'] = '[1]'
638
    resp2 = resp.forms[0].submit()
639
    assert not PaymentBackend.objects.count()
634
    resp.form['label'] = 'label1-modified'
635
    resp.form['siret'] = '12345'
636
    resp.form['consider_all_response_signed'] = True
637
    resp.form['number'] = 12
638
    resp.form['choice'] = 'b'
639
    resp.form['choices'] = ['a', 'b']
640
    resp = resp.form.submit()
640 641

  
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}
642
    assert resp.location.endswith('/manage/lingo/paymentbackends/')
643
    payment_backend = PaymentBackend.objects.get(slug='slug1')
644
    assert payment_backend.label == 'label1-modified'
645
    assert payment_backend.service_options['siret'] == '12345'
646
    assert payment_backend.service_options['consider_all_response_signed'] == True
647
    assert payment_backend.service_options['number'] == 12
648
    assert payment_backend.service_options['choice'] == 'b'
649
    assert payment_backend.service_options['choices'] == ['a', 'b']
645 650

  
646 651

  
647
@pytest.mark.xfail
648
def test_jsonfield_null_bug(app, admin_user):
652
def test_edit_payment_backend_validation(app, admin_user):
653
    payment_backend = PaymentBackend.objects.create(label='label1', slug='slug1', service='dummy')
649 654
    app = login(app)
650
    resp = app.get('/manage/lingo/paymentbackends/add/', status=200)
651
    assert '/manage/lingo/paymentbackends/' in resp.text
652

  
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}
655
    resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk)
660 656

  
657
    # required field
658
    resp.form['siret'] = ''
659
    resp = resp.form.submit()
660
    assert 'This field is required.' in resp.text
661
    assert resp.form['siret']._value == None
661 662

  
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
663
    # validation function
664
    resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk)
665
    resp.form['siret'] = 'a'
666
    resp = resp.form.submit()
667
    assert 'must be a number' in resp.text
668
    assert resp.form['siret']._value == 'a'
667 669

  
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()
670
    # wrong type
671
    resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk)
672
    resp.form['number'] = 'a'
673
    resp = resp.form.submit()
674
    assert 'Enter a whole number.' in resp.text
675
    assert resp.form['number']._value == 'a'
673 676

  
674
    assert resp.location.endswith('/manage/lingo/paymentbackends/')
675
    payment_backend = PaymentBackend.objects.get(slug='slug1')
676
    assert payment_backend.label == 'label1-modified'
677
    # not in choices
678
    resp = app.get('/manage/lingo/paymentbackends/%s/edit' % payment_backend.pk)
679
    resp.form['choice'].force_value('c')
680
    resp = resp.form.submit()
681
    assert 'c is not one of the available choices' in resp.text
677 682

  
678 683

  
679 684
def test_use_old_service_options_safely(app, admin_user):
680
-