Projet

Général

Profil

0001-csvdatasource-remove-advanced-lookup-filters-13748.patch

Serghei Mihai, 13 novembre 2018 16:45

Télécharger (9,1 ko)

Voir les différences:

Subject: [PATCH] csvdatasource: remove advanced lookup filters (#13748)

 passerelle/apps/csvdatasource/lookups.py | 70 ------------------------
 passerelle/apps/csvdatasource/models.py  | 31 ++++++-----
 passerelle/apps/csvdatasource/views.py   | 28 ++--------
 tests/test_csv_datasource.py             | 35 +-----------
 4 files changed, 22 insertions(+), 142 deletions(-)
 delete mode 100644 passerelle/apps/csvdatasource/lookups.py
passerelle/apps/csvdatasource/lookups.py
1
DELIMITER = '__'
2

  
3
class InvalidOperatorError(Exception):
4
    pass
5

  
6
compare_str = cmp
7

  
8

  
9
def is_int(value):
10
    try:
11
        int(value)
12
        return True
13
    except (ValueError, TypeError):
14
        return False
15

  
16

  
17
class Lookup(object):
18

  
19
    def contains(self, key, value):
20
        return lambda x: value in x[key]
21

  
22
    def icontains(self, key, value):
23
        return lambda x: value.lower() in x[key].lower()
24

  
25
    def gt(self, key, value):
26
        return lambda x: int(x[key]) > int(value)
27

  
28
    def igt(self, key, value):
29
        return lambda x: compare_str(x[key].lower(), value.lower()) > 0
30

  
31
    def ge(self, key, value):
32
        return lambda x: int(x[key]) >= int(value)
33

  
34
    def ige(self, key, value):
35
        return lambda x: compare_str(x[key].lower(), value.lower()) >= 0
36

  
37
    def lt(self, key, value):
38
        return lambda x: int(x[key]) < int(value)
39

  
40
    def ilt(self, key, value):
41
        return lambda x: compare_str(x[key].lower(), value.lower()) < 0
42

  
43
    def le(self, key, value):
44
        return lambda x: int(x[key]) <= int(value)
45

  
46
    def ile(self, key, value):
47
        return lambda x: compare_str(x[key].lower(), value.lower()) <= 0
48

  
49
    def eq(self, key, value):
50
        if is_int(value):
51
            return lambda x: int(value) == int(x[key])
52
        return lambda x: value == x[key]
53

  
54
    def ieq(self, key, value):
55
        return lambda x: value.lower() == x[key].lower()
56

  
57
    def ne(self, key, value):
58
        if is_int(value):
59
            return lambda x: int(value) != int(x[key])
60
        return lambda x: value != x[key]
61

  
62
    def ine(self, key, value):
63
        return lambda x: value.lower() != x[key].lower()
64

  
65

  
66
def get_lookup(operator, key, value):
67
    try:
68
        return getattr(Lookup(), operator)(key, value)
69
    except (AttributeError,):
70
        raise InvalidOperatorError('%s is not a valid operator' % operator)
passerelle/apps/csvdatasource/models.py
35 35
from passerelle.utils.jsonresponse import APIError
36 36
from passerelle.utils.api import endpoint
37 37

  
38
import lookups
39

  
40 38
identifier_re = re.compile(r"^[^\d\W]\w*\Z", re.UNICODE)
41 39

  
42 40

  
......
242 240
            for data in self.get_cached_rows(initial=False):
243 241
                yield data
244 242

  
245
    def get_data(self, filters=None):
243
    def get_data(self, filters={}, case_insensitive=False):
246 244
        titles = [t.strip() for t in self.columns_keynames.split(',')]
247 245

  
248
        # validate filters (appropriate columns must exist)
249
        if filters:
250
            for filter_key in filters.keys():
251
                if not filter_key.split(lookups.DELIMITER)[0] in titles:
252
                    del filters[filter_key]
246
        for filter_key in filters.keys():
247
            # allow 'q' filter
248
            if filter_key == 'q':
249
                continue
250
            if filter_key not in titles:
251
                del filters[filter_key]
253 252

  
254 253
        rows = self.get_cached_rows()
255 254
        data = []
256 255

  
257 256
        # build a generator of all filters
258
        def filters_generator(filters, titles):
257
        def filters_generator(filters, case_insensitive):
259 258
            if not filters:
260 259
                return
261 260
            for key, value in filters.items():
262
                try:
263
                    key, op = key.split(lookups.DELIMITER)
264
                except (ValueError,):
265
                    op = 'eq'
266
                yield lookups.get_lookup(op, key, value)
261
                def operation(key, value, case_insensitive):
262
                    if key == 'q':
263
                        return lambda x: value.lower() in x['text'].lower()
264
                    if case_insensitive:
265
                        return lambda x: value.lower() == x[key].lower()
266
                    return lambda x: value == x[key]
267
                yield operation(key, value, case_insensitive)
267 268

  
268 269
        # apply filters to data
269 270
        def super_filter(filters, data):
......
272 273
            return data
273 274

  
274 275
        data = list(super_filter(
275
            filters_generator(filters, titles), rows
276
            filters_generator(filters, case_insensitive), rows
276 277
        ))
277 278

  
278 279
        return data
passerelle/apps/csvdatasource/views.py
34 34
    def _filters_builder(self, request):
35 35
        filters = {}
36 36
        obj = self.get_object()
37

  
38 37
        params = request.GET
39

  
40
        case_insensitive = 'case-insensitive' in params
41 38
        query = params.get('q', None)
42 39

  
43 40
        if query:
44
            if case_insensitive:
45
                filters['text__icontains'] = query.lower()
46
            else:
47
                filters['text__contains'] = query
41
            filters['q'] = query
48 42

  
49 43
        # builds filters according to csv file header
50 44
        for column_title in [t.strip() for t in obj.columns_keynames.split(',') if t]:
51
            match = filter(
52
                (lambda ct: lambda x: x.startswith(ct))(column_title), params.keys()
53
            )
54
            for key in match:
55
                if case_insensitive:
56
                    filters[key + '__ieq'] = params[key].lower()
57
                else:
58
                    filters[key] = params[key]
59

  
60
        if 'text' in filters:
61
            if case_insensitive:
62
                filters['text__ieq'] = filters['text'].lower()
63
            else:
64
                filters['text__eq'] = filters['text']
65
            filters.pop('text')
45
            if column_title in params.keys():
46
                filters[column_title] = params[column_title]
66 47

  
67 48
        return filters
68 49

  
......
72 53
    def get(self, request, *args, **kwargs):
73 54
        obj = self.get_object()
74 55
        filters = self._filters_builder(request)
75
        return {'data': obj.get_data(filters)}
56
        case_insensitive = 'case-insensitive' in request.GET
57
        return {'data': obj.get_data(filters, case_insensitive)}
76 58

  
77 59

  
78 60
class NewQueryView(CreateView):
tests/test_csv_datasource.py
234 234
    assert result[0]['text'] == 'Eliot'
235 235
    assert len(result) == 1
236 236

  
237
def test_advanced_filters(client, setup, filetype):
238
    csvdata, url = setup(filename=filetype, data=get_file_content(filetype))
239
    filters = {'id__gt':20, 'id__lt': 40}
240
    resp = client.get(url, filters)
241
    result = parse_response(resp)
242
    assert len(result) == 3
243
    for stuff in result:
244
        assert stuff['id'] in ('22', '36', '38')
245

  
246
def test_advanced_filters_combo(client, setup, filetype):
247
    csvdata, url = setup(filename=filetype, data=get_file_content(filetype))
248
    filters = {
249
        'id__ge': '20',
250
        'id__lt': '40',
251
        'fam__gt': '234',
252
        'fam__le': '235',
253
        'fname__icontains': 'Sandra'
254
    }
255
    resp = client.get(url, filters)
256
    result = parse_response(resp)
257
    assert len(result) == 1
258
    assert result[0]['id'] == '22'
259
    assert result[0]['lname'] == 'MARTIN'
260

  
261
def test_unknown_operator(client, setup, filetype):
262
    csvdata, url = setup(filename=filetype, data=get_file_content(filetype))
263
    filters = {'id__whatever': '25', 'fname__icontains':'Eliot'}
264
    resp = client.get(url, filters)
265
    result = json.loads(resp.content)
266
    assert result['err'] == 1
267
    assert result['err_class'] == 'passerelle.apps.csvdatasource.lookups.InvalidOperatorError'
268
    assert result['err_desc'] == 'whatever is not a valid operator'
269

  
270 237

  
271 238
def test_dialect(client, setup):
272 239
    csvdata, url = setup(data=data)
......
280 247
    }
281 248

  
282 249
    assert expected == csvdata.dialect_options
283
    filters = {'id__gt': '20', 'id__lt': '40', 'fname__icontains': 'Sandra'}
250
    filters = {'id': '22', 'fname': 'Sandra'}
284 251
    resp = client.get(url, filters)
285 252
    result = parse_response(resp)
286 253
    assert len(result) == 1
287
-