Bin
2025-12-17 dcf780a91c16b6be28635b6e2e0e702060ee19f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license.
"""
import json
 
import pytest
from django.conf import settings
from projects.functions.utils import recalculate_created_annotations_and_labels_from_scratch
from projects.models import Project, ProjectSummary
 
from ..utils import make_task, project_id  # noqa
 
 
def get_filtered_task_ids(business_client, view_id):
    response = business_client.get(f'/api/tasks/?view={view_id}')
    response_data = response.json()
    assert 'tasks' in response_data, response_data
    return [task['id'] for task in response_data['tasks']]
 
 
def apply_filter_and_get_view_id(business_client, project_id, filters):
    payload = {
        'project': project_id,
        'data': {'filters': filters},
    }
    response = business_client.post(
        '/api/dm/views/',
        data=json.dumps(payload),
        content_type='application/json',
    )
    assert response.status_code == 201, response.content
    return response.json()['id']
 
 
@pytest.mark.django_db
def test_views_filters_with_undefined(business_client, project_id):
    """
    1. Import task 1: {"$undefined$": "photo1.jpg"}
    2. Filter by `data` with value `photo`
    3. It should return task 1
 
    4. Set labeling config <View> <Image value="$image" name="img"/> </View>
    5. Filter by `image` with value `photo`
    6. It should return task 1
 
    7. Add task 2: {"$undefined$": "photo2.jpg", "extra": "123"}
    8. Filter by "extra": "123"
    9. It should return task 2
    10. Filter by "image" with value `photo`
    11. It should return task 1 and task 2
 
    12. Update task 1 with {"extra": "456"}
    13. Check project.summary.common_data_columns, there should be ["$undefined$", "extra"]
 
    14. Filter by "image" with "photo" should return task 1 and task 2
    """
    project = Project.objects.get(pk=project_id)
    project.label_config = '<View></View>'
    project.save()
 
    # Step 1: Import task 1: {"$undefined$": "photo1.jpg"}
    task_data_field_name = settings.DATA_UNDEFINED_NAME  # "$undefined$"
    task_1 = make_task({'data': {task_data_field_name: 'photo1.jpg'}}, project)
    task_id_1 = task_1.id
 
    # Step 2-3: Filter by `data` with value `photo`, should return task 1
    filters = {
        'conjunction': 'and',
        'items': [
            {
                # data default name when label config is not yet set
                # and a file is uploaded directly
                'filter': 'filter:tasks:data.data',
                'operator': 'contains',
                'type': 'String',
                'value': 'photo',
            }
        ],
    }
    view_id = apply_filter_and_get_view_id(business_client, project_id, filters)
    response_ids = get_filtered_task_ids(business_client, view_id)
    assert set(response_ids) == {task_id_1}, f'Expected {[task_id_1]}, got {response_ids}'
 
    # Step 4: Set labeling config <View> <Image value="$image" name="img"/> </View>
    project.label_config = '<View> <Image value="$image" name="img"/> </View>'
    project.save()
 
    # Step 5-6: Filter by `image` with value `photo`, should return task 1
    filters['items'][0]['filter'] = 'filter:tasks:data.image'
    view_id = apply_filter_and_get_view_id(business_client, project_id, filters)
    response_ids = get_filtered_task_ids(business_client, view_id)
    assert set(response_ids) == {task_id_1}, f'Expected {[task_id_1]}, got {response_ids}'
 
    # Step 7: Add task 2: {"$undefined$": "photo2.jpg", "extra": "123"}
    task_2 = make_task({'data': {task_data_field_name: 'photo2.jpg', 'extra': '123'}}, project)
    task_id_2 = task_2.id
 
    # Step 8-9: Filter by "extra": "123", should return task 2
    filters['items'][0]['filter'] = 'filter:tasks:data.extra'
    filters['items'][0]['value'] = '123'
    view_id = apply_filter_and_get_view_id(business_client, project_id, filters)
    response_ids = get_filtered_task_ids(business_client, view_id)
    assert set(response_ids) == {task_id_2}, f'Expected {[task_id_2]}, got {response_ids}'
 
    # Step 10-11: Filter by "image" with value `photo`, should return task 1 and task 2
    filters['items'][0]['filter'] = 'filter:tasks:data.image'
    filters['items'][0]['value'] = 'photo'
    view_id = apply_filter_and_get_view_id(business_client, project_id, filters)
    response_ids = get_filtered_task_ids(business_client, view_id)
    assert set(response_ids) == {task_id_1, task_id_2}, f'Expected {[task_id_1, task_id_2]}, got {response_ids}'
 
    # Step 12: Update task 1 with {"extra": "456"}
    task_1.data['extra'] = '456'
    task_1.save()
 
    # we need to fully reset cache, because summary.update_data_columns()
    # can't work incrementally
    recalculate_created_annotations_and_labels_from_scratch(project, project.summary, 1)
 
    # Step 13: Check project.summary.common_data_columns, there should be ["$undefined$", "extra"]
    project.refresh_from_db()
    summary = ProjectSummary.objects.get(project=project)
    assert set(summary.common_data_columns) == {
        task_data_field_name,
        'extra',
    }, f"Expected {[task_data_field_name, 'extra']}, got {summary.common_data_columns}"
 
    # Step 14: Filter by "image" with "photo" should return task 1 and task 2
    # The filter is already set to 'photo' for 'data.image' from previous steps
    view_id = apply_filter_and_get_view_id(business_client, project_id, filters)
    response_ids = get_filtered_task_ids(business_client, view_id)
    assert set(response_ids) == {task_id_1, task_id_2}, f'Expected {[task_id_1, task_id_2]}, got {response_ids}'