Projet

Général

Profil

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

Lauréline Guérin, 22 novembre 2021 16:58

Télécharger (16,2 ko)

Voir les différences:

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

 tests/test_carddef.py | 236 +++++++++++++++++++++++++++++++++++++++++-
 wcs/blocks.py         |  14 +++
 wcs/formdef.py        |  91 ++++++++++++++++
 3 files changed, 340 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(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
    ]
624
    block1.store()
625

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

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

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

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

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

  
668
    carddef1.fields = [
669
        ItemField(id='0', label='unknown', type='item', data_source={'type': 'carddef:unknown'}),
670
        ItemField(id='1', label='item', type='item', data_source={'type': 'carddef:carddef-2'}),
671
    ]
672
    carddef1.store()
673

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

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

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

  
707
    # custom views ?
708
    carddef1.fields = [
709
        ItemsField(id='1', label='items', type='items', data_source={'type': 'carddef:carddef-2:view'}),
710
    ]
711
    carddef1.store()
712

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

  
727
    # circular relation ?
728
    carddef2.fields = [
729
        ItemsField(id='1', label='items', type='items', data_source={'type': 'carddef:carddef-2'}),
730
    ]
731
    carddef2.store()
732

  
733
    formdef1.refresh_from_storage()
734
    formdef2.refresh_from_storage()
735
    carddef1.refresh_from_storage()
736
    carddef2.refresh_from_storage()
737
    assert formdef1.reverse_relations == []
738
    assert formdef2.reverse_relations == []
739
    assert carddef1.reverse_relations == [
740
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
741
    ]
742
    assert carddef2.reverse_relations == [
743
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
744
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
745
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
746
    ]
747

  
748
    # block field
749
    formdef1.fields.append(BlockField(id='2', label='block', type='block:%s' % block1.slug))
750
    formdef1.store()
751

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

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

  
772
    formdef1.refresh_from_storage()
773
    formdef2.refresh_from_storage()
774
    carddef1.refresh_from_storage()
775
    carddef2.refresh_from_storage()
776
    assert formdef1.reverse_relations == []
777
    assert formdef2.reverse_relations == []
778
    # varname defined for block field
779
    assert carddef1.reverse_relations == [
780
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
781
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
782
        {'varname': 'foo_block_foo_1', 'type': 'item', 'obj': 'formdef:formdef-1'},
783
    ]
784
    assert carddef2.reverse_relations == [
785
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
786
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
787
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
788
    ]
789

  
790
    # update blockdef fields
791
    block1.fields = [
792
        ItemField(
793
            id='1',
794
            label='item',
795
            type='item',
796
            varname='block_foo_1',
797
            data_source={'type': 'carddef:carddef-2'},
798
        ),
799
        ItemsField(id='2', label='items', type='items', data_source={'type': 'carddef:carddef-1'}),
800
    ]
801
    block1.store()
802

  
803
    formdef1.refresh_from_storage()
804
    formdef2.refresh_from_storage()
805
    carddef1.refresh_from_storage()
806
    carddef2.refresh_from_storage()
807
    assert formdef1.reverse_relations == []
808
    assert formdef2.reverse_relations == []
809
    # varname defined for block field
810
    assert carddef1.reverse_relations == [
811
        {'varname': '', 'type': 'item', 'obj': 'formdef:formdef-1'},
812
        {'varname': '', 'type': 'items', 'obj': 'formdef:formdef-1'},
813
    ]
814
    assert carddef2.reverse_relations == [
815
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-1'},
816
        {'varname': '', 'type': 'items', 'obj': 'carddef:carddef-2'},
817
        {'varname': 'foo_block_foo_1', 'type': 'item', 'obj': 'formdef:formdef-1'},
818
        {'varname': 'bar', 'type': 'items', 'obj': 'formdef:formdef-2'},
819
    ]
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']:
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']:
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
-