Projet

Général

Profil

0006-toulouse-maelis-add-search-on-referentials-70982.patch

Nicolas Roche, 08 décembre 2022 17:28

Télécharger (21,8 ko)

Voir les différences:

Subject: [PATCH 6/7] toulouse-maelis: add search on referentials (#70982)

 .../0003_referential_item_unaccent_text.py    |  18 ++
 passerelle/contrib/toulouse_maelis/models.py  | 186 ++++++++++++++----
 tests/test_toulouse_maelis.py                 |  36 ++++
 3 files changed, 203 insertions(+), 37 deletions(-)
 create mode 100644 passerelle/contrib/toulouse_maelis/migrations/0003_referential_item_unaccent_text.py
passerelle/contrib/toulouse_maelis/migrations/0003_referential_item_unaccent_text.py
1
# Generated by Django 2.2.26 on 2022-12-08 16:11
2

  
3
from django.db import migrations, models
4

  
5

  
6
class Migration(migrations.Migration):
7

  
8
    dependencies = [
9
        ('toulouse_maelis', '0002_referential'),
10
    ]
11

  
12
    operations = [
13
        migrations.AddField(
14
            model_name='referential',
15
            name='item_unaccent_text',
16
            field=models.CharField(max_length=64, null=True, verbose_name='Text'),
17
        ),
18
    ]
passerelle/contrib/toulouse_maelis/models.py
18 18
import zeep
19 19
from django.contrib.postgres.fields import JSONField
20 20
from django.db import models
21 21
from zeep.helpers import serialize_object
22 22
from zeep.wsse.username import UsernameToken
23 23

  
24 24
from passerelle.base.models import BaseResource, HTTPResource
25 25
from passerelle.utils.api import endpoint
26
from passerelle.utils.conversion import simplify
26 27
from passerelle.utils.jsonresponse import APIError
27 28

  
28 29
from . import schemas
29 30

  
30 31

  
31 32
class UpdateError(Exception):
32 33
    pass
33 34

  
......
115 116
        for item in response:
116 117
            text = item[text_key].strip()
117 118
            self.referential.update_or_create(
118 119
                resource_id=self.id,
119 120
                referential_name=referential_name,
120 121
                item_id=item[id_key],
121 122
                defaults={
122 123
                    'item_text': text,
124
                    'item_unaccent_text': simplify(text),
123 125
                    'item_data': dict({'id': item[id_key], 'text': text}, **item),
124 126
                },
125 127
            )
126 128

  
127 129
        # clean item removed from referential
128 130
        if latest_update is not None:
129 131
            self.referential.filter(referential_name=referential_name, updated=latest_update).delete()
130 132

  
......
153 155
    def daily(self):
154 156
        try:
155 157
            self.update_referentials()
156 158
        except UpdateError as e:
157 159
            self.logger.warning('Erreur sur la mise à jour: %s' % e)
158 160
        else:
159 161
            self.logger.info('Réferentiels mis à jour.')
160 162

  
161
    def get_referential(self, referential_name):
162
        return [
163
            x.item_data
164
            for x in self.referential.distinct('referential_name', 'item_text').filter(
165
                referential_name=referential_name
166
            )
167
        ]
163
    def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
164
        if id is not None:
165
            queryset = self.referential.filter(referential_name=referential_name, item_id=id)
166
        else:
167
            queryset = self.referential.filter(referential_name=referential_name).all()
168
            if q:
169
                queryset = queryset.filter(item_unaccent_text__icontains=simplify(q))
170

  
171
        if distinct:
172
            queryset = queryset.distinct('referential_name', 'item_text')
173

  
174
        if limit:
175
            try:
176
                limit = int(limit)
177
            except ValueError:
178
                pass
179
            else:
180
                queryset = queryset[:limit]
181

  
182
        return [x.item_data for x in queryset]
168 183

  
169 184
    def get_referential_value(self, referential_name, key):
170 185
        try:
171 186
            return self.referential.get(referential_name=referential_name, item_id=key).item_text
172 187
        except Referential.DoesNotExist:
173 188
            self.logger.warning("No '%s' key into Maelis '%s' referential", key, referential_name)
174 189
            return key
175 190

  
......
406 421
            if value is None:
407 422
                dico[key] = ''
408 423

  
409 424
    @endpoint(
410 425
        display_category='Famille',
411 426
        description='Liste des catégories',
412 427
        name='read-category-list',
413 428
        perm='can_access',
429
        parameters={
430
            'id': {'description': 'Identifiant de l’enregistrement'},
431
            'q': {'description': 'Recherche en texte intégral'},
432
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
433
            'distinct': {'description': 'Supression des doublons'},
434
        },
414 435
    )
415
    def read_category_list(self, request):
416
        return {'data': self.get_referential('Category')}
436
    def read_category_list(self, request, id=None, q=None, limit=None, distinct=True):
437
        return {'data': self.get_referential('Category', id, q, limit, distinct)}
417 438

  
418 439
    @endpoint(
419 440
        display_category='Famille',
420 441
        description='Liste des indicateurs sur le enfants',
421 442
        name='read-child-indicator-list',
422 443
        perm='can_access',
444
        parameters={
445
            'id': {'description': 'Identifiant de l’enregistrement'},
446
            'q': {'description': 'Recherche en texte intégral'},
447
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
448
            'distinct': {'description': 'Supression des doublons'},
449
        },
423 450
    )
424
    def read_child_indicator_list(self, request):
425
        return {'data': self.get_referential('ChildIndicator')}
451
    def read_child_indicator_list(self, request, id=None, q=None, limit=None, distinct=True):
452
        return {'data': self.get_referential('ChildIndicator', id, q, limit, distinct)}
426 453

  
427 454
    @endpoint(
428 455
        display_category='Famille',
429 456
        description='Liste des civilités',
430 457
        name='read-civility-list',
431 458
        perm='can_access',
459
        parameters={
460
            'id': {'description': 'Identifiant de l’enregistrement'},
461
            'q': {'description': 'Recherche en texte intégral'},
462
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
463
            'distinct': {'description': 'Supression des doublons'},
464
        },
432 465
    )
433
    def read_civility_list(self, request):
434
        return {'data': self.get_referential('Civility')}
466
    def read_civility_list(self, request, id=None, q=None, limit=None, distinct=True):
467
        return {'data': self.get_referential('Civility', id, q, limit, distinct)}
435 468

  
436 469
    @endpoint(
437 470
        display_category='Famille',
438 471
        description='Liste des compléments du numéro de voie',
439 472
        name='read-complement-list',
440 473
        perm='can_access',
474
        parameters={
475
            'id': {'description': 'Identifiant de l’enregistrement'},
476
            'q': {'description': 'Recherche en texte intégral'},
477
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
478
            'distinct': {'description': 'Supression des doublons'},
479
        },
441 480
    )
442
    def read_complement_list(self, request):
443
        return {'data': self.get_referential('Complement')}
481
    def read_complement_list(self, request, id=None, q=None, limit=None, distinct=True):
482
        return {'data': self.get_referential('Complement', id, q, limit, distinct)}
444 483

  
445 484
    @endpoint(
446 485
        display_category='Famille',
447 486
        description='Liste des pays',
448 487
        name='read-country-list',
449 488
        perm='can_access',
489
        parameters={
490
            'id': {'description': 'Identifiant de l’enregistrement'},
491
            'q': {'description': 'Recherche en texte intégral'},
492
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
493
            'distinct': {'description': 'Supression des doublons'},
494
        },
450 495
    )
451
    def read_country_list(self, request):
452
        return {'data': self.get_referential('Country')}
496
    def read_country_list(self, request, id=None, q=None, limit=None, distinct=True):
497
        return {'data': self.get_referential('Country', id, q, limit, distinct)}
453 498

  
454 499
    @endpoint(
455 500
        display_category='Famille',
456 501
        description='liste des catégories socio-professionnelles',
457 502
        name='read-csp-list',
458 503
        perm='can_access',
504
        parameters={
505
            'id': {'description': 'Identifiant de l’enregistrement'},
506
            'q': {'description': 'Recherche en texte intégral'},
507
            'limit': {'description': 'Nombre maximal de résultats'},
508
            'distinct': {'description': 'Supression des doublons'},
509
        },
459 510
    )
460
    def read_csp_list(self, request):
461
        return {'data': self.get_referential('CSP')}
511
    def read_csp_list(self, request, id=None, q=None, limit=None, distinct=True):
512
        return {'data': self.get_referential('CSP', id, q, limit, distinct)}
462 513

  
463 514
    @endpoint(
464 515
        display_category='Famille',
465 516
        description='Liste des régimes alimentaires',
466 517
        name='read-dietcode-list',
467 518
        perm='can_access',
519
        parameters={
520
            'id': {'description': 'Identifiant de l’enregistrement'},
521
            'q': {'description': 'Recherche en texte intégral'},
522
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
523
            'distinct': {'description': 'Supression des doublons'},
524
        },
468 525
    )
469
    def read_dietcode_list(self, request):
470
        return {'data': self.get_referential('DietCode')}
526
    def read_dietcode_list(self, request, id=None, q=None, limit=None, distinct=True):
527
        return {'data': self.get_referential('DietCode', id, q, limit, distinct)}
471 528

  
472 529
    @endpoint(
473 530
        display_category='Famille',
474 531
        description='Liste des organismes (CAF)',
475 532
        name='read-organ-list',
476 533
        perm='can_access',
534
        parameters={
535
            'id': {'description': 'Identifiant de l’enregistrement'},
536
            'q': {'description': 'Recherche en texte intégral'},
537
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
538
            'distinct': {'description': 'Supression des doublons'},
539
        },
477 540
    )
478
    def read_organ_list(self, request):
541
    def read_organ_list(self, request, id=None, q=None, limit=None, distinct=True):
479 542
        return {'data': self.get_referential('Organ')}
480 543

  
481 544
    @endpoint(
482 545
        display_category='Famille',
483 546
        description="Liste des projet d'accueil individualisés",
484 547
        name='read-pai-list',
485 548
        perm='can_access',
549
        parameters={
550
            'id': {'description': 'Identifiant de l’enregistrement'},
551
            'q': {'description': 'Recherche en texte intégral'},
552
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
553
            'distinct': {'description': 'Supression des doublons'},
554
        },
486 555
    )
487
    def read_pai_list(self, request):
488
        return {'data': self.get_referential('PAI')}
556
    def read_pai_list(self, request, id=None, q=None, limit=None, distinct=True):
557
        return {'data': self.get_referential('PAI', id, q, limit, distinct)}
489 558

  
490 559
    @endpoint(
491 560
        display_category='Famille',
492 561
        description='liste des qualités du référenciel',
493 562
        name='read-quality-list',
494 563
        perm='can_access',
564
        parameters={
565
            'id': {'description': 'Identifiant de l’enregistrement'},
566
            'q': {'description': 'Recherche en texte intégral'},
567
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
568
            'distinct': {'description': 'Supression des doublons'},
569
        },
495 570
    )
496
    def read_quality_list(self, request):
497
        return {'data': self.get_referential('Quality')}
571
    def read_quality_list(self, request, id=None, q=None, limit=None, distinct=True):
572
        return {'data': self.get_referential('Quality', id, q, limit, distinct)}
498 573

  
499 574
    @endpoint(
500 575
        display_category='Famille',
501 576
        description='Liste des quotients',
502 577
        name='read-quotient-list',
503 578
        perm='can_access',
579
        parameters={
580
            'id': {'description': 'Identifiant de l’enregistrement'},
581
            'q': {'description': 'Recherche en texte intégral'},
582
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
583
            'distinct': {'description': 'Supression des doublons'},
584
        },
504 585
    )
505
    def read_quotient_list(self, request):
506
        return {'data': self.get_referential('Quotient')}
586
    def read_quotient_list(self, request, id=None, q=None, limit=None, distinct=True):
587
        return {'data': self.get_referential('Quotient', id, q, limit, distinct)}
507 588

  
508 589
    @endpoint(
509 590
        display_category='Famille',
510 591
        description='Liste des indicateurs sur les responsables légaux',
511 592
        name='read-rl-indicator-list',
512 593
        perm='can_access',
594
        parameters={
595
            'id': {'description': 'Identifiant de l’enregistrement'},
596
            'q': {'description': 'Recherche en texte intégral'},
597
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
598
            'distinct': {'description': 'Supression des doublons'},
599
        },
513 600
    )
514
    def read_rl_indicator_list(self, request):
601
    def read_rl_indicator_list(self, request, id=None, q=None, limit=None, distinct=True):
515 602
        return {'data': self.get_referential('RLIndicator')}
516 603

  
517 604
    @endpoint(
518 605
        display_category='Famille',
519 606
        description='Liste des sexes',
520 607
        name='read-sex-list',
521 608
        perm='can_access',
609
        parameters={
610
            'id': {'description': 'Identifiant de l’enregistrement'},
611
            'q': {'description': 'Recherche en texte intégral'},
612
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
613
            'distinct': {'description': 'Supression des doublons'},
614
        },
522 615
    )
523
    def read_sex_list(self, request):
524
        return {'data': self.get_referential('Sex')}
616
    def read_sex_list(self, request, id=None, q=None, limit=None, distinct=True):
617
        return {'data': self.get_referential('Sex', id, q, limit, distinct)}
525 618

  
526 619
    @endpoint(
527 620
        display_category='Famille',
528 621
        description='liste des situations',
529 622
        name='read-situation-list',
530 623
        perm='can_access',
624
        parameters={
625
            'id': {'description': 'Identifiant de l’enregistrement'},
626
            'q': {'description': 'Recherche en texte intégral'},
627
            'limit': {'description': 'Nombre maximal de résultats'},
628
            'distinct': {'description': 'Supression des doublons'},
629
        },
531 630
    )
532
    def read_situation_list(self, request):
533
        return {'data': self.get_referential('Situation')}
631
    def read_situation_list(self, request, id=None, q=None, limit=None, distinct=True):
632
        return {'data': self.get_referential('Situation', id, q, limit, distinct)}
534 633

  
535 634
    @endpoint(
536 635
        display_category='Famille',
537 636
        description='liste des voies',
538 637
        name='read-street-list',
539 638
        perm='can_access',
639
        parameters={
640
            'id': {'description': 'Identifiant de l’enregistrement'},
641
            'q': {'description': 'Recherche en texte intégral'},
642
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
643
            'distinct': {'description': 'Supression des doublons'},
644
        },
540 645
    )
541
    def read_street_list(self, request):
542
        return {'data': self.get_referential('Street')}
646
    def read_street_list(self, request, id=None, q=None, limit=None, distinct=True):
647
        return {'data': self.get_referential('Street', id, q, limit, distinct)}
543 648

  
544 649
    @endpoint(
545 650
        display_category='Famille',
546 651
        description='Liste des vaccins',
547 652
        name='read-vaccin-list',
548 653
        perm='can_access',
654
        parameters={
655
            'id': {'description': 'Identifiant de l’enregistrement'},
656
            'q': {'description': 'Recherche en texte intégral'},
657
            'limit': {'description': 'Nombre maximal de résultats; doit être inférieur à 20.'},
658
            'distinct': {'description': 'Supression des doublons'},
659
        },
549 660
    )
550
    def read_vaccin_list(self, request):
551
        return {'data': self.get_referential('Vaccin')}
661
    def read_vaccin_list(self, request, id=None, q=None, limit=None, distinct=True):
662
        return {'data': self.get_referential('Vaccin', id, q, limit, distinct)}
552 663

  
553 664
    @endpoint(
554 665
        display_category='Famille',
555 666
        description='Lier un compte usager à une famille',
556 667
        perm='can_access',
557 668
        parameters={'NameID': {'description': 'Publik NameID'}},
558 669
        post={'request_body': {'schema': {'application/json': schemas.LINK_SCHEMA}}},
559 670
    )
......
1217 1328
        verbose_name='Resource',
1218 1329
        to=ToulouseMaelis,
1219 1330
        on_delete=models.CASCADE,
1220 1331
        related_name='referential',
1221 1332
    )
1222 1333
    referential_name = models.CharField('Name', max_length=64)
1223 1334
    item_id = models.CharField('Key', max_length=64)
1224 1335
    item_text = models.CharField('Text', max_length=128)
1336
    item_unaccent_text = models.CharField('Text', max_length=64, null=True)
1225 1337
    item_data = JSONField('Data')
1226 1338
    created = models.DateTimeField('Created', auto_now_add=True)
1227 1339
    updated = models.DateTimeField('Updated', auto_now=True)
1228 1340

  
1229 1341
    def __repr__(self):
1230 1342
        return '<Referential "%s/%s">' % (self.referential_name, self.item_id)
1231 1343

  
1232 1344
    class Meta:
tests/test_toulouse_maelis.py
414 414
        {'id': 'T', 'text': 'ter'},
415 415
    ]
416 416

  
417 417

  
418 418
def test_read_csp_list(con, app):
419 419
    url = get_endpoint('read-csp-list')
420 420
    resp = app.get(url)
421 421
    assert resp.json['err'] == 0
422
    assert len(resp.json['data']) == 27
422 423
    assert resp.json['data'][:5] == [
423 424
        {'code': '14', 'id': '14', 'libelle': 'AGENT DE MAITRISE', 'text': 'AGENT DE MAITRISE'},
424 425
        {'code': '1', 'id': '1', 'libelle': 'AGRICULTEUR', 'text': 'AGRICULTEUR'},
425 426
        {'code': 'ART', 'id': 'ART', 'libelle': 'ARTISAN', 'text': 'ARTISAN'},
426 427
        {'code': '2', 'id': '2', 'libelle': 'ARTISAN-COMMERCANT', 'text': 'ARTISAN-COMMERCANT'},
427 428
        {'code': '15', 'id': '15', 'libelle': 'AUTRES', 'text': 'AUTRES'},
428 429
    ]
429 430

  
431
    resp = app.get(url + '?distinct=False')
432
    assert resp.json['err'] == 0
433
    assert len(resp.json['data']) == 43
434
    assert resp.json['data'][:5] == [
435
        {'id': '14', 'code': '14', 'text': 'AGENT DE MAITRISE', 'libelle': 'AGENT DE MAITRISE'},
436
        {'id': '1', 'code': '1', 'text': 'AGRICULTEUR', 'libelle': 'AGRICULTEUR'},
437
        {'id': 'AGR', 'code': 'AGR', 'text': 'AGRICULTEUR', 'libelle': 'AGRICULTEUR'},
438
        {'id': 'ART', 'code': 'ART', 'text': 'ARTISAN', 'libelle': 'ARTISAN'},
439
        {'id': 'ARTI', 'code': 'ARTI', 'text': 'ARTISAN', 'libelle': 'ARTISAN'},
440
    ]
441

  
430 442

  
431 443
def test_read_dietcode_list(con, app):
432 444
    url = get_endpoint('read-dietcode-list')
433 445
    resp = app.get(url)
434 446
    assert resp.json['err'] == 0
435 447
    assert resp.json['data'] == [
436 448
        {'id': 'STD', 'code': 'STD', 'text': '1- REPAS STANDARD', 'libelle': '1- REPAS STANDARD'},
437 449
        {'id': 'RSP', 'code': 'RSP', 'text': '2- RÉGIME SANS PORC', 'libelle': '2- RÉGIME SANS PORC'},
......
542 554
        {'id': 'M', 'text': 'Masculin'},
543 555
    ]
544 556

  
545 557

  
546 558
def test_read_situation_list(con, app):
547 559
    url = get_endpoint('read-situation-list')
548 560
    resp = app.get(url)
549 561
    assert resp.json['err'] == 0
562
    assert len(resp.json['data']) == 9
550 563
    assert resp.json['data'][:5] == [
551 564
        {'id': 'C', 'code': 'C', 'text': 'Célibataire', 'libelle': 'Célibataire'},
552 565
        {'id': 'D', 'code': 'D', 'text': 'Divorcé (e)', 'libelle': 'Divorcé (e)'},
553 566
        {'id': 'CS', 'code': 'CS', 'text': 'EN COURS DE SEPARATION', 'libelle': 'EN COURS DE SEPARATION'},
554 567
        {'id': 'M', 'code': 'M', 'text': 'Marié (e)', 'libelle': 'Marié (e)'},
555 568
        {'id': 'P', 'code': 'P', 'text': 'Pacsé (e)', 'libelle': 'Pacsé (e)'},
556 569
    ]
557 570

  
571
    resp = app.get(url + '?q= ')
572
    assert resp.json['err'] == 0
573
    assert len(resp.json['data']) == 9
574

  
575
    resp = app.get(url + '?id=V')
576
    assert resp.json['err'] == 0
577
    assert resp.json['data'] == [{'id': 'V', 'code': 'V', 'text': 'Veuf (ve)', 'libelle': 'Veuf (ve)'}]
578

  
579
    resp = app.get(url + '?q= sÉ ')
580
    assert resp.json['err'] == 0
581
    assert resp.json['data'] == [
582
        {'id': 'CS', 'code': 'CS', 'text': 'EN COURS DE SEPARATION', 'libelle': 'EN COURS DE SEPARATION'},
583
        {'id': 'P', 'code': 'P', 'text': 'Pacsé (e)', 'libelle': 'Pacsé (e)'},
584
        {'id': 'S', 'code': 'S', 'text': 'Séparé (e)', 'libelle': 'Séparé (e)'},
585
    ]
586

  
587
    resp = app.get(url + '?q= sÉ &limit=2')
588
    assert resp.json['err'] == 0
589
    assert resp.json['data'] == [
590
        {'id': 'CS', 'code': 'CS', 'text': 'EN COURS DE SEPARATION', 'libelle': 'EN COURS DE SEPARATION'},
591
        {'id': 'P', 'code': 'P', 'text': 'Pacsé (e)', 'libelle': 'Pacsé (e)'},
592
    ]
593

  
558 594

  
559 595
def test_read_street_list(con, app):
560 596
    url = get_endpoint('read-street-list')
561 597
    resp = app.get(url)
562 598
    assert resp.json['err'] == 0
563 599
    assert len(resp.json['data']) == 3
564 600
    assert resp.json['data'] == [
565 601
        {'id': 'AP0594', 'text': 'AVENUE PAULIANI', 'idStreet': 'AP0594', 'libelleStreet': 'AVENUE PAULIANI'},
566
-