Projet

Général

Profil

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

Lauréline Guérin, 26 novembre 2021 15:09

Télécharger (16,8 ko)

Voir les différences:

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

 tests/test_carddef.py | 251 +++++++++++++++++++++++++++++++++++++++++-
 wcs/blocks.py         |  14 +++
 wcs/formdef.py        |  91 +++++++++++++++
 3 files changed, 355 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, ComputedField, 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(id='0', label='unknown', type='item', data_source={'type': 'carddef:unknown'}),
615
        ItemField(
616
            id='1',
617
            label='item',
618
            type='item',
619
            varname='block_foo_1',
620
            data_source={'type': 'carddef:carddef-1'},
621
        ),
622
        ItemsField(id='2', label='items', type='items', data_source={'type': 'carddef:carddef-1'}),
623
        ComputedField(
624
            id='3',
625
            label='computed',
626
            type='computed',
627
            varname='block_computed_foo_1',
628
            data_source={'type': 'carddef:carddef-1'},
629
        ),
630
    ]
631
    block1.store()
632

  
633
    assert formdef1.reverse_relations == []
634
    assert formdef2.reverse_relations == []
635
    assert carddef1.reverse_relations == []
636
    assert carddef2.reverse_relations == []
637

  
638
    formdef1.fields = [
639
        ItemField(id='0', label='unknown', type='item', data_source={'type': 'carddef:unknown'}),
640
        ItemField(id='1', label='item', type='item', data_source={'type': 'carddef:carddef-1'}),
641
    ]
642
    formdef1.store()
643

  
644
    formdef1.refresh_from_storage()
645
    formdef2.refresh_from_storage()
646
    carddef1.refresh_from_storage()
647
    carddef2.refresh_from_storage()
648
    assert formdef1.reverse_relations == []
649
    assert formdef2.reverse_relations == []
650
    assert carddef1.reverse_relations == [
651
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
652
    ]
653
    assert carddef2.reverse_relations == []
654

  
655
    formdef2.fields = [
656
        ItemsField(
657
            id='1', label='items', type='items', varname='bar', data_source={'type': 'carddef:carddef-2'}
658
        ),
659
    ]
660
    formdef2.store()
661

  
662
    formdef1.refresh_from_storage()
663
    formdef2.refresh_from_storage()
664
    carddef1.refresh_from_storage()
665
    carddef2.refresh_from_storage()
666
    assert formdef1.reverse_relations == []
667
    assert formdef2.reverse_relations == []
668
    assert carddef1.reverse_relations == [
669
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
670
    ]
671
    assert carddef2.reverse_relations == [
672
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
673
    ]
674

  
675
    carddef1.fields = [
676
        ItemField(id='0', label='unknown', type='item', data_source={'type': 'carddef:unknown'}),
677
        ItemField(id='1', label='item', type='item', data_source={'type': 'carddef:carddef-2'}),
678
    ]
679
    carddef1.store()
680

  
681
    formdef1.refresh_from_storage()
682
    formdef2.refresh_from_storage()
683
    carddef1.refresh_from_storage()
684
    carddef2.refresh_from_storage()
685
    assert formdef1.reverse_relations == []
686
    assert formdef2.reverse_relations == []
687
    assert carddef1.reverse_relations == [
688
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
689
    ]
690
    assert carddef2.reverse_relations == [
691
        {'varname': '', 'type': 'item', 'obj': 'carddef:carddef-1'},
692
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
693
    ]
694

  
695
    carddef1.fields = [
696
        ItemsField(id='1', label='items', type='items', data_source={'type': 'carddef:carddef-2'}),
697
    ]
698
    carddef1.store()
699

  
700
    formdef1.refresh_from_storage()
701
    formdef2.refresh_from_storage()
702
    carddef1.refresh_from_storage()
703
    carddef2.refresh_from_storage()
704
    assert formdef1.reverse_relations == []
705
    assert formdef2.reverse_relations == []
706
    assert carddef1.reverse_relations == [
707
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
708
    ]
709
    assert carddef2.reverse_relations == [
710
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
711
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
712
    ]
713

  
714
    # custom views ?
715
    carddef1.fields = [
716
        ComputedField(
717
            id='1',
718
            label='computed',
719
            type='computed',
720
            varname='computed_foobar',
721
            data_source={'type': 'carddef:carddef-2:view'},
722
        ),
723
    ]
724
    carddef1.store()
725

  
726
    formdef1.refresh_from_storage()
727
    formdef2.refresh_from_storage()
728
    carddef1.refresh_from_storage()
729
    carddef2.refresh_from_storage()
730
    assert formdef1.reverse_relations == []
731
    assert formdef2.reverse_relations == []
732
    assert carddef1.reverse_relations == [
733
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
734
    ]
735
    assert carddef2.reverse_relations == [
736
        {'varname': 'computed_foobar', 'type': 'computed', 'obj': 'carddef:carddef-1'},
737
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
738
    ]
739

  
740
    # circular relation ?
741
    carddef2.fields = [
742
        ItemsField(id='1', label='items', type='items', data_source={'type': 'carddef:carddef-2'}),
743
    ]
744
    carddef2.store()
745

  
746
    formdef1.refresh_from_storage()
747
    formdef2.refresh_from_storage()
748
    carddef1.refresh_from_storage()
749
    carddef2.refresh_from_storage()
750
    assert formdef1.reverse_relations == []
751
    assert formdef2.reverse_relations == []
752
    assert carddef1.reverse_relations == [
753
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
754
    ]
755
    assert carddef2.reverse_relations == [
756
        {'varname': 'computed_foobar', 'type': 'computed', 'obj': 'carddef:carddef-1'},
757
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
758
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
759
    ]
760

  
761
    # block field
762
    formdef1.fields.append(BlockField(id='2', label='block', type='block:%s' % block1.slug))
763
    formdef1.store()
764

  
765
    formdef1.refresh_from_storage()
766
    formdef2.refresh_from_storage()
767
    carddef1.refresh_from_storage()
768
    carddef2.refresh_from_storage()
769
    assert formdef1.reverse_relations == []
770
    assert formdef2.reverse_relations == []
771
    assert carddef1.reverse_relations == [
772
        {'varname': '', 'type': 'computed', 'obj': 'formdef:formdef-1'},
773
        # no varname for block field, item/formdef-1 is already in reverse_relations
774
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
775
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
776
    ]
777
    assert carddef2.reverse_relations == [
778
        {'varname': 'computed_foobar', 'type': 'computed', 'obj': 'carddef:carddef-1'},
779
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
780
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
781
    ]
782

  
783
    formdef1.fields[2] = BlockField(id='2', label='block', type='block:%s' % block1.slug, varname='foo')
784
    formdef1.store()
785

  
786
    formdef1.refresh_from_storage()
787
    formdef2.refresh_from_storage()
788
    carddef1.refresh_from_storage()
789
    carddef2.refresh_from_storage()
790
    assert formdef1.reverse_relations == []
791
    assert formdef2.reverse_relations == []
792
    # varname defined for block field
793
    assert carddef1.reverse_relations == [
794
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
795
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
796
        {'varname': 'foo_block_computed_foo_1', 'type': 'computed', 'obj': 'formdef:formdef-1'},
797
        {'varname': 'foo_block_foo_1', 'type': 'item', 'obj': 'formdef:formdef-1'},
798
    ]
799
    assert carddef2.reverse_relations == [
800
        {'varname': 'computed_foobar', 'type': 'computed', 'obj': 'carddef:carddef-1'},
801
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
802
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
803
    ]
804

  
805
    # update blockdef fields
806
    block1.fields = [
807
        ItemField(
808
            id='1',
809
            label='item',
810
            type='item',
811
            varname='block_foo_1',
812
            data_source={'type': 'carddef:carddef-2'},
813
        ),
814
        ItemsField(id='2', label='items', type='items', data_source={'type': 'carddef:carddef-1'}),
815
    ]
816
    block1.store()
817

  
818
    formdef1.refresh_from_storage()
819
    formdef2.refresh_from_storage()
820
    carddef1.refresh_from_storage()
821
    carddef2.refresh_from_storage()
822
    assert formdef1.reverse_relations == []
823
    assert formdef2.reverse_relations == []
824
    # varname defined for block field
825
    assert carddef1.reverse_relations == [
826
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
827
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
828
    ]
829
    assert carddef2.reverse_relations == [
830
        {'varname': 'computed_foobar', 'type': 'computed', 'obj': 'carddef:carddef-1'},
831
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
832
        {'varname': 'foo_block_foo_1', 'type': 'item', 'obj': 'formdef:formdef-1'},
833
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
834
    ]
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
                    break
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
            obj_ref = ':'.join(data_source['type'].split(':')[:2])  # remove possible custom-view
532
            relations_by_ref[obj_ref].append(
533
                {
534
                    'varname': varname,
535
                    'type': field.key,
536
                    'obj': self_ref,
537
                }
538
            )
539

  
540
        for field in self.get_all_fields():
541
            if field.key in ['item', 'items', 'computed']:
542
                _check_field(field)
543
            if field.key == 'block':
544
                try:
545
                    field.block  # load block
546
                except KeyError:
547
                    # blockdef not found
548
                    continue
549
                for _field in field.block.fields:
550
                    if _field.key not in ['item', 'items', 'computed']:
551
                        continue
552
                    _check_field(_field, in_block_field=True, prefix=field.varname or '')
553
                field._block = None  # reset cache
554

  
555
        # remove duplicated items
556
        return {
557
            k: list(map(dict, {tuple(sorted(d.items())) for d in v})) for k, v in relations_by_ref.items()
558
        }
559

  
469 560
    def store_related_custom_views(self):
470 561
        for view in getattr(self, '_custom_views', []):
471 562
            view.formdef = self
472
-