Skip to content

Commit

Permalink
Merge branch 'release/0.3.20' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
erikvw committed Jul 12, 2023
2 parents 846e0e1 + 153c1d8 commit 9e9d87b
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 38 deletions.
27 changes: 18 additions & 9 deletions edc_reference/models/managers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Iterable

from django.core.exceptions import ObjectDoesNotExist
from django.db import models

if TYPE_CHECKING:
from edc_reference.models import Reference


class ReferenceManager(models.Manager):
def get_by_natural_key(
Expand All @@ -25,7 +32,7 @@ def get_by_natural_key(
field_name=field_name,
)

def filter_crf_for_visit(self, name=None, visit=None):
def filter_crf_for_visit(self, name=None, visit=None) -> Iterable[Reference]:
"""Returns a queryset of reference model instances
for this model on this visit.
"""
Expand All @@ -41,28 +48,30 @@ def filter_crf_for_visit(self, name=None, visit=None):

return self.filter(**opts)

def get_crf_for_visit(self, name=None, visit=None, field_name=None):
def get_crf_for_visit(
self, name=None, related_visit=None, field_name=None
) -> Reference | None:
"""Returns an instance of reference model
for this model on this visit for this field.
visit is a visit model instance.
"""
try:
model_obj = self.get(
identifier=visit.subject_identifier,
identifier=related_visit.subject_identifier,
model=name,
report_datetime=visit.report_datetime,
visit_schedule_name=visit.visit_schedule_name,
schedule_name=visit.schedule_name,
visit_code=visit.visit_code,
timepoint=visit.timepoint,
report_datetime=related_visit.report_datetime,
visit_schedule_name=related_visit.visit_schedule_name,
schedule_name=related_visit.schedule_name,
visit_code=related_visit.visit_code,
timepoint=related_visit.timepoint,
field_name=field_name,
)
except ObjectDoesNotExist:
model_obj = None
return model_obj

def get_requisition_for_visit(self, name=None, visit=None):
def get_requisition_for_visit(self, name=None, visit=None) -> Reference:
"""Returns an instance of reference model
for this requisition on this visit for this panel.
Expand Down
25 changes: 18 additions & 7 deletions edc_reference/reference/reference_deleter.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from django.apps import apps as django_apps
from django.db import transaction

from ..site_reference import site_reference_configs

if TYPE_CHECKING:
from ..models import Reference


class ReferenceDeleter:

"""A class to delete all instances for the reference
model for this model instance.
"""A class to delete all instances from edc_reference.Reference
model linked to this Crf or Requisition model instance.
See signals.
See signals and edc_reference.Reference.
"""

def __init__(self, model_obj=None):
reference_model = site_reference_configs.get_reference_model(
name=model_obj.reference_name
)
self.reference_model_cls = django_apps.get_model(reference_model)
self.model_obj = model_obj
self.reference_objects = self.reference_model_cls.objects.filter(**self.options)
self.reference_model_cls: Reference = django_apps.get_model(reference_model)
with transaction.atomic():
self.reference_objects.delete()
self.reference_model_cls.objects.filter(**self.options).delete()

@property
def options(self):
def options(self) -> dict[str, Any]:
"""Returns query lookup options.
Note: `Reference` model instances for requisitions use the
`label_lower.panel_name` format for field `reference_name`.
"""
return dict(
identifier=self.model_obj.related_visit.subject_identifier,
report_datetime=self.model_obj.related_visit.report_datetime,
Expand Down
44 changes: 32 additions & 12 deletions edc_reference/reference/reference_getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import datetime
from decimal import Decimal
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Type

from django.apps import apps as django_apps
from django.core.exceptions import ObjectDoesNotExist
Expand Down Expand Up @@ -43,14 +43,14 @@ def __init__(
visit_code_sequence: str | None = None,
timepoint: Decimal | None = None,
site=None,
create=None,
create: bool | None = None,
):
self._object = None
self.created = None
self._object: Reference | None = None
self.created: bool = False
self.value = None
self.has_value = False
self.has_value: bool = False

self.create = create
self.create = True if create is None else create
self.field_name = field_name
if model_obj:
try:
Expand Down Expand Up @@ -98,12 +98,12 @@ def __init__(
self.visit_code = visit_code
self.visit_code_sequence = visit_code_sequence
self.visit_schedule_name = visit_schedule_name
reference_model = site_reference_configs.get_reference_model(name=self.name)
self.reference_model_cls = django_apps.get_model(reference_model)
reference_model: str = site_reference_configs.get_reference_model(name=self.name)
self.reference_model_cls: Type[Reference] = django_apps.get_model(reference_model)

# note: updater needs to "update_value"
# if 'object' does not exist, will be created
self.value = getattr(self.object, "value")
self.value = getattr(self.reference_obj, "value")
self.has_value = True
setattr(self, self.field_name, self.value)

Expand All @@ -115,10 +115,9 @@ def __repr__(self):
)

@property
def object(self) -> Reference:
def reference_obj(self) -> Reference:
"""Returns a reference model instance."""
if not self._object:
self.created = False
try:
self._object = self.reference_model_cls.objects.get(**self.options)
except ObjectDoesNotExist as e:
Expand All @@ -134,7 +133,10 @@ def object(self) -> Reference:
)
self.created = True
else:
raise ReferenceObjectDoesNotExist(f"{e}. Using {self.options}")
raise ReferenceObjectDoesNotExist(
f"Unable to create reference. create=False! Options=`{self.options}`. "
f"Got {e}."
)
return self._object

@property
Expand All @@ -155,4 +157,22 @@ def options(self) -> dict:
"Unable to get a reference instance. Null values for attrs "
f"not allowed. {self}. Got {opts}."
)
if self.field_name not in [
f.name for f in django_apps.get_model(self.label_lower)._meta.get_fields()
]:
raise ReferenceGetterError(
"Unable to get reference instance. Field does not exist on source model. "
f"Got {self.label_lower}.{self.field_name}. See {self}."
)

return opts

@property
def label_lower(self) -> str:
"""Returns label_lower.
Note: for `References` linked to requisitions, `Reference.model`
is `app_label.model.panel_name`, for example,
`my_app.subjectrequisition.glucose`.
"""
return f"{self.name.split('.')[0]}.{self.name.split('.')[1]}"
4 changes: 2 additions & 2 deletions edc_reference/reference/reference_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ def __init__(self, model_obj=None):
else:
internal_type = "UUIDField"
related_name = getattr(model_obj, field_name)._meta.label_lower
reference_getter.object.update_value(
reference_getter.reference_obj.update_value(
internal_type=internal_type,
value=value,
related_name=related_name,
)
reference_getter.object.save()
reference_getter.reference_obj.save()
13 changes: 5 additions & 8 deletions edc_reference/tests/tests/test_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,8 @@ def test_reference_getter_without_using_model_obj_and_missing_visit_attr(self):
reference = ReferenceGetter(**opts)
self.assertEqual(reference.field_int, integer)

def test_reference_getter_raises(self):
"""Assert raises if reference instance does not exist
def test_reference_getter_raises_if_create_is_false(self):
"""Assert raises if Reference model instance does not exist
and not create.
"""
opts = dict(
Expand All @@ -487,6 +487,7 @@ def test_reference_getter_raises(self):
visit_code_sequence=self.subject_visit.visit_code_sequence,
timepoint=self.subject_visit.timepoint,
site=self.subject_visit.site,
create=False,
)
self.assertRaises(ReferenceObjectDoesNotExist, ReferenceGetter, **opts)

Expand All @@ -504,16 +505,12 @@ def test_reference_getter_with_bad_field_raises(self):
integer = 100
crf_one = CrfOne.objects.create(subject_visit=self.subject_visit, field_int=integer)
self.assertRaises(
ReferenceObjectDoesNotExist,
ReferenceGetterError,
ReferenceGetter,
name="reference_app.crfone",
field_name="blah",
model_obj=crf_one,
)
try:
ReferenceGetter(field_name="blah", name="reference_app.crfone", model_obj=crf_one)
except ReferenceObjectDoesNotExist:
pass

def test_reference_getter_object_doesnotexist(self):
crf_one_obj = CrfOne.objects.create(
Expand All @@ -524,7 +521,7 @@ def test_reference_getter_object_doesnotexist(self):
field_datetime=get_utcnow(),
)
self.assertRaises(
ReferenceObjectDoesNotExist,
ReferenceGetterError,
ReferenceGetter,
field_name="blah",
model_obj=crf_one_obj,
Expand Down

0 comments on commit 9e9d87b

Please sign in to comment.