Skip to content

Commit

Permalink
Add get_first_or_create method for CIVs (#3602)
Browse files Browse the repository at this point in the history
There can be multiple CIVs with the same interface and value/image/file
in the DB. Retrieve the first of those if that's the case rather than
throwing an error.

Closes #3594
  • Loading branch information
amickan authored Oct 9, 2024
1 parent b43c2f1 commit c371e83
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 10 deletions.
12 changes: 6 additions & 6 deletions app/grandchallenge/algorithms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,21 +666,21 @@ def retrieve_existing_civs(*, civ_data):
continue
elif civ.image:
try:
civ = ComponentInterfaceValue.objects.filter(
civs = ComponentInterfaceValue.objects.filter(
interface__slug=civ.interface_slug, image=civ.image
).get()
existing_civs.append(civ)
).all()
existing_civs.extend(civs)
except ObjectDoesNotExist:
continue
elif civ.file_civ:
existing_civs.append(civ.file_civ)
else:
# values can be of different types, including None and False
try:
civ = ComponentInterfaceValue.objects.filter(
civs = ComponentInterfaceValue.objects.filter(
interface__slug=civ.interface_slug, value=civ.value
).get()
existing_civs.append(civ)
).all()
existing_civs.extend(civs)
except ObjectDoesNotExist:
continue

Expand Down
16 changes: 14 additions & 2 deletions app/grandchallenge/components/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,15 @@ def component_interface_value_path(instance, filename):
)


class ComponentInterfaceValueManager(models.Manager):

def get_first_or_create(self, **kwargs):
try:
return self.get_or_create(**kwargs)
except MultipleObjectsReturned:
return self.filter(**kwargs).first(), False


class ComponentInterfaceValue(models.Model):
"""Encapsulates the value of an interface at a certain point in the graph."""

Expand Down Expand Up @@ -1238,6 +1247,8 @@ class ComponentInterfaceValue(models.Model):

_user_upload_validated = False

objects = ComponentInterfaceValueManager()

@property
def title(self):
if self.value is not None:
Expand Down Expand Up @@ -2249,7 +2260,7 @@ def create_civ_for_value(
):
current_value = current_civ.value if current_civ else None

civ, created = ComponentInterfaceValue.objects.get_or_create(
civ, created = ComponentInterfaceValue.objects.get_first_or_create(
interface=ci, value=new_value
)

Expand Down Expand Up @@ -2290,9 +2301,10 @@ def create_civ_for_image( # noqa: C901
):
current_image = current_civ.image if current_civ else None
if image and current_image != image:
civ, created = ComponentInterfaceValue.objects.get_or_create(
civ, created = ComponentInterfaceValue.objects.get_first_or_create(
interface=ci, image=image
)

if created:
try:
civ.full_clean()
Expand Down
2 changes: 1 addition & 1 deletion app/grandchallenge/components/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ def add_image_to_object(
interface=interface, upload_session=upload_session
)

civ, created = ComponentInterfaceValue.objects.get_or_create(
civ, created = ComponentInterfaceValue.objects.get_first_or_create(
interface=interface, image=image
)

Expand Down
22 changes: 21 additions & 1 deletion app/tests/components_tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from unittest.mock import call

import pytest
from django.core.exceptions import ValidationError
from django.core.exceptions import MultipleObjectsReturned, ValidationError
from django.core.files.base import ContentFile
from django.utils import timezone
from panimg.models import MAXIMUM_SEGMENTS_LENGTH
Expand Down Expand Up @@ -1581,3 +1581,23 @@ def test_all_examples_present():
assert set(INTERFACE_TYPE_JSON_EXAMPLES.keys()) == set(
InterfaceKind.interface_type_json()
)


@pytest.mark.django_db
def test_component_interface_value_manager():
ci = ComponentInterfaceFactory(kind=InterfaceKindChoices.STRING)
civ1, civ2 = ComponentInterfaceValueFactory.create_batch(
2, interface=ci, value="Foo"
)

with pytest.raises(MultipleObjectsReturned):
ComponentInterfaceValue.objects.get_or_create(
interface=ci, value="Foo"
)

civ, created = ComponentInterfaceValue.objects.get_first_or_create(
interface=ci, value="Foo"
)

assert civ == civ1
assert not created

0 comments on commit c371e83

Please sign in to comment.