Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions core/map_projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,16 @@ class Meta:
]


class MapProjectSerializer(serializers.ModelSerializer):
class MapProjectConfigurationsSerializer(serializers.ModelSerializer):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once CONFIGURATION_FIELDS lives on the model (see top-level review comment), replace the hardcoded list here with ['id', 'url'] + MapProject.CONFIGURATION_FIELDS. Also worth a one-line docstring on the class stating the rule (e.g. "Fields that define how a project matches — excluding identity, results, logs, and audit metadata. Used by the copy-project flow.") so the contract is discoverable.

Two field-scope questions worth confirming explicitly (in code or PR description):

  • description is in MapProjectCreateUpdateSerializer but excluded here — copy won't carry the description forward. Intentional? (Arguably correct since the description likely refers to the original project, but worth being deliberate.)
  • public_access is in the base serializer but excluded here — the copied project gets default access rather than inheriting. Likely correct, but same point: worth being deliberate.

class Meta:
model = MapProject
fields = [
'id', 'url', 'algorithms', 'encoder_model', 'filters', 'include_retired', 'lookup_config',
'score_configuration', 'target_repo_url'
]


class MapProjectSerializer(MapProjectConfigurationsSerializer):
created_by = CharField(source='created_by.username', read_only=True)
updated_by = CharField(source='updated_by.username', read_only=True)
owner = CharField(source='parent.mnemonic', read_only=True)
Expand All @@ -105,13 +114,12 @@ class MapProjectSerializer(serializers.ModelSerializer):

class Meta:
model = MapProject
fields = [
'id', 'name', 'input_file_name',
'created_by', 'updated_by', 'created_at', 'updated_at', 'url', 'is_active',
fields = MapProjectConfigurationsSerializer.Meta.fields + [
'name', 'input_file_name',
'created_by', 'updated_by', 'created_at', 'updated_at', 'is_active',
'owner', 'owner_type', 'owner_url', 'public_access',
'target_repo_url', 'summary', 'logs', 'include_retired',
'score_configuration', 'filters', 'candidates', 'algorithms', 'lookup_config', 'analysis',
'encoder_model'
'summary', 'logs', 'include_retired',
'candidates', 'analysis',
]

def __init__(self, *args, **kwargs):
Expand All @@ -138,6 +146,7 @@ class Meta:
model = MapProject
fields = MapProjectSerializer.Meta.fields + ['file_url', 'matches', 'columns']


class MapProjectLogsSerializer(serializers.ModelSerializer):
class Meta:
model = MapProject
Expand Down
33 changes: 33 additions & 0 deletions core/map_projects/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def setUp(self):

self.file = SimpleUploadedFile('input.csv', b'content', "application/csv")


class MapProjectListViewTest(MapProjectAbstractViewTest):
@patch('core.services.storages.cloud.aws.S3.upload')
def test_post(self, upload_mock):
Expand Down Expand Up @@ -120,3 +121,35 @@ def test_put(self, upload_mock):
self.assertEqual(len(response.data['columns']), 1)
upload_mock.assert_called_once_with(
key=f"map_projects/{response.data['id']}/input.csv", file_content=ANY)


class MapProjectConfigurationsViewTest(MapProjectAbstractViewTest):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test asserts that included fields are correct, but doesn't assert the ticket's actual contract: that excluded fields are absent. Please add assertions that the response does NOT contain candidates, logs, summary, analysis, input_file_name, matches, columns. That's what guarantees we're not leaking project identity/results into a copy.

Also missing:

  • A negative auth test (unauthorized user → 403/404). Other map-project views are protected by CanEditConceptDictionary; one assertion confirming this endpoint inherits that protection would lock it down.
  • A 404 test for a non-existent project ID.

def test_get(self):
project = MapProjectFactory(
organization=self.org,
algorithms=[{'name': 'exact-match', 'enabled': True}],
encoder_model='snowflake-arctic-embed-l-v2.0',
filters={'retired': False, 'class': ['LabSet']},
include_retired=True,
lookup_config={'concepts': {'limit': 20}},
score_configuration={'recommended': 95, 'available': 75},
target_repo_url='/orgs/CIEL/sources/CIEL/',
)
project.save()

response = self.client.get(
f'/orgs/CIEL/map-projects/{project.id}/configurations/',
HTTP_AUTHORIZATION='Token ' + self.user.get_token(),
)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['id'], project.id)
self.assertIsNotNone(response.data['id'])
self.assertEqual(response.data['url'], f'/orgs/CIEL/map-projects/{project.id}/')
self.assertEqual(response.data['algorithms'], [{'name': 'exact-match', 'enabled': True}])
self.assertEqual(response.data['encoder_model'], 'snowflake-arctic-embed-l-v2.0')
self.assertEqual(response.data['filters'], {'retired': False, 'class': ['LabSet']})
self.assertTrue(response.data['include_retired'])
self.assertEqual(response.data['lookup_config'], {'concepts': {'limit': 20}})
self.assertEqual(response.data['score_configuration'], {'recommended': 95, 'available': 75})
self.assertEqual(response.data['target_repo_url'], '/orgs/CIEL/sources/CIEL/')
4 changes: 4 additions & 0 deletions core/map_projects/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
path('<int:project>/', views.MapProjectView.as_view(), name='map-project'),
path('<int:project>/summary/', views.MapProjectSummaryView.as_view(), name='map-project-summary'),
path('<int:project>/logs/', views.MapProjectLogsView.as_view(), name='map-project-logs'),
path(
'<int:project>/configurations/', views.MapProjectConfigurationsView.as_view(),
name='map-project-configurations'
),
]
10 changes: 9 additions & 1 deletion core/map_projects/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from core.common.views import BaseAPIView
from core.map_projects.models import MapProject
from core.map_projects.serializers import MapProjectCreateUpdateSerializer, \
MapProjectDetailSerializer, MapProjectSummarySerializer, MapProjectLogsSerializer, MapProjectListSerializer
MapProjectDetailSerializer, MapProjectSummarySerializer, MapProjectLogsSerializer, MapProjectListSerializer, \
MapProjectConfigurationsSerializer


class MapProjectBaseView(BaseAPIView):
Expand Down Expand Up @@ -61,6 +62,13 @@ def get_serializer_class(self):
return self.serializer_class


class MapProjectConfigurationsView(MapProjectBaseView, RetrieveAPIView):
serializer_class = MapProjectConfigurationsSerializer
lookup_url_kwarg = 'project'
lookup_field = 'project'
pk_field = 'id'


class MapProjectSummaryView(MapProjectBaseView, RetrieveAPIView):
serializer_class = MapProjectSummarySerializer
lookup_url_kwarg = 'project'
Expand Down
Loading