Projet

Général

Profil

0001-formdefs-store-reverse-relations-on-form-carddefs-57.patch

Lauréline Guérin, 17 novembre 2021 10:43

Télécharger (14,9 ko)

Voir les différences:

Subject: [PATCH] formdefs: store reverse relations on form/carddefs (#57963)

 tests/test_carddef.py | 213 +++++++++++++++++++++++++++++++++++++++++-
 wcs/blocks.py         |  14 +++
 wcs/formdef.py        |  85 +++++++++++++++++
 3 files changed, 311 insertions(+), 1 deletion(-)
tests/test_carddef.py
3 3

  
4 4
import pytest
5 5

  
6
from wcs.blocks import BlockDef
6 7
from wcs.carddef import CardDef
7 8
from wcs.categories import CardDefCategory
8
from wcs.fields import ItemField, StringField
9
from wcs.fields import BlockField, ItemField, ItemsField, StringField
10
from wcs.formdef import FormDef
9 11
from wcs.qommon.http_request import HTTPRequest
10 12
from wcs.qommon.misc import indent_xml as indent
11 13
from wcs.qommon.template import Template
......
583 585
    cards = CardDef.get_data_source_items('carddef:foo', query='_')
584 586
    assert len(cards) == 1
585 587
    assert cards[0]['text'] == 'Astreinte\\Mardi _'
588

  
589

  
590
def test_reverse_relations(pub):
591
    FormDef.wipe()
592
    CardDef.wipe()
593
    BlockDef.wipe()
594

  
595
    formdef1 = FormDef()
596
    formdef1.name = 'formdef 1'
597
    formdef1.store()
598

  
599
    formdef2 = FormDef()
600
    formdef2.name = 'formdef 2'
601
    formdef2.store()
602

  
603
    carddef1 = CardDef()
604
    carddef1.name = 'carddef 1'
605
    carddef1.store()
606

  
607
    carddef2 = CardDef()
608
    carddef2.name = 'carddef 2'
609
    carddef2.store()
610

  
611
    block1 = BlockDef()
612
    block1.name = 'block 1'
613
    block1.fields = [
614
        ItemField(
615
            id='1',
616
            label='item',
617
            type='item',
618
            varname='block_foo_1',
619
            data_source={'type': 'carddef:carddef-1'},
620
        ),
621
        ItemsField(id='2', label='items', type='items', data_source={'type': 'carddef:carddef-1'}),
622
    ]
623
    block1.store()
624

  
625
    assert formdef1.reverse_relations == []
626
    assert formdef2.reverse_relations == []
627
    assert carddef1.reverse_relations == []
628
    assert carddef2.reverse_relations == []
629

  
630
    formdef1.fields = [
631
        ItemField(id='1', label='item', type='item', data_source={'type': 'carddef:carddef-1'}),
632
    ]
633
    formdef1.store()
634

  
635
    formdef1.refresh_from_storage()
636
    formdef2.refresh_from_storage()
637
    carddef1.refresh_from_storage()
638
    carddef2.refresh_from_storage()
639
    assert formdef1.reverse_relations == []
640
    assert formdef2.reverse_relations == []
641
    assert carddef1.reverse_relations == [
642
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
643
    ]
644
    assert carddef2.reverse_relations == []
645

  
646
    formdef2.fields = [
647
        ItemsField(
648
            id='1', label='items', type='items', varname='bar', data_source={'type': 'carddef:carddef-2'}
649
        ),
650
    ]
651
    formdef2.store()
652

  
653
    formdef1.refresh_from_storage()
654
    formdef2.refresh_from_storage()
655
    carddef1.refresh_from_storage()
656
    carddef2.refresh_from_storage()
657
    assert formdef1.reverse_relations == []
658
    assert formdef2.reverse_relations == []
659
    assert carddef1.reverse_relations == [
660
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
661
    ]
662
    assert carddef2.reverse_relations == [
663
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
664
    ]
665

  
666
    carddef1.fields = [
667
        ItemField(id='1', label='item', type='item', data_source={'type': 'carddef:carddef-2'}),
668
    ]
669
    carddef1.store()
670

  
671
    formdef1.refresh_from_storage()
672
    formdef2.refresh_from_storage()
673
    carddef1.refresh_from_storage()
674
    carddef2.refresh_from_storage()
675
    assert formdef1.reverse_relations == []
676
    assert formdef2.reverse_relations == []
677
    assert carddef1.reverse_relations == [
678
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
679
    ]
680
    assert carddef2.reverse_relations == [
681
        {'varname': '', 'type': 'item', 'obj': 'carddef:carddef-1'},
682
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
683
    ]
684

  
685
    carddef1.fields = [
686
        ItemsField(id='1', label='items', type='items', data_source={'type': 'carddef:carddef-2'}),
687
    ]
688
    carddef1.store()
689

  
690
    formdef1.refresh_from_storage()
691
    formdef2.refresh_from_storage()
692
    carddef1.refresh_from_storage()
693
    carddef2.refresh_from_storage()
694
    assert formdef1.reverse_relations == []
695
    assert formdef2.reverse_relations == []
696
    assert carddef1.reverse_relations == [
697
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
698
    ]
699
    assert carddef2.reverse_relations == [
700
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
701
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
702
    ]
703

  
704
    # circular relation ?
705
    carddef2.fields = [
706
        ItemsField(id='1', label='items', type='items', data_source={'type': 'carddef:carddef-2'}),
707
    ]
708
    carddef2.store()
709

  
710
    formdef1.refresh_from_storage()
711
    formdef2.refresh_from_storage()
712
    carddef1.refresh_from_storage()
713
    carddef2.refresh_from_storage()
714
    assert formdef1.reverse_relations == []
715
    assert formdef2.reverse_relations == []
716
    assert carddef1.reverse_relations == [
717
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
718
    ]
719
    assert carddef2.reverse_relations == [
720
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
721
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
722
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
723
    ]
724

  
725
    # block field
726
    formdef1.fields.append(BlockField(id='2', label='block', type='block:%s' % block1.slug))
727
    formdef1.store()
728

  
729
    formdef1.refresh_from_storage()
730
    formdef2.refresh_from_storage()
731
    carddef1.refresh_from_storage()
732
    carddef2.refresh_from_storage()
733
    assert formdef1.reverse_relations == []
734
    assert formdef2.reverse_relations == []
735
    # no varname for block field, item/formdef-1 is already in reverse_relations
736
    assert carddef1.reverse_relations == [
737
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
738
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
739
    ]
740
    assert carddef2.reverse_relations == [
741
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
742
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
743
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
744
    ]
745

  
746
    formdef1.fields[1] = BlockField(id='2', label='block', type='block:%s' % block1.slug, varname='foo')
747
    formdef1.store()
748

  
749
    formdef1.refresh_from_storage()
750
    formdef2.refresh_from_storage()
751
    carddef1.refresh_from_storage()
752
    carddef2.refresh_from_storage()
753
    assert formdef1.reverse_relations == []
754
    assert formdef2.reverse_relations == []
755
    # varname defined for block field
756
    assert carddef1.reverse_relations == [
757
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
758
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
759
        {'varname': 'foo_block_foo_1', 'type': 'item', 'obj': 'formdef:formdef-1'},
760
    ]
761
    assert carddef2.reverse_relations == [
762
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
763
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
764
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
765
    ]
766

  
767
    # update blockdef fields
768
    block1.fields = [
769
        ItemField(
770
            id='1',
771
            label='item',
772
            type='item',
773
            varname='block_foo_1',
774
            data_source={'type': 'carddef:carddef-2'},
775
        ),
776
        ItemsField(id='2', label='items', type='items', data_source={'type': 'carddef:carddef-1'}),
777
    ]
778
    block1.store()
779

  
780
    formdef1.refresh_from_storage()
781
    formdef2.refresh_from_storage()
782
    carddef1.refresh_from_storage()
783
    carddef2.refresh_from_storage()
784
    assert formdef1.reverse_relations == []
785
    assert formdef2.reverse_relations == []
786
    # varname defined for block field
787
    assert carddef1.reverse_relations == [
788
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
789
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
790
    ]
791
    assert carddef2.reverse_relations == [
792
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
793
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
794
        {'varname': 'foo_block_foo_1', 'type': 'item', 'obj': 'formdef:formdef-1'},
795
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
796
    ]
wcs/blocks.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import itertools
17 18
import uuid
18 19
import xml.etree.ElementTree as ET
19 20

  
......
52 53
        self.fields = []
53 54

  
54 55
    def store(self, comment=None, *args, **kwargs):
56
        from wcs.carddef import CardDef
57
        from wcs.formdef import FormDef
58

  
55 59
        assert not self.is_readonly()
56 60
        if self.slug is None:
57 61
            # set slug if it's not yet there
......
61 65
        if get_publisher().snapshot_class:
62 66
            get_publisher().snapshot_class.snap(instance=self, comment=comment)
63 67

  
68
        # update relations
69
        for objdef in itertools.chain(
70
            FormDef.select(ignore_errors=True, ignore_migration=True),
71
            CardDef.select(ignore_errors=True, ignore_migration=True),
72
        ):
73
            for field in objdef.get_all_fields():
74
                if field.key == 'block' and field.type == 'block:%s' % self.slug:
75
                    objdef.store()
76
                    continue
77

  
64 78
    def get_new_slug(self):
65 79
        new_slug = misc.simplify(self.name, space='_')
66 80
        base_new_slug = new_slug
wcs/formdef.py
28 28
import time
29 29
import types
30 30
import xml.etree.ElementTree as ET
31
from operator import itemgetter
31 32

  
32 33
from django.utils.encoding import force_bytes, force_text
33 34
from quixote import get_publisher, get_session
......
145 146

  
146 147
    max_field_id = None
147 148

  
149
    # store reverse relations
150
    reverse_relations = None
151

  
148 152
    # store fields in a separate pickle chunk
149 153
    lightweight = True
150 154

  
......
445 449
            if self.id is None or get_publisher().is_using_postgresql() or self.data_class().count() == 0:
446 450
                self.internal_identifier = new_internal_identifier
447 451
        object_only = kwargs.pop('object_only', False)
452

  
453
        if not object_only:
454
            self.update_relations()
455

  
448 456
        StorableObject.store(self, *args, **kwargs)
457

  
449 458
        if object_only:
450 459
            return
451 460
        if get_publisher().snapshot_class:
......
466 475
            for action in actions:
467 476
                getattr(cls, action)()
468 477

  
478
    def update_relations(self):
479
        from wcs.carddef import CardDef
480

  
481
        self_ref = '%s:%s' % (self.xml_root_node, self.url_name)
482
        self_relations_by_ref = self.build_relations_by_ref()
483
        reverse_relations = []
484

  
485
        # cross each formdef and cardef and check relations
486
        for objdef in itertools.chain(
487
            FormDef.select(ignore_errors=True, ignore_migration=True),
488
            CardDef.select(ignore_errors=True, ignore_migration=True),
489
        ):
490
            objdef_ref = '%s:%s' % (objdef.xml_root_node, objdef.url_name)
491
            if objdef.xml_root_node == self.xml_root_node and objdef.id == self.id:
492
                # don't build relations twice
493
                objdef_relations_by_ref = self_relations_by_ref
494
            else:
495
                objdef_relations_by_ref = objdef.build_relations_by_ref()
496
            reverse_relations += objdef_relations_by_ref.get(self_ref, [])
497

  
498
            old_objdef_reverse_relations = copy.deepcopy(objdef.reverse_relations)
499
            # remove relations with self in objdef's reverse_relations
500
            new_objdef_reverse_relations = [
501
                r for r in (objdef.reverse_relations or []) if r['obj'] != self_ref
502
            ]
503
            # and update objdef's reverse_relations from self_relations_by_ref
504
            new_objdef_reverse_relations += self_relations_by_ref.get(objdef_ref, [])
505
            # sort objectdef's reverse_relations
506
            new_objdef_reverse_relations = sorted(
507
                new_objdef_reverse_relations, key=itemgetter('obj', 'varname', 'type')
508
            )
509
            if old_objdef_reverse_relations != new_objdef_reverse_relations:
510
                objdef.reverse_relations = new_objdef_reverse_relations
511
                objdef.store(object_only=True)
512
        # sort self's reverse_relations and set
513
        self.reverse_relations = sorted(reverse_relations, key=itemgetter('obj', 'varname', 'type'))
514

  
515
    def build_relations_by_ref(self):
516
        # build relations to other carddefs, to be stored in some object reverse field
517
        self_ref = '%s:%s' % (self.xml_root_node, self.url_name)
518
        relations_by_ref = collections.defaultdict(list)
519

  
520
        def _check_field(field, in_block_field=False, prefix=''):
521
            data_source = getattr(field, 'data_source', None)
522
            if not data_source or not data_source['type'].startswith('carddef:'):
523
                return
524
            varname = field.varname or ''
525
            if in_block_field:
526
                if prefix and field.varname:
527
                    varname = '%s_%s' % (prefix, field.varname)
528
                else:
529
                    varname = ''
530
            # reverse relation of data_source['type'] to this object
531
            relations_by_ref[data_source['type']].append(
532
                {
533
                    'varname': varname,
534
                    'type': field.key,
535
                    'obj': self_ref,
536
                }
537
            )
538

  
539
        for field in self.get_all_fields():
540
            if field.key in ['item', 'items']:
541
                _check_field(field)
542
            if field.key == 'block':
543
                for _field in field.block.fields:
544
                    if _field.key not in ['item', 'items']:
545
                        continue
546
                    _check_field(_field, in_block_field=True, prefix=field.varname or '')
547
                field._block = None  # reset cache
548

  
549
        # remove duplicated items
550
        return {
551
            k: list(map(dict, {tuple(sorted(d.items())) for d in v})) for k, v in relations_by_ref.items()
552
        }
553

  
469 554
    def store_related_custom_views(self):
470 555
        for view in getattr(self, '_custom_views', []):
471 556
            view.formdef = self
472
-