Projet

Général

Profil

0001-api-add-data-and-geojson-views-covering-all-formdata.patch

Frédéric Péters, 28 mai 2017 10:46

Télécharger (13 ko)

Voir les différences:

Subject: [PATCH] api: add data and geojson views covering all formdatas
 (#14260)

 help/fr/api-get.page |  64 +++++++++++++++++++++++++++++++
 tests/test_api.py    | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++
 wcs/api.py           | 100 +++++++++++++++++++++++++++++++++++-------------
 wcs/formdata.py      |   6 ++-
 4 files changed, 248 insertions(+), 28 deletions(-)
help/fr/api-get.page
358 358

  
359 359
</section>
360 360

  
361
<section id="global-data">
362
  <title>Données de l'ensemble des formulaires</title>
363

  
364
  <p>
365
   De manière similaire à l'API de récupération de la liste des demandes d'un
366
   formulaire, il est possible de récupérer l'ensemble des demandes de la
367
   plateforme, peu importe leurs types.
368
  </p>
369

  
370
<screen>
371
<output style="prompt">$ </output><input>curl -H "Accept: application/json" \
372
       https://www.example.net/api/forms/</input>
373
</screen>
374

  
375
<code mime="application/json">
376
[
377
    {
378
        url: "https://www.example.net/inscriptions/1/",
379
        last_update_time: "2015-03-26T23:08:45",
380
        receipt_time: "2015-03-26T23:08:44",
381
        id: 1
382
    },
383
    {
384
        url: "https://www.example.net/inscriptions/3/",
385
        last_update_time: "2015-03-27T12:11:21",
386
        receipt_time: "2015-03-27T12:45:19",
387
        id: 3
388
    },
389
    {
390
        url: "https://www.example.net/signalement/1/",
391
        last_update_time: "2015-03-25T14:14:21",
392
        receipt_time: "2015-03-25T14:48:20",
393
        id: 1
394
    }
395
]
396
</code>
397

  
398
<p>
399
Des paramètres peuvent être envoyés dans la requête pour filtrer les résultats.
400
Il s'agit des mêmes paramètres que ceux du tableau global en backoffice.
401
Par exemple, pour avoir une liste limitée aux demandes terminées :
402
</p>
403

  
404
<screen>
405
<output style="prompt">$ </output><input>curl -H "Accept: application/json" \
406
       https://www.example.net/api/forms/?status=done</input>
407
</screen>
408

  
409
<note><p>
410
Le paramètre <code>full</code> n'est pas pris en charge dans cette API; le
411
paramètre <code>anonymise</code> non plus, les données l'étant déjà.
412
</p></note>
413

  
414
</section>
361 415

  
362 416
<section id="geolocation">
363 417
<title>Données géolocalisées</title>
......
400 454
Les URL retournées pour les demandes pointent vers l'interface de gestion de celles-ci.
401 455
</p></note>
402 456

  
457
<p>
458
Il est également possible d'obtenir les informations géographiques de
459
l'ensemble des demandes :
460
</p>
461

  
462
<screen>
463
<output style="prompt">$ </output><input>curl -H "Accept: application/json" \
464
       https://www.example.net/api/forms/geojson</input>
465
</screen>
466

  
403 467
</section>
404 468

  
405 469
<section id="tracking-code">
tests/test_api.py
1446 1446
    formdef.store()
1447 1447
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=404)
1448 1448

  
1449
def test_api_global_geojson(pub, local_user):
1450
    Role.wipe()
1451
    role = Role(name='test')
1452
    role.store()
1453

  
1454
    FormDef.wipe()
1455
    formdef = FormDef()
1456
    formdef.name = 'test'
1457
    formdef.workflow_roles = {'_receiver': role.id}
1458
    formdef.fields = []
1459
    formdef.store()
1460

  
1461
    data_class = formdef.data_class()
1462
    data_class.wipe()
1463

  
1464
    formdef.geolocations = {'base': 'Location'}
1465
    formdef.store()
1466

  
1467
    for i in range(30):
1468
        formdata = data_class()
1469
        date = time.strptime('2014-01-20', '%Y-%m-%d')
1470
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
1471
        formdata.user_id = local_user.id
1472
        formdata.just_created()
1473
        if i%3 == 0:
1474
            formdata.jump_status('new')
1475
        else:
1476
            formdata.jump_status('finished')
1477
        formdata.store()
1478

  
1479
    if not pub.is_using_postgresql():
1480
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
1481
        pytest.skip('this requires SQL')
1482
        return
1483

  
1484
    # check empty content if user doesn't have the appropriate role
1485
    resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user))
1486
    assert 'features' in resp.json
1487
    assert len(resp.json['features']) == 0
1488

  
1489
    # add proper role to user
1490
    local_user.roles = [role.id]
1491
    local_user.store()
1492

  
1493
    # check it gets the data
1494
    resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user))
1495
    assert 'features' in resp.json
1496
    assert len(resp.json['features']) == 10
1497

  
1498
    # check with a filter
1499
    resp = get_app(pub).get(sign_uri('/api/forms/geojson?status=done', user=local_user))
1500
    assert 'features' in resp.json
1501
    assert len(resp.json['features']) == 20
1502

  
1503
def test_api_global_listing(pub, local_user):
1504
    Role.wipe()
1505
    role = Role(name='test')
1506
    role.store()
1507

  
1508
    FormDef.wipe()
1509
    formdef = FormDef()
1510
    formdef.name = 'test'
1511
    formdef.workflow_roles = {'_receiver': role.id}
1512
    formdef.fields = [
1513
        fields.StringField(id='0', label='foobar', varname='foobar'),
1514
        ]
1515
    formdef.store()
1516

  
1517
    data_class = formdef.data_class()
1518
    data_class.wipe()
1519

  
1520
    formdef.store()
1521

  
1522
    for i in range(30):
1523
        formdata = data_class()
1524
        date = time.strptime('2014-01-20', '%Y-%m-%d')
1525
        formdata.data = {'0': 'FOO BAR'}
1526
        formdata.user_id = local_user.id
1527
        formdata.just_created()
1528
        if i%3 == 0:
1529
            formdata.jump_status('new')
1530
        else:
1531
            formdata.jump_status('finished')
1532
        formdata.store()
1533

  
1534
    if not pub.is_using_postgresql():
1535
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
1536
        pytest.skip('this requires SQL')
1537
        return
1538

  
1539
    # check empty content if user doesn't have the appropriate role
1540
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
1541
    assert len(resp.json['data']) == 0
1542

  
1543
    # add proper role to user
1544
    local_user.roles = [role.id]
1545
    local_user.store()
1546

  
1547
    # check it gets the data
1548
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
1549
    assert len(resp.json['data']) == 10
1550

  
1551
    # check with a filter
1552
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done', user=local_user))
1553
    assert len(resp.json['data']) == 20
1554

  
1449 1555
def test_roles(pub, local_user):
1450 1556
    Role.wipe()
1451 1557
    role = Role(name='Hello World')
wcs/api.py
22 22
import sys
23 23

  
24 24
from quixote import get_request, get_publisher, get_response, get_session, redirect
25
from quixote.directory import Directory
25
from quixote.directory import Directory, AccessControlled
26 26

  
27 27
from qommon import _
28 28
from qommon import misc
......
39 39
from wcs.api_utils import is_url_signed, get_user_from_api_query_string
40 40

  
41 41
from backoffice.management import FormPage as BackofficeFormPage
42
from backoffice.management import ManagementDirectory
42 43

  
43 44
def posted_json_data_to_formdata_data(formdef, data):
44 45
    # remap fields from varname to field id
......
68 69
    return data
69 70

  
70 71

  
72
def get_formdata_dict(formdata, user, consider_status_visibility=True):
73
    if consider_status_visibility:
74
        status = formdata.get_visible_status(user=user)
75
        if not status:
76
            # skip hidden forms
77
            return None
78
    else:
79
        status = formdata.get_status()
80

  
81
    title = _('%(name)s #%(id)s (%(status)s)') % {
82
            'name': formdata.formdef.name,
83
            'id': formdata.get_display_id(),
84
            'status': status.name,
85
    }
86
    d = {'title': title,
87
        'name': formdata.formdef.name,
88
        'url': formdata.get_url(),
89
        'datetime': misc.strftime.strftime('%Y-%m-%d %H:%M:%S', formdata.receipt_time),
90
        'status': status.name,
91
        'status_css_class': status.extra_css_class,
92
        'keywords': formdata.formdef.keywords_list,
93
    }
94
    d.update(formdata.get_substitution_variables(minimal=True))
95
    if get_request().form.get('full') == 'on':
96
        d.update(formdata.get_json_export_dict(include_files=False))
97
    return d
98

  
99

  
71 100
class ApiFormdataPage(FormStatusPage):
72 101
    _q_exports_orig = ['', 'download']
73 102

  
......
145 174
        return ApiFormdataPage(self.formdef, formdata)
146 175

  
147 176

  
148
class ApiFormsDirectory(Directory):
149
    def _q_lookup(self, component):
177
class ApiFormsDirectory(AccessControlled, Directory):
178
    _q_exports = ['', 'geojson']
179

  
180
    def _q_access(self):
150 181
        if not is_url_signed():
151 182
            # grant access to admins, to ease debug
152 183
            if not (get_request().user and get_request().user.is_admin):
153 184
                raise AccessForbiddenError('user not authenticated')
185

  
186
    def _q_index(self):
187
        if not get_publisher().is_using_postgresql():
188
            raise TraversalError()
189

  
190
        get_request().user = get_user_from_api_query_string() or get_request().user
191

  
192
        from wcs import sql
193

  
194
        management_directory = ManagementDirectory()
195
        criterias = management_directory.get_global_listing_criterias()
196

  
197
        limit = int(get_request().form.get('limit',
198
            get_publisher().get_site_option('default-page-size') or 20))
199
        offset = int(get_request().form.get('offset', 0))
200
        order_by = get_request().form.get('order_by',
201
            get_publisher().get_site_option('default-sort-order') or '-receipt_time')
202

  
203
        output = [get_formdata_dict(x, user=get_request().user, consider_status_visibility=False)
204
                  for x in sql.AnyFormData.select(
205
                      criterias, order_by=order_by, limit=limit, offset=offset)]
206

  
207
        get_response().set_content_type('application/json')
208
        return json.dumps({'data': output},
209
                cls=misc.JSONEncoder,
210
                encoding=get_publisher().site_charset)
211

  
212

  
213
    def geojson(self):
214
        if not get_publisher().is_using_postgresql():
215
            raise TraversalError()
216
        get_request().user = get_user_from_api_query_string() or get_request().user
217
        return ManagementDirectory().geojson()
218

  
219
    def _q_lookup(self, component):
154 220
        return ApiFormPage(component)
155 221

  
156 222

  
......
485 551
        for form in self.get_user_forms(user):
486 552
            if form.is_draft():
487 553
                continue
488
            visible_status = form.get_visible_status(user=user)
489
            # skip hidden forms
490
            if not visible_status:
554
            formdata_dict = get_formdata_dict(form, user)
555
            if not formdata_dict:
556
                # skip hidden forms
491 557
                continue
492
            name = form.formdef.name
493
            id = form.get_display_id()
494
            status = visible_status.name
495
            title = _('%(name)s #%(id)s (%(status)s)') % {
496
                    'name': name,
497
                    'id': id,
498
                    'status': status
499
            }
500
            url = form.get_url()
501
            d = {'title': title,
502
                'name': form.formdef.name,
503
                'url': url,
504
                'datetime': misc.strftime.strftime('%Y-%m-%d %H:%M:%S', form.receipt_time),
505
                'status': status,
506
                'status_css_class': visible_status.extra_css_class,
507
                'keywords': form.formdef.keywords_list,
508
            }
509
            d.update(form.get_substitution_variables(minimal=True))
510
            if get_request().form.get('full') == 'on':
511
                d.update(form.get_json_export_dict(include_files=False))
512
            forms.append(d)
558
            forms.append(formdata_dict)
513 559

  
514 560
        return json.dumps(forms,
515 561
                cls=misc.JSONEncoder,
wcs/formdata.py
30 30
from qommon.storage import StorableObject, Intersects, Contains
31 31
import qommon.misc
32 32
from qommon import ezt
33
from qommon.evalutils import make_datetime
33 34
from qommon.substitution import Substitutions
34 35

  
35 36
from roles import Role
......
555 556
                'form_criticality_level': self.criticality_level,
556 557
            })
557 558
            if self.receipt_time:
558
                d['form_receipt_datetime'] = datetime.datetime(*self.receipt_time[:6])
559
                # always get receipt time as a datetime object, this handles
560
                # both normal formdata (where receipt_time is a time.struct_time)
561
                # and sql.AnyFormData where it's already a datetime object.
562
                d['form_receipt_datetime'] = make_datetime(self.receipt_time)
559 563

  
560 564
        d['form_status'] = self.get_status_label()
561 565

  
562
-