"""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 rest_framework import status
from ..utils import project_id # noqa
pytestmark = pytest.mark.django_db
def test_views_api(business_client, project_id):
# create
payload = dict(project=project_id, data={'test': 1})
response = business_client.post(
'/api/dm/views/',
data=json.dumps(payload),
content_type='application/json',
)
assert response.status_code == 201, response.content
# list
response = business_client.get(
'/api/dm/views/',
)
assert response.status_code == 200, response.content
assert response.json()[0]['project'] == project_id
view_id = response.json()[0]['id']
# partial update
updated_payload = dict(data={'test': 2})
response = business_client.patch(
f'/api/dm/views/{view_id}/',
data=json.dumps(updated_payload),
content_type='application/json',
)
assert response.status_code == 200, response.content
# retrieve
response = business_client.get(
f'/api/dm/views/{view_id}/',
)
assert response.status_code == 200, response.content
assert response.json()['data'] == updated_payload['data']
# reset
response = business_client.delete(
'/api/dm/views/reset',
data=json.dumps(dict(project=project_id)),
content_type='application/json',
)
assert response.status_code == 204, response.content
response = business_client.get('/api/dm/views/')
assert response.json() == []
def test_views_api_filter_project(business_client):
# create project
response = business_client.post(
'/api/projects/',
data=json.dumps(dict(title='test_project1')),
content_type='application/json',
)
project1_id = response.json()['id']
business_client.post(
'/api/dm/views/',
data=json.dumps(dict(project=project1_id)),
content_type='application/json',
)
response = business_client.post(
'/api/projects/',
data=json.dumps(dict(title='test_project2')),
content_type='application/json',
)
project2_id = response.json()['id']
business_client.post(
'/api/dm/views/',
data=json.dumps(dict(project=project2_id)),
content_type='application/json',
)
# list all
response = business_client.get('/api/dm/views/')
assert response.status_code == 200, response.content
assert len(response.json()) == 2
# filtered list
response = business_client.get(f'/api/dm/views/?project={project1_id}')
assert response.status_code == 200, response.content
assert response.json()[0]['project'] == project1_id
# filtered reset
response = business_client.delete(
'/api/dm/views/reset/',
data=json.dumps(dict(project=project1_id)),
content_type='application/json',
)
assert response.status_code == 204, response.content
# filtered list
response = business_client.get(f'/api/dm/views/?project={project2_id}')
assert len(response.json()) == 1
assert response.json()[0]['project'] == project2_id
def test_views_api_filters(business_client, project_id):
# create
payload = dict(
project=project_id,
data={
'filters': {
'conjunction': 'or',
'items': [
{
'filter': 'filter:tasks:data.image',
'operator': 'contains',
'type': 'Image',
'value': {},
},
{
'filter': 'filter:tasks:data.image',
'operator': 'equal',
'type': 'Image',
'value': {},
},
],
}
},
)
response = business_client.post(
'/api/dm/views/',
data=json.dumps(payload),
content_type='application/json',
)
assert response.status_code == 201, response.content
view_id = response.json()['id']
# retrieve
response = business_client.get(
f'/api/dm/views/{view_id}/',
)
assert response.status_code == 200, response.content
assert response.json()['data'] == payload['data']
updated_payload = dict(
project=project_id,
data={
'filters': {
'conjunction': 'and',
'items': [
{
'filter': 'filter:tasks:data.text',
'operator': 'equal',
'type': 'Text',
'value': {},
},
{
'filter': 'filter:tasks:data.text',
'operator': 'contains',
'type': 'Text',
'value': {},
},
],
}
},
)
response = business_client.put(
f'/api/dm/views/{view_id}/',
data=json.dumps(updated_payload),
content_type='application/json',
)
assert response.status_code == 200, response.content
# check after update
response = business_client.get(
f'/api/dm/views/{view_id}/',
)
assert response.status_code == 200, response.content
assert response.json()['data'] == updated_payload['data']
def test_views_api_nested_filters(business_client, project_id):
"""Test creating views with nested filters using child filters.
This test validates the nested filter structure where a parent filter
can have child filters that are AND-merged with the parent. This is
similar to the enterprise annotations_results_json filters but uses
regular task data filters.
"""
# Create a project with specific label config for testing
project_response = business_client.post(
'/api/projects/',
data=json.dumps(
{
'title': 'test_nested_filters',
'label_config': """
""",
}
),
content_type='application/json',
)
assert project_response.status_code == 201
project = project_response.json()
# Create tasks with different data
task1_data = {'text': 'task1', 'category': 'A'}
task1_response = business_client.post(
f'/api/projects/{project["id"]}/tasks',
data=json.dumps({'data': task1_data}),
content_type='application/json',
)
assert task1_response.status_code == 201
task1 = task1_response.json()
task2_data = {'text': 'task2', 'category': 'B'}
task2_response = business_client.post(
f'/api/projects/{project["id"]}/tasks',
data=json.dumps({'data': task2_data}),
content_type='application/json',
)
assert task2_response.status_code == 201
task2 = task2_response.json()
task3_data = {'text': 'task3', 'category': 'A'}
task3_response = business_client.post(
f'/api/projects/{project["id"]}/tasks',
data=json.dumps({'data': task3_data}),
content_type='application/json',
)
assert task3_response.status_code == 201
task3 = task3_response.json()
# Create a view with nested filters
# Parent filter: tasks with category 'A'
# Child filter: tasks with text containing 'task1'
nested_filter_payload = {
'project': project['id'],
'data': {
'filters': {
'conjunction': 'and',
'items': [
{
'filter': 'filter:tasks:data.category',
'operator': 'equal',
'type': 'String',
'value': 'A',
'child_filter': {
'filter': 'filter:tasks:data.text',
'operator': 'contains',
'type': 'String',
'value': 'task1',
},
}
],
}
},
}
response = business_client.post(
'/api/dm/views/',
data=json.dumps(nested_filter_payload),
content_type='application/json',
)
assert response.status_code == 201, response.content
view_id = response.json()['id']
# Retrieve the created view and verify the nested structure
response = business_client.get(f'/api/dm/views/{view_id}/')
assert response.status_code == 200, response.content
view_data = response.json()['data']
filter_data = view_data['filters']
# Verify the filter structure
assert filter_data['conjunction'] == 'and'
assert len(filter_data['items']) == 1
root_filter = filter_data['items'][0]
assert root_filter['filter'] == 'filter:tasks:data.category'
assert root_filter['operator'] == 'equal'
assert root_filter['type'] == 'String'
assert root_filter['value'] == 'A'
# Verify child filter structure
assert 'child_filter' in root_filter
child_filter = root_filter['child_filter']
assert child_filter['filter'] == 'filter:tasks:data.text'
assert child_filter['operator'] == 'contains'
assert child_filter['type'] == 'String'
assert child_filter['value'] == 'task1'
# Test that the view filters tasks correctly
# Only task1 should match: category='A' AND text contains 'task1'
response = business_client.get(f'/api/tasks?view={view_id}')
assert response.status_code == 200, response.content
tasks = response.json()['tasks']
assert len(tasks) == 1
assert tasks[0]['id'] == task1['id']
# Test with a different nested filter structure
# Parent filter: tasks with category 'A' or 'B'
# Child filter: tasks with text containing 'task'
complex_nested_payload = {
'project': project['id'],
'data': {
'filters': {
'conjunction': 'or',
'items': [
{
'filter': 'filter:tasks:data.category',
'operator': 'equal',
'type': 'String',
'value': 'A',
'child_filter': {
'filter': 'filter:tasks:data.text',
'operator': 'contains',
'type': 'String',
'value': 'task',
},
},
{
'filter': 'filter:tasks:data.category',
'operator': 'equal',
'type': 'String',
'value': 'B',
'child_filter': {
'filter': 'filter:tasks:data.text',
'operator': 'contains',
'type': 'String',
'value': 'task',
},
},
],
}
},
}
response = business_client.post(
'/api/dm/views/',
data=json.dumps(complex_nested_payload),
content_type='application/json',
)
assert response.status_code == 201, response.content
complex_view_id = response.json()['id']
# Test the complex nested filter
response = business_client.get(f'/api/tasks?view={complex_view_id}')
assert response.status_code == 200, response.content
tasks = response.json()['tasks']
# Should match all tasks: (category='A' AND text contains 'task') OR (category='B' AND text contains 'task')
assert len(tasks) == 3
task_ids = [task['id'] for task in tasks]
assert task1['id'] in task_ids
assert task2['id'] in task_ids
assert task3['id'] in task_ids
def test_views_api_patch_add_child_filter(business_client, project_id):
"""Test creating a view with a non-nested filter, then PATCHing it to add a child filter.
This test validates the behavior of updating a view's filter structure by adding
child filters to existing filters through PATCH requests.
"""
# Create a project with specific label config for testing
project_response = business_client.post(
'/api/projects/',
data=json.dumps(
{
'title': 'test_patch_child_filter',
'label_config': """
""",
}
),
content_type='application/json',
)
assert project_response.status_code == 201
project = project_response.json()
# Create tasks with different data
task1_data = {'text': 'task1', 'category': 'A'}
task1_response = business_client.post(
f'/api/projects/{project["id"]}/tasks',
data=json.dumps({'data': task1_data}),
content_type='application/json',
)
assert task1_response.status_code == 201
task1 = task1_response.json()
task2_data = {'text': 'task2', 'category': 'A'}
task2_response = business_client.post(
f'/api/projects/{project["id"]}/tasks',
data=json.dumps({'data': task2_data}),
content_type='application/json',
)
assert task2_response.status_code == 201
task2 = task2_response.json()
task3_data = {'text': 'task3', 'category': 'B'}
task3_response = business_client.post(
f'/api/projects/{project["id"]}/tasks',
data=json.dumps({'data': task3_data}),
content_type='application/json',
)
assert task3_response.status_code == 201
task3 = task3_response.json()
# Step 1: Create a view with a non-nested filter
# Filter: tasks with category 'A'
simple_filter_payload = {
'project': project['id'],
'data': {
'filters': {
'conjunction': 'and',
'items': [
{
'filter': 'filter:tasks:data.category',
'operator': 'equal',
'type': 'String',
'value': 'A',
}
],
}
},
}
response = business_client.post(
'/api/dm/views/',
data=json.dumps(simple_filter_payload),
content_type='application/json',
)
assert response.status_code == 201, response.content
view_id = response.json()['id']
# Verify the initial view has no child filters
response = business_client.get(f'/api/dm/views/{view_id}/')
assert response.status_code == 200, response.content
view_data = response.json()['data']
filter_data = view_data['filters']
# Verify the initial filter structure (no child filters)
assert filter_data['conjunction'] == 'and'
assert len(filter_data['items']) == 1
root_filter = filter_data['items'][0]
assert root_filter['filter'] == 'filter:tasks:data.category'
assert root_filter['operator'] == 'equal'
assert root_filter['type'] == 'String'
assert root_filter['value'] == 'A'
# Verify no child filter exists initially
assert 'child_filter' not in root_filter
# Test that the initial view filters tasks correctly
# Should match task1 and task2 (both have category='A')
response = business_client.get(f'/api/tasks?view={view_id}')
assert response.status_code == 200, response.content
tasks = response.json()['tasks']
assert len(tasks) == 2
task_ids = [task['id'] for task in tasks]
assert task1['id'] in task_ids
assert task2['id'] in task_ids
assert task3['id'] not in task_ids
# Step 2: PATCH the view to add a child filter
# Add child filter: tasks with text containing 'task1'
patch_payload = {
'data': {
'filters': {
'conjunction': 'and',
'items': [
{
'filter': 'filter:tasks:data.category',
'operator': 'equal',
'type': 'String',
'value': 'A',
'child_filter': {
'filter': 'filter:tasks:data.text',
'operator': 'contains',
'type': 'String',
'value': 'task1',
},
}
],
}
},
}
response = business_client.patch(
f'/api/dm/views/{view_id}/',
data=json.dumps(patch_payload),
content_type='application/json',
)
assert response.status_code == 200, response.content
# Step 3: Verify the PATCHed view has the child filter
response = business_client.get(f'/api/dm/views/{view_id}/')
assert response.status_code == 200, response.content
view_data = response.json()['data']
filter_data = view_data['filters']
# Verify the updated filter structure (now has child filter)
assert filter_data['conjunction'] == 'and'
assert len(filter_data['items']) == 1
root_filter = filter_data['items'][0]
assert root_filter['filter'] == 'filter:tasks:data.category'
assert root_filter['operator'] == 'equal'
assert root_filter['type'] == 'String'
assert root_filter['value'] == 'A'
# Verify child filter was added
assert 'child_filter' in root_filter
child_filter = root_filter['child_filter']
assert child_filter['filter'] == 'filter:tasks:data.text'
assert child_filter['operator'] == 'contains'
assert child_filter['type'] == 'String'
assert child_filter['value'] == 'task1'
# Step 4: Test that the PATCHed view filters tasks correctly
# Should now only match task1: category='A' AND text contains 'task1'
response = business_client.get(f'/api/tasks?view={view_id}')
assert response.status_code == 200, response.content
tasks = response.json()['tasks']
assert len(tasks) == 1
assert tasks[0]['id'] == task1['id']
# Step 5: PATCH again to modify the child filter
# Change child filter to: tasks with text containing 'task'
patch_payload_2 = {
'data': {
'filters': {
'conjunction': 'and',
'items': [
{
'filter': 'filter:tasks:data.category',
'operator': 'equal',
'type': 'String',
'value': 'A',
'child_filter': {
'filter': 'filter:tasks:data.text',
'operator': 'contains',
'type': 'String',
'value': 'task',
},
}
],
}
},
}
response = business_client.patch(
f'/api/dm/views/{view_id}/',
data=json.dumps(patch_payload_2),
content_type='application/json',
)
assert response.status_code == 200, response.content
# Step 6: Verify the child filter was updated
response = business_client.get(f'/api/dm/views/{view_id}/')
assert response.status_code == 200, response.content
view_data = response.json()['data']
filter_data = view_data['filters']
root_filter = filter_data['items'][0]
child_filter = root_filter['child_filter']
assert child_filter['value'] == 'task' # Updated value
# Test that the updated view filters tasks correctly
# Should now match task1 and task2: category='A' AND text contains 'task'
response = business_client.get(f'/api/tasks?view={view_id}')
assert response.status_code == 200, response.content
tasks = response.json()['tasks']
assert len(tasks) == 2
task_ids = [task['id'] for task in tasks]
assert task1['id'] in task_ids
assert task2['id'] in task_ids
assert task3['id'] not in task_ids
def test_views_ordered_by_id(business_client, project_id):
views = [{'view_data': 1}, {'view_data': 2}, {'view_data': 3}]
for view in views:
payload = dict(project=project_id, data=view)
business_client.post(
'/api/dm/views/',
data=json.dumps(payload),
content_type='application/json',
)
response = business_client.get('/api/dm/views/')
data = response.json()
assert response.status_code == status.HTTP_200_OK
ids = [view['id'] for view in data]
assert ids == sorted(ids)
def test_update_views_order(business_client, project_id):
# Create views
views = [{'view_data': 1}, {'view_data': 2}, {'view_data': 3}]
view_ids = []
for view in views:
payload = dict(project=project_id, data=view)
response = business_client.post(
'/api/dm/views/',
data=json.dumps(payload),
content_type='application/json',
)
assert response.status_code == status.HTTP_201_CREATED
view_ids.append(response.json()['id'])
# Update the order of views
new_order = {'project': project_id, 'ids': [view_ids[2], view_ids[0], view_ids[1]]}
response = business_client.post(
'/api/dm/views/order/',
data=json.dumps(new_order),
content_type='application/json',
)
assert response.status_code == status.HTTP_200_OK
# Verify the new order
response = business_client.get('/api/dm/views/')
data = response.json()
assert response.status_code == status.HTTP_200_OK
returned_ids = [view['id'] for view in data]
assert returned_ids == new_order['ids']