Projet

Général

Profil

0001-profile_views-address-autocomplete-field-41919.patch

Lauréline Guérin, 06 octobre 2020 15:49

Télécharger (13,5 ko)

Voir les différences:

Subject: [PATCH] profile_views: address autocomplete field (#41919)

 src/authentic2/api_urls.py                    |  1 +
 src/authentic2/api_views.py                   | 23 +++++++
 src/authentic2/attribute_kinds.py             | 29 +++++++++
 .../static/authentic2/manager/css/style.css   |  3 +-
 src/authentic2/manager/user_views.py          |  2 +
 .../authentic2/js/address_autocomplete.js     | 60 +++++++++++++++++++
 .../widgets/address_autocomplete.html         |  5 ++
 tests/test_api.py                             | 47 +++++++++++++++
 tests/test_user_manager.py                    | 23 +++++++
 9 files changed, 192 insertions(+), 1 deletion(-)
 create mode 100644 src/authentic2/static/authentic2/js/address_autocomplete.js
 create mode 100644 src/authentic2/templates/authentic2/widgets/address_autocomplete.html
src/authentic2/api_urls.py
28 28
        name='a2-api-role-members'),
29 29
    url(r'^check-password/$', api_views.check_password, name='a2-api-check-password'),
30 30
    url(r'^validate-password/$', api_views.validate_password, name='a2-api-validate-password'),
31
    url(r'^address-autocomplete/$', api_views.address_autocomplete, name='a2-api-address-autocomplete'),
31 32
]
32 33

  
33 34
urlpatterns += api_views.router.urls
src/authentic2/api_views.py
21 21
from pytz.exceptions import AmbiguousTimeError
22 22
import django
23 23
from django.db import models
24
from django.conf import settings
24 25
from django.contrib.auth import get_user_model
25 26
from django.contrib.auth.hashers import identify_hasher
26 27
from django.core.exceptions import MultipleObjectsReturned
......
33 34
from django.shortcuts import get_object_or_404
34 35

  
35 36
from django_rbac.utils import get_ou_model, get_role_model
37
import requests
38
from requests.exceptions import RequestException
36 39

  
37 40
from rest_framework import serializers, pagination
38 41
from rest_framework.validators import UniqueTogetherValidator
......
1038 1041
        return result, status.HTTP_200_OK
1039 1042

  
1040 1043
validate_password = ValidatePasswordAPI.as_view()
1044

  
1045

  
1046
class AddressAutocompleteAPI(APIView):
1047
    permission_classes = (permissions.IsAuthenticated,)
1048

  
1049
    def get(self, request):
1050
        if not getattr(settings, 'ADDRESS_AUTOCOMPLETE_URL', None):
1051
            return Response({})
1052
        try:
1053
            response = requests.get(
1054
                settings.ADDRESS_AUTOCOMPLETE_URL,
1055
                params=request.GET
1056
            )
1057
            response.raise_for_status()
1058
            return Response(response.json())
1059
        except RequestException:
1060
            return Response({})
1061

  
1062

  
1063
address_autocomplete = AddressAutocompleteAPI.as_view()
src/authentic2/attribute_kinds.py
23 23
from itertools import chain
24 24

  
25 25
from django import forms
26
from django.conf import settings
26 27
from django.core.exceptions import ValidationError
27 28
from django.core.validators import RegexValidator
29
from django.urls import reverse
28 30
from django.utils import six, formats
29 31
from django.utils.translation import ugettext_lazy as _, pgettext_lazy
30 32
from django.utils import html
......
110 112
    ]
111 113

  
112 114

  
115
class AddressAutocompleteInput(forms.Select):
116
    template_name = 'authentic2/widgets/address_autocomplete.html'
117

  
118
    class Media:
119
        js = [
120
            settings.SELECT2_JS,
121
            'authentic2/js/address_autocomplete.js',
122
        ]
123
        css = {
124
            'screen': [settings.SELECT2_CSS],
125
        }
126

  
127
    def __init__(self, **kwargs):
128
        super().__init__(**kwargs)
129
        self.attrs['data-select2-url'] = reverse('a2-api-address-autocomplete')
130
        self.attrs['class'] = 'address-autocomplete'
131

  
132

  
133
class AddressAutocompleteField(forms.CharField):
134
    widget = AddressAutocompleteInput
135

  
136

  
113 137
@to_iter
114 138
def get_title_choices():
115 139
    return app_settings.A2_ATTRIBUTE_KIND_TITLE_CHOICES or DEFAULT_TITLE_CHOICES
......
269 293
        'rest_framework_field_class': BirthdateRestField,
270 294
        'free_text_search': date_free_text_search,
271 295
    },
296
    {
297
        'label': _('address (autocomplete)'),
298
        'name': 'address_auto',
299
        'field_class': AddressAutocompleteField,
300
    },
272 301
    {
273 302
        'label': _('french postcode'),
274 303
        'name': 'fr_postcode',
src/authentic2/manager/static/authentic2/manager/css/style.css
248 248
	margin-right: 1em;
249 249
}
250 250

  
251
.ui-dialog-content span.select2-container {
251
.ui-dialog-content span.select2-container,
252
form .widget span.select2-container {
252 253
	width: 100% !important;
253 254
}
254 255

  
src/authentic2/manager/user_views.py
315 315
        if not self.object.username and self.object.ou and not self.object.ou.show_username:
316 316
            fields.remove('username')
317 317
        for attribute in Attribute.objects.all():
318
            if attribute.name == 'address_autocomplete':
319
                continue
318 320
            fields.append(attribute.name)
319 321
        if self.request.user.is_superuser and \
320 322
                'is_superuser' not in self.fields:
src/authentic2/static/authentic2/js/address_autocomplete.js
1
$(function() {
2
    $('select.address-autocomplete').select2({
3
        ajax: {
4
            delay: 250,
5
            dataType: 'json',
6
            data: function(params) {
7
                return {q: params.term, page_limit: 10};
8
            },
9
            processResults: function (data, params) {
10
                return {results: data.data};
11
            },
12
            url: function (params) {
13
                return $(this).data('select2-url')
14
            }
15
        }
16
    }).on('select2:select', function(e) {
17
        var data = e.params.data;
18
        if (data) {
19
            var address = undefined;
20
            if (typeof data.address == "object") {
21
                address = data.address;
22
            } else {
23
                address = data;
24
            }
25
            var road = address.road || address.nom_rue;
26
            var house_number = address.house_number || address.numero;
27
            var city = address.city || address.nom_commune;
28
            var postcode = address.postcode || address.code_postal;
29
            var number_and_street = null;
30
            if (house_number && road) {
31
                number_and_street = house_number + ' ' + road;
32
            } else {
33
                number_and_street = road;
34
            }
35
            $('#id_address').val(number_and_street);
36
            $('#id_city').val(city);
37
            $('#id_zipcode').val(postcode);
38
        }
39
    });
40
    $('#id_address, #id_city, #id_zipcode').attr('disabled', 'disabled');
41
    $('#manual-address').on('change', function() {
42
        $('#id_address, #id_city, #id_zipcode').attr('disabled', this.checked ? null : 'disabled');
43
    });
44
    if ($('#id_address').val() || $('#id_city').val() || $('#id_zipcode').val()) {
45
        var data = {
46
            id: 1,
47
            text: ''
48
        }
49
        $.each(['#id_address', '#id_zipcode', '#id_city'], function(idx, value) {
50
            if ($(value).val()) {
51
                if (data.text) {
52
                    data.text += ' ';
53
                }
54
                data.text += $(value).val();
55
            }
56
        })
57
        var newOption = new Option(data.text, data.id, false, false);
58
        $('select.address-autocomplete').append(newOption).trigger('change');
59
    }
60
});
src/authentic2/templates/authentic2/widgets/address_autocomplete.html
1
{% load i18n %}
2
{% include "django/forms/widgets/select.html" %}
3
<div>
4
  <label><input id="manual-address" type="checkbox">{% trans "Manually enter the address" %}</label>
5
</div>
tests/test_api.py
18 18

  
19 19
import datetime
20 20
import json
21
import mock
21 22
import pytest
22 23
import random
23 24
import uuid
......
36 37

  
37 38
from django_rbac.models import SEARCH_OP
38 39
from django_rbac.utils import get_role_model, get_ou_model
40
from requests.models import Response
39 41

  
40 42
from authentic2.a2_rbac.models import Role
41 43
from authentic2.a2_rbac.utils import get_default_ou
......
1926 1928
    resp = app.get('/api/users/find_duplicates/', params=params)
1927 1929
    assert len(resp.json['data']) == 2
1928 1930
    assert resp.json['data'][0]['id'] == homonym.pk
1931

  
1932

  
1933
class MockedRequestResponse(mock.Mock):
1934
    status_code = 200
1935

  
1936
    def json(self):
1937
        return json.loads(self.content)
1938

  
1939

  
1940
def test_api_address_autocomplete(app, admin, settings):
1941
    app.authorization = ('Basic', (admin.username, admin.username))
1942

  
1943
    settings.ADDRESS_AUTOCOMPLETE_URL = 'example.com'
1944

  
1945
    params = {'q': '42 avenue'}
1946
    with mock.patch('authentic2.api_views.requests.get') as requests_get:
1947
        mock_resp = Response()
1948
        mock_resp.status_code = 500
1949
        requests_get.return_value = mock_resp
1950
        resp = app.get('/api/address-autocomplete/', params=params)
1951
    assert resp.json == {}
1952
    assert requests_get.call_args_list[0][0][0] == 'example.com'
1953
    assert requests_get.call_args_list[0][1]['params'] == {'q': ['42 avenue']}
1954
    with mock.patch('authentic2.api_views.requests.get') as requests_get:
1955
        mock_resp = Response()
1956
        mock_resp.status_code = 404
1957
        requests_get.return_value = mock_resp
1958
        resp = app.get('/api/address-autocomplete/', params=params)
1959
    assert resp.json == {}
1960
    with mock.patch('authentic2.api_views.requests.get') as requests_get:
1961
        requests_get.return_value = MockedRequestResponse(content=json.dumps({'data': {'foo': 'bar'}}))
1962
        resp = app.get('/api/address-autocomplete/', params=params)
1963
    assert resp.json == {'data': {'foo': 'bar'}}
1964

  
1965
    settings.ADDRESS_AUTOCOMPLETE_URL = None
1966
    with mock.patch('authentic2.api_views.requests.get') as requests_get:
1967
        resp = app.get('/api/address-autocomplete/', params=params)
1968
    assert resp.json == {}
1969
    assert requests_get.call_args_list == []
1970

  
1971
    del settings.ADDRESS_AUTOCOMPLETE_URL
1972
    with mock.patch('authentic2.api_views.requests.get') as requests_get:
1973
        resp = app.get('/api/address-autocomplete/', params=params)
1974
    assert resp.json == {}
1975
    assert requests_get.call_args_list == []
tests/test_user_manager.py
733 733
    assert not user.email_verified
734 734

  
735 735

  
736
def test_manager_edit_user_address_autocomplete(app, simple_user, superuser_or_admin):
737
    url = u'/manage/users/%s/edit/' % simple_user.pk
738
    login(app, superuser_or_admin, '/manage/')
739

  
740
    Attribute.objects.create(
741
        name='address_autocomplete', label='Address (autocomplete)',
742
        kind='address_auto', user_visible=True, user_editable=True)
743

  
744
    resp = app.get(url)
745
    assert resp.html.find('select', {'name': 'address_autocomplete'})
746
    assert resp.html.find('input', {'id': 'manual-address'})
747

  
748

  
736 749
def test_manager_email_verified_column_user(app, simple_user, superuser_or_admin):
737 750
    login(app, superuser_or_admin, '/manage/')
738 751

  
......
793 806
    assert resp.html.find('input', {'name': 'username'})
794 807

  
795 808

  
809
def test_manager_user_address_autocomplete_field(app, superuser, simple_user):
810
    login(app, superuser, '/manage/')
811
    Attribute.objects.create(
812
        name='address_autocomplete', label='Address (autocomplete)',
813
        kind='address_auto', user_visible=True, user_editable=True)
814
    resp = app.get(reverse('a2-manager-user-detail', kwargs={'pk': simple_user.id}))
815
    assert not resp.html.find('select', {'name': 'address_autocomplete'})
816
    assert not resp.html.find('input', {'id': 'manual-address'})
817

  
818

  
796 819
def test_manager_user_roles_visibility(app, simple_user, admin, ou1, ou2):
797 820
    Role = get_role_model()
798 821
    role1 = Role.objects.create(name='Role 1', slug='role1', ou=ou1)
799
-