Projet

Général

Profil

0004-wcs-get-card-ids-from-related-58833.patch

Lauréline Guérin, 09 décembre 2021 14:57

Télécharger (21,3 ko)

Voir les différences:

Subject: [PATCH 4/5] wcs: get card ids from related (#58833)

 combo/apps/wcs/models.py | 141 +++++++++++-
 tests/test_wcs.py        | 452 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 589 insertions(+), 4 deletions(-)
combo/apps/wcs/models.py
973 973
            context[self.global_context_key] = card_ids
974 974

  
975 975
    def get_repeat_template(self, context):
976
        return len(context[self.global_context_key])
976
        return len(context.get(self.global_context_key) or [])
977 977

  
978 978
    def get_card_data(self, card_slug, card_id, context, synchronous=False):
979 979
        api_url = '/api/cards/%s/%s/?include-files-content=off' % (card_slug, card_id)
......
1063 1063
            )
1064 1064
        return results
1065 1065

  
1066
    def get_card_ids_from_related(self, context, request):
1067
        def get_relation(relations, varname, reverse):
1068
            for relation in relations:
1069
                if relation['reverse'] == reverse and relation['varname'] == varname:
1070
                    return relation
1071

  
1072
        def follow_data(card_data, relations, varname, parts):
1073
            reverse = varname.startswith('reverse:')
1074
            if reverse:
1075
                varname = varname[8:]  # remove 'reverse:'
1076
            relation = get_relation(relations, varname, reverse)
1077
            if not relation:
1078
                # not found - stop
1079
                return []
1080
            card_slug = relation['obj'][8:]  # remove 'carddef:'
1081

  
1082
            if not parts and card_slug != self.card_slug:
1083
                # last part, but wrong card model
1084
                return []
1085

  
1086
            if reverse:
1087
                # reverse relation: always multiple
1088
                if not parts:
1089
                    # last part - get ids and stop
1090
                    ids = self.get_card_ids_from_template(
1091
                        '{{ cards|objects:"%s"|filter_by:"%s"|filter_value:"%s"|getlist:"id"|join:"," }}'
1092
                        % (card_slug, varname, card_data['id']),
1093
                        context,
1094
                        request,
1095
                    )
1096
                    return ids
1097
                # multiple relation, but still parts to follow - stop
1098
                return []
1099

  
1100
            # direct relation
1101

  
1102
            if not parts:
1103
                # last part - stop
1104
                raw_value = None
1105
                if '%s_raw' % varname in card_data['fields']:
1106
                    raw_value = card_data['fields']['%s_raw' % varname]
1107
                else:
1108
                    # may be a fields block ?
1109
                    varname_parts = varname.split('_')
1110
                    for i in range(len(varname_parts)):
1111
                        block_varname = '%s_raw' % '_'.join(varname_parts[: i + 1])
1112
                        if block_varname in card_data['fields']:
1113
                            block_data = card_data['fields'][block_varname]
1114
                            if not block_data:
1115
                                continue
1116
                            field_varname = '%s_raw' % '_'.join(varname_parts[i + 1 :])
1117
                            values = [v.get(field_varname) for v in block_data]
1118
                            values = [v for v in values if v]
1119
                            if values:
1120
                                return values
1121

  
1122
                if not raw_value:
1123
                    return []
1124
                if not isinstance(raw_value, list):
1125
                    # item or computed
1126
                    return [raw_value]
1127
                # items
1128
                return raw_value
1129

  
1130
            if relation['type'] == 'items':
1131
                # multiple relation, but still parts to follow - stop
1132
                return []
1133

  
1134
            # single relation, get card_data and follow next part
1135

  
1136
            if not card_data['fields'].get('%s_raw' % varname):
1137
                # field not found or empty
1138
                return []
1139

  
1140
            # get schema
1141
            card_schema = self.get_card_schema(card_slug)
1142
            if not card_schema:
1143
                # card schema not found
1144
                return []
1145
            # and data
1146
            next_card_data = self.get_card_data(
1147
                card_slug, card_data['fields']['%s_raw' % varname], context, synchronous=True
1148
            )
1149
            if not next_card_data:
1150
                # card data not found
1151
                return []
1152
            # continue
1153
            return follow_data(
1154
                card_data=next_card_data,
1155
                relations=card_schema['relations'],
1156
                varname=parts[0],
1157
                parts=parts[1:],
1158
            )
1159

  
1160
        first_cell_slug = self.related_card_path.split('/')[0]
1161
        try:
1162
            first_cell = WcsCardInfosCell.objects.get(page=self.page_id, slug=first_cell_slug)
1163
        except (WcsCardInfosCell.DoesNotExist, WcsCardInfosCell.MultipleObjectsReturned):
1164
            return []
1165
        if first_cell.related_card_path:
1166
            # no explicit ids
1167
            return []
1168
        if ',' in first_cell.card_ids:
1169
            # multiple ids, can not follow relations
1170
            return []
1171
        first_cell.repeat_index = 0
1172
        card_id = first_cell.get_card_id(context)
1173
        if not card_id:
1174
            # no card id found
1175
            return []
1176
        card_data = self.get_card_data(first_cell.card_slug, card_id, context, synchronous=True)
1177
        if not card_data:
1178
            # card data not found
1179
            return []
1180
        parts = self.related_card_path.split('/')[1:]
1181
        return follow_data(
1182
            card_data=card_data,
1183
            relations=first_cell.cached_json['relations'],
1184
            varname=parts[0],
1185
            parts=parts[1:],
1186
        )
1187

  
1066 1188
    def get_card_ids_from_template(self, template, original_context, request):
1067 1189
        try:
1068 1190
            context = RequestContext(request)
......
1073 1195
            return []
1074 1196

  
1075 1197
    def get_card_ids(self, original_context, request):
1198
        if not self.carddef_reference:
1199
            # not configured
1200
            return []
1201

  
1202
        if self.related_card_path:
1203
            # look at other cells to get related ids
1204
            return self.get_card_ids_from_related(original_context, request)
1205

  
1076 1206
        if self.card_ids:
1077 1207
            # card ids template is defined
1078 1208
            return self.get_card_ids_from_template(self.card_ids, original_context, request)
......
1088 1218
    def get_card_id(self, context):
1089 1219
        repeat_index = getattr(self, 'repeat_index', context.get('repeat_index'))
1090 1220
        if repeat_index is not None:
1091
            return context[self.global_context_key][repeat_index]
1092
        return None
1221
            try:
1222
                return context.get(self.global_context_key)[repeat_index]
1223
            except IndexError:
1224
                return None
1093 1225

  
1094 1226
    def get_extra_manager_context(self):
1095 1227
        extra_context = super().get_extra_manager_context()
......
1111 1243

  
1112 1244
        card_id = self.get_card_id(context)
1113 1245
        if not card_id:
1114
            if self.card_ids:  # template defined, but result is empty or None
1246
            if self.card_ids or self.related_card_path:
1247
                # template or related_card_path defined, but result is empty or None
1115 1248
                extra_context['card_not_found'] = True
1116 1249
            return extra_context
1117 1250

  
tests/test_wcs.py
164 164
            'url': '/backoffice/data/card_model_1/13/',
165 165
        },
166 166
    ],
167
    'card_a': [
168
        {
169
            'id': 1,
170
            'fields': {
171
                'cardb_raw': 1,
172
                'cardsb_raw': [2, 3],
173
                'blockb_raw': [{'cardb_raw': 4}, {'cardb_raw': 5}],
174
                'cardc_raw': 6,
175
                'cardz_raw': 42,
176
            },
177
        },
178
        {
179
            'id': 2,
180
            'fields': {
181
                'cardb_raw': 1,
182
                'cardsb_raw': [2, 3],
183
                'blockb_raw': [{'cardb_raw': 4}, {'cardb_raw': 5}],
184
                'cardc_raw': 61,  # unknown card_c
185
            },
186
        },
187
        {
188
            'id': 3,
189
            'fields': {
190
                # some missing fields
191
                'blockb_raw': [{}],
192
            },
193
        },
194
        {
195
            'id': 4,
196
            'fields': {
197
                # some empty fields
198
                'cardb_raw': None,
199
                'cardsb_raw': None,
200
                'blockb_raw': [{'cardb_raw': None}],
201
                'cardc_raw': 7,
202
            },
203
        },
204
    ],
205
    'card_b': [{'id': i, 'fields': []} for i in range(1, 12) if i != 6],
206
    'card_c': [
207
        {
208
            'id': 6,
209
            'fields': {
210
                'cardb_raw': 7,
211
                'cardsb_raw': [8, 9],
212
                'blockb_raw': [{'cardb_raw': 10}, {'cardb_raw': 11}],
213
            },
214
        },
215
        {
216
            'id': 7,
217
            'fields': {},
218
        },
219
    ],
167 220
}
168 221

  
169 222
WCS_CARDS_CUSTOM_VIEW_DATA = [
......
2385 2438
    assert cell_resp.text.replace('\n', '') == ''  # empty-cell
2386 2439

  
2387 2440

  
2441
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
2442
def test_card_cell_render_identifier_from_related(mock_send, nocache, app):
2443
    page = Page.objects.create(title='xxx', slug='foo', template_name='standard')
2444
    cell = WcsCardInfosCell.objects.create(
2445
        page=page,
2446
        placeholder='content',
2447
        order=0,
2448
        slug='sluga',
2449
        carddef_reference='default:card_a',
2450
        card_ids='1',
2451
    )
2452
    cell2 = WcsCardInfosCell.objects.create(
2453
        page=page, placeholder='content', order=1, slug='slugb', carddef_reference='default:card_b'
2454
    )
2455

  
2456
    cell2_url = reverse(
2457
        'combo-public-ajax-page-cell',
2458
        kwargs={'page_pk': page.pk, 'cell_reference': cell2.get_reference()},
2459
    )
2460

  
2461
    def failing(urls):
2462
        resp = app.get(page.get_online_url())
2463
        assert len(resp.context['cells']) >= 2
2464
        for i in range(0, len(resp.context['cells']) - 1):
2465
            assert resp.context['cells'][i].pk == cell.pk
2466
        assert resp.context['cells'][-1].pk == cell2.pk
2467
        extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text)
2468
        mock_send.reset_mock()
2469
        cell_resp = app.get(cell2_url + '?ctx=' + extra_ctx[-1])
2470
        assert cell_resp.text.replace('\n', '') == ''  # empty-cell
2471
        assert len(mock_send.call_args_list) == len(urls)
2472
        for j, url in enumerate(urls):
2473
            assert url in mock_send.call_args_list[j][0][0].url
2474

  
2475
    def single(urls):
2476
        resp = app.get(page.get_online_url())
2477
        assert len(resp.context['cells']) == 2
2478
        assert resp.context['cells'][0].pk == cell.pk
2479
        assert resp.context['cells'][1].pk == cell2.pk
2480
        assert resp.context['cells'][1].repeat_index == 0
2481
        extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text)
2482
        mock_send.reset_mock()
2483
        cell_resp = app.get(cell2_url + '?ctx=' + extra_ctx[1])
2484
        assert cell_resp.context['repeat_index'] == 0
2485
        assert len(mock_send.call_args_list) == len(urls)
2486
        for j, url in enumerate(urls):
2487
            assert url in mock_send.call_args_list[j][0][0].url
2488

  
2489
    def multiple(urls):
2490
        resp = app.get(page.get_online_url())
2491
        assert len(resp.context['cells']) >= 3
2492
        assert resp.context['cells'][0].pk == cell.pk
2493
        extra_ctx = re.findall(r'data-extra-context="(.*)"', resp.text)
2494
        for i in range(0, len(resp.context['cells']) - 1):
2495
            assert resp.context['cells'][i + 1].pk == cell2.pk
2496
            assert resp.context['cells'][i + 1].repeat_index == i
2497
            mock_send.reset_mock()
2498
            cell_resp = app.get(cell2_url + '?ctx=' + extra_ctx[i + 1])
2499
            assert cell_resp.context['repeat_index'] == i
2500
            assert len(mock_send.call_args_list) == len(urls)
2501
            for j, url in enumerate(urls):
2502
                if isinstance(url, list):
2503
                    assert url[i] in mock_send.call_args_list[j][0][0].url
2504
                else:
2505
                    assert url in mock_send.call_args_list[j][0][0].url
2506

  
2507
    # no cell with this slug
2508
    cell2.related_card_path = 'slugz/cardb'
2509
    cell2.save()
2510
    failing(urls=[])
2511

  
2512
    # another cell with the same slug
2513
    cell3 = WcsCardInfosCell.objects.create(page=page, placeholder='content', order=2, slug='sluga')
2514
    cell2.related_card_path = 'sluga/foo'
2515
    cell2.save()
2516
    failing(urls=[])
2517
    cell3.delete()
2518

  
2519
    # multiple ids configured on first cell
2520
    cell.card_ids = '{{ cards|objects:"card_a"|getlist:"id"|join:"," }}'
2521
    cell.save()
2522
    failing(
2523
        urls=[
2524
            # get first cell data
2525
            '/api/cards/card_a/list',
2526
        ]
2527
    )
2528

  
2529
    # related_card_path configured on first cell
2530
    cell.card_ids = '1'  # reset
2531
    cell.related_path = 'foobar'
2532
    cell.save()
2533
    failing(
2534
        urls=[
2535
            # get first cell data
2536
            '/api/cards/card_a/1/',
2537
        ]
2538
    )
2539

  
2540
    # reset
2541
    cell.related_path = ''
2542
    cell.save()
2543

  
2544
    # another cell as the same slug, but not a WcsCardInfosCell
2545
    cell3 = WcsCardsCell.objects.create(page=page, placeholder='content', order=2, slug='sluga')
2546

  
2547
    # direct and single relation (item)
2548
    cell2.related_card_path = 'sluga/cardb'
2549
    cell2.save()
2550
    single(
2551
        urls=[
2552
            # get first cell data
2553
            '/api/cards/card_a/1/',
2554
            # and follow cardb relation
2555
            '/api/cards/card_b/1/',
2556
        ]
2557
    )
2558
    cell3.delete()  # reset
2559

  
2560
    cell2.related_card_path = 'sluga/cardc/cardb'
2561
    cell2.save()
2562
    single(
2563
        urls=[
2564
            # get first cell data
2565
            '/api/cards/card_a/1/',
2566
            # get card_c schema
2567
            '/api/cards/card_c/@schema',
2568
            # follow cardc relation
2569
            '/api/cards/card_c/6/',
2570
            # and follow cardb relation
2571
            '/api/cards/card_b/7/',
2572
        ]
2573
    )
2574

  
2575
    # direct and multiple relation (items)
2576
    cell2.related_card_path = 'sluga/cardsb'
2577
    cell2.save()
2578
    multiple(
2579
        urls=[
2580
            # get first cell data
2581
            '/api/cards/card_a/1/',
2582
            # and follow cardb relation
2583
            ['/api/cards/card_b/2/', '/api/cards/card_b/3/'],
2584
        ]
2585
    )
2586

  
2587
    cell2.related_card_path = 'sluga/cardc/cardsb'
2588
    cell2.save()
2589
    multiple(
2590
        urls=[
2591
            # get first cell data
2592
            '/api/cards/card_a/1/',
2593
            # get card_c schema
2594
            '/api/cards/card_c/@schema',
2595
            # follow cardc relation
2596
            '/api/cards/card_c/6/',
2597
            # and follow cardb relation
2598
            ['/api/cards/card_b/8/', '/api/cards/card_b/9/'],
2599
        ]
2600
    )
2601

  
2602
    # direct and multiple relation through a block
2603
    cell2.related_card_path = 'sluga/blockb_cardb'
2604
    cell2.save()
2605
    multiple(
2606
        urls=[
2607
            # get first cell data
2608
            '/api/cards/card_a/1/',
2609
            # and follow cardb relation
2610
            ['/api/cards/card_b/4/', '/api/cards/card_b/5/'],
2611
        ]
2612
    )
2613

  
2614
    cell2.related_card_path = 'sluga/cardc/blockb_cardb'
2615
    cell2.save()
2616
    multiple(
2617
        urls=[
2618
            # get first cell data
2619
            '/api/cards/card_a/1/',
2620
            # get card_c schema
2621
            '/api/cards/card_c/@schema',
2622
            # follow cardc relation
2623
            '/api/cards/card_c/6/',
2624
            # and follow cardb relation
2625
            ['/api/cards/card_b/10/', '/api/cards/card_b/11/'],
2626
        ]
2627
    )
2628

  
2629
    # unknown part in related_card_path
2630
    cell2.related_card_path = 'sluga/foobar'
2631
    cell2.save()
2632
    failing(
2633
        urls=[
2634
            # get first cell data
2635
            '/api/cards/card_a/1/',
2636
        ]
2637
    )
2638
    cell2.related_card_path = 'sluga/cardc/foobar'
2639
    cell2.save()
2640
    failing(
2641
        urls=[
2642
            # get first cell data
2643
            '/api/cards/card_a/1/',
2644
            # get card_c schema
2645
            '/api/cards/card_c/@schema',
2646
            # follow cardc relation
2647
            '/api/cards/card_c/6/',
2648
        ]
2649
    )
2650

  
2651
    # card data not found
2652
    cell.card_ids = '42'
2653
    cell.save()
2654
    cell2.related_card_path = 'sluga/cardb'
2655
    cell2.save()
2656
    failing(
2657
        urls=[
2658
            # get first cell data
2659
            '/api/cards/card_a/42/',
2660
        ]
2661
    )
2662

  
2663
    cell.card_ids = '2'
2664
    cell.save()
2665
    cell2.related_card_path = 'sluga/cardc/cardb'
2666
    cell2.save()
2667
    failing(
2668
        urls=[
2669
            # get first cell data
2670
            '/api/cards/card_a/2/',
2671
            # get card_c schema
2672
            '/api/cards/card_c/@schema',
2673
            # follow cardc relation
2674
            '/api/cards/card_c/61/',
2675
        ]
2676
    )
2677
    # reset
2678
    cell.card_ids = '1'
2679
    cell.save()
2680

  
2681
    # last part has not the correct card slug
2682
    cell2.related_card_path = 'sluga/cardc'
2683
    cell2.save()
2684
    failing(
2685
        urls=[
2686
            # get first cell data
2687
            '/api/cards/card_a/1/',
2688
        ]
2689
    )
2690

  
2691
    # unknown schema
2692
    cell2.related_card_path = 'sluga/cardz/cardb'
2693
    cell2.save()
2694
    failing(
2695
        urls=[
2696
            # get first cell data
2697
            '/api/cards/card_a/1/',
2698
            # get card_z schema
2699
            '/api/cards/card_z/@schema',
2700
        ]
2701
    )
2702

  
2703
    # multiple relation of multiple relation
2704
    cell2.related_card_path = 'sluga/cardsb/reverse:cardb'
2705
    cell2.save()
2706
    failing(
2707
        urls=[
2708
            # get first cell data
2709
            '/api/cards/card_a/1/',
2710
        ]
2711
    )
2712

  
2713
    # field not found
2714
    cell.card_ids = '3'
2715
    cell.save()
2716
    cell2.related_card_path = 'sluga/cardb'
2717
    cell2.save()
2718
    failing(
2719
        urls=[
2720
            # get first cell data
2721
            '/api/cards/card_a/3/',
2722
        ]
2723
    )
2724
    cell2.related_card_path = 'sluga/cardc/cardb'
2725
    cell2.save()
2726
    failing(
2727
        urls=[
2728
            # get first cell data
2729
            '/api/cards/card_a/3/',
2730
        ]
2731
    )
2732

  
2733
    # field empty
2734
    cell.card_ids = '4'
2735
    cell.save()
2736
    cell2.related_card_path = 'sluga/cardb'
2737
    cell2.save()
2738
    failing(
2739
        urls=[
2740
            # get first cell data
2741
            '/api/cards/card_a/4/',
2742
        ]
2743
    )
2744

  
2745
    # field not found in block
2746
    cell.card_ids = '3'
2747
    cell.save()
2748
    cell2.related_card_path = 'sluga/blockb_cardb'
2749
    cell2.save()
2750
    failing(
2751
        urls=[
2752
            # get first cell data
2753
            '/api/cards/card_a/3/',
2754
        ]
2755
    )
2756

  
2757
    # field empty in block
2758
    cell.card_ids = '4'
2759
    cell.save()
2760
    cell2.related_card_path = 'sluga/blockb_cardb'
2761
    cell2.save()
2762
    failing(
2763
        urls=[
2764
            # get first cell data
2765
            '/api/cards/card_a/4/',
2766
        ]
2767
    )
2768

  
2769
    # reverse relation of item
2770
    cell.carddef_reference = 'default:card_b'
2771
    cell.slug = 'slugb'
2772
    cell.card_ids = '1'
2773
    cell.related_card_path = ''
2774
    cell.save()
2775
    cell2.carddef_reference = 'default:card_a'
2776
    cell2.slug = 'sluga'
2777
    cell2.card_ids = ''
2778
    cell2.related_card_path = 'slugb/reverse:cardb'
2779
    cell2.save()
2780
    multiple(
2781
        urls=[
2782
            # get first cell data
2783
            '/api/cards/card_b/1/',
2784
            # get list of card_a with cardb=1
2785
            '/api/cards/card_a/list?orig=combo&filter-cardb=1',
2786
            # and follow carda reverse relation
2787
            ['/api/cards/card_a/%s/' % i for i in range(1, 5)],
2788
        ]
2789
    )
2790

  
2791
    # reverse relation of items
2792
    cell2.related_card_path = 'slugb/reverse:cardsb'
2793
    cell2.save()
2794
    multiple(
2795
        urls=[
2796
            # get first cell data
2797
            '/api/cards/card_b/1/',
2798
            # get list of card_a with cardsb=1
2799
            '/api/cards/card_a/list?orig=combo&filter-cardsb=1',
2800
            # and follow carda reverse relation
2801
            ['/api/cards/card_a/%s/' % i for i in range(1, 5)],
2802
        ]
2803
    )
2804

  
2805
    # reverse relation of item through a block
2806
    cell2.related_card_path = 'slugb/reverse:blockb_cardb'
2807
    cell2.save()
2808
    multiple(
2809
        urls=[
2810
            # get first cell data
2811
            '/api/cards/card_b/1/',
2812
            # get list of card_a with cardsb=1
2813
            '/api/cards/card_a/list?orig=combo&filter-blockb_cardb=1',
2814
            # and follow carda reverse relation
2815
            ['/api/cards/card_a/%s/' % i for i in range(1, 5)],
2816
        ]
2817
    )
2818

  
2819
    # unknown part in related_card_path
2820
    cell2.related_card_path = 'slugb/foobar'
2821
    cell2.save()
2822
    failing(
2823
        urls=[
2824
            # get first cell data
2825
            '/api/cards/card_b/1/',
2826
        ]
2827
    )
2828

  
2829
    # multiple relation of multiple relation
2830
    cell2.related_card_path = 'slugb/reverse:cardb/cardsb'
2831
    cell2.save()
2832
    failing(
2833
        urls=[
2834
            # get first cell data
2835
            '/api/cards/card_b/1/',
2836
        ]
2837
    )
2838

  
2839

  
2388 2840
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
2389 2841
def test_card_cell_render_user(mock_send, context, nocache):
2390 2842
    page = Page.objects.create(title='xxx', template_name='standard')
2391
-