0001-formdefs-store-reverse-relations-on-form-carddefs-57.patch
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 |
- |