Skip to content

Commit

Permalink
Merge branch 'release/0.3.79' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
erikvw committed Dec 3, 2023
2 parents ec9d9f2 + 227239f commit 6219da6
Show file tree
Hide file tree
Showing 19 changed files with 458 additions and 181 deletions.
9 changes: 9 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@

0.3.79
------
- review and change indexes on CRF/Requisition models
- convert unique_together to UniqueConstraint
- refactor Creator classes to refer differentiate between
`metadata` model instance and `source` model instance
- typing hints

0.3.63
------
- add RuleGroup Meta class attribute `predicates`.
Expand Down
34 changes: 17 additions & 17 deletions edc_metadata/metadata/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@
from edc_visit_tracking.constants import MISSED_VISIT

from ..constants import KEYED, NOT_REQUIRED, REQUIRED
from ..source_model_metadata_mixin import SourceModelMetadataMixin
from ..metadata_mixins import SourceModelMetadataMixin
from ..utils import verify_model_cls_registered_with_admin

if TYPE_CHECKING:
from edc_model.models import BaseUuidModel
from edc_sites.model_mixins import SiteModelMixin
from edc_visit_schedule.visit import Crf, Requisition
from edc_visit_tracking.typing_stubs import RelatedVisitProtocol
from edc_visit_tracking.model_mixins import VisitModelMixin as Base

from ..model_mixins.creates import CreatesMetadataModelMixin
from ..models import CrfMetadata, RequisitionMetadata

class RelatedVisitModel(SiteModelMixin, CreatesMetadataModelMixin, Base, BaseUuidModel):
pass


class CreatesMetadataError(Exception):
pass
Expand Down Expand Up @@ -48,7 +54,7 @@ class CrfCreator(SourceModelMetadataMixin):

def __init__(
self,
related_visit: RelatedVisitProtocol,
related_visit: RelatedVisitModel,
update_keyed: bool,
crf: Crf | Requisition,
) -> None:
Expand Down Expand Up @@ -131,7 +137,7 @@ def update_entry_status_to_default_or_keyed(
Note: that the default `entry_status` may be changed by rules
later on.
"""
if metadata_obj.entry_status != KEYED and self.is_keyed:
if metadata_obj.entry_status != KEYED and self.source_model_obj_exists:
metadata_obj.entry_status = KEYED
metadata_obj.save(update_fields=["entry_status"])
metadata_obj.refresh_from_db()
Expand All @@ -154,7 +160,7 @@ def __init__(
self,
requisition: Requisition,
update_keyed: bool,
related_visit: RelatedVisitProtocol,
related_visit: RelatedVisitModel,
) -> None:
super().__init__(
crf=requisition,
Expand All @@ -174,15 +180,9 @@ def query_options(self) -> dict:
return query_options

@property
def source_model_obj(self):
if not self._source_model_obj:
try:
self._source_model_obj = self.source_model_cls.objects.get(
subject_visit=self.related_visit, panel__name=self.requisition.panel.name
)
except ObjectDoesNotExist:
self._source_model_obj = None
return self._source_model_obj
def source_model_options(self) -> dict:
"""Source model query options"""
return dict(subject_visit=self.related_visit, panel__name=self.requisition.panel.name)


class Creator:
Expand All @@ -192,7 +192,7 @@ class Creator:
def __init__(
self,
update_keyed: bool,
related_visit: RelatedVisitProtocol,
related_visit: RelatedVisitModel,
) -> None:
self.related_visit = related_visit
self.update_keyed = update_keyed
Expand Down Expand Up @@ -266,7 +266,7 @@ class Destroyer:
metadata_crf_model = "edc_metadata.crfmetadata"
metadata_requisition_model = "edc_metadata.requisitionmetadata"

def __init__(self, related_visit: RelatedVisitProtocol) -> None:
def __init__(self, related_visit: RelatedVisitModel) -> None:
self.related_visit = related_visit

@property
Expand Down Expand Up @@ -300,7 +300,7 @@ class Metadata:

def __init__(
self,
related_visit: RelatedVisitProtocol,
related_visit: RelatedVisitModel,
update_keyed: bool,
) -> None:
self._reason = None
Expand Down
38 changes: 19 additions & 19 deletions edc_metadata/metadata/metadata_getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@

from django.apps import apps as django_apps
from django.contrib.admin.sites import all_sites
from django.core.exceptions import (
ImproperlyConfigured,
MultipleObjectsReturned,
ObjectDoesNotExist,
)
from django.core.exceptions import MultipleObjectsReturned
from django.db.models import QuerySet

from .metadata import model_cls_registered_with_admin_site
Expand Down Expand Up @@ -43,34 +39,38 @@ def validate_metadata_object(self) -> None:
if self.metadata_obj:
# confirm model class exists
try:
model_cls = django_apps.get_model(self.metadata_obj.model)
source_model_cls = django_apps.get_model(self.metadata_obj.model)
except LookupError:
self.metadata_obj.delete()
self.metadata_obj = None
else:
if not model_cls_registered_with_admin_site(model_cls):
if not model_cls_registered_with_admin_site(source_model_cls):
warn(
"Model class not registered with Admin. "
f"Deleting related metadata. Got {model_cls}."
f"Deleting related metadata. Got {source_model_cls}."
)
self.metadata_obj.delete()
self.metadata_obj = None
else:
# confirm metadata.entry_status is correct
query_attrs = {
f"{model_cls.related_visit_model_attr()}": self.related_visit
f"{source_model_cls.related_visit_model_attr()}": self.related_visit
}
query_attrs.update(**self.extra_query_attrs)
try:
model_cls.objects.get(**query_attrs)
except AttributeError as e:
if "related_visit_model_attr" not in str(e):
raise ImproperlyConfigured(f"{e} See {repr(model_cls)}")
raise
except ObjectDoesNotExist:
pass
except MultipleObjectsReturned:
raise
if source_model_cls.objects.filter(**query_attrs).values("id").count() > 1:
raise MultipleObjectsReturned(
f"{source_model_cls._meta.label_lower} {self.related_visit}"
)
# try:
# model_cls.objects.get(**query_attrs)
# except AttributeError as e:
# if "related_visit_model_attr" not in str(e):
# raise ImproperlyConfigured(f"{e} See {repr(model_cls)}")
# raise
# except ObjectDoesNotExist:
# pass
# except MultipleObjectsReturned:
# raise

@staticmethod
def model_cls_registered_with_admin_site(model_cls: Any) -> bool:
Expand Down
12 changes: 9 additions & 3 deletions edc_metadata/metadata_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
from .metadata import Creator

if TYPE_CHECKING:
from edc_visit_tracking.typing_stubs import RelatedVisitProtocol
from edc_model.models import BaseUuidModel
from edc_sites.model_mixins import SiteModelMixin
from edc_visit_tracking.model_mixins import VisitModelMixin as Base

from .model_mixins.creates import CreatesMetadataModelMixin
from .models import CrfMetadata, RequisitionMetadata

class RelatedVisitModel(SiteModelMixin, CreatesMetadataModelMixin, Base, BaseUuidModel):
pass


class MetadataHandlerError(Exception):
pass
Expand All @@ -31,7 +37,7 @@ class MetadataHandler:
def __init__(
self,
metadata_model: str = None,
related_visit: RelatedVisitProtocol = None,
related_visit: RelatedVisitModel = None,
model: str = None,
allow_create: bool | None = None,
):
Expand All @@ -57,7 +63,7 @@ def metadata_obj(self) -> CrfMetadata | RequisitionMetadata:
if self.allow_create:
metadata_obj = self._create()
else:
raise MetadataObjectDoesNotExist(f"Unable to metadata run rule. Got `{e}`")
raise MetadataObjectDoesNotExist(f"Unable to run metadata rule. Got `{e}`")
return metadata_obj

def _create(self) -> CrfMetadata | RequisitionMetadata:
Expand Down
1 change: 1 addition & 0 deletions edc_metadata/metadata_mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .source_model_metadata_mixin import SourceModelMetadataMixin
76 changes: 76 additions & 0 deletions edc_metadata/metadata_mixins/source_model_metadata_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Type

from django.apps import apps as django_apps
from django.core.exceptions import ObjectDoesNotExist

if TYPE_CHECKING:
from datetime import datetime

from edc_crf.model_mixins import CrfModelMixin
from edc_model.models import BaseUuidModel
from edc_sites.model_mixins import SiteModelMixin
from edc_visit_tracking.model_mixins import VisitModelMixin as Base

from ..model_mixins.creates import CreatesMetadataModelMixin

class RelatedVisitModel(SiteModelMixin, CreatesMetadataModelMixin, Base, BaseUuidModel):
pass


class SourceModelMetadataMixin:
"""Mixin class for Metadata and MetadataUpdater class."""

def __init__(self, source_model: str, related_visit: RelatedVisitModel = None):
self._source_model_obj = None
self._source_model = source_model
self.related_visit = related_visit

@property
def source_model(self) -> str:
return self._source_model

@property
def source_model_cls(self) -> Type[CrfModelMixin]:
return django_apps.get_model(self.source_model)

@property
def source_model_obj(self) -> CrfModelMixin | None:
"""Returns the source model instance or None."""
if not self._source_model_obj:
try:
self._source_model_obj = self.source_model_cls.objects.get(
**self.source_model_options
)
except ObjectDoesNotExist:
self._source_model_obj = None
return self._source_model_obj

@property
def source_model_options(self) -> dict[str, Any]:
"""Returns a dictionary of query options to get/filter the
source_obj.
"""
return dict(subject_visit_id=self.related_visit.id)

@property
def source_model_obj_exists(self) -> bool:
"""Returns True if the source model instance exists."""
return self.source_model_cls.objects.filter(**self.source_model_options).exists()

@property
def due_datetime(self) -> datetime | None:
return self.related_visit.report_datetime

@property
def fill_datetime(self) -> datetime | None:
return getattr(self.source_model_obj, "created", None)

@property
def document_user(self) -> str | None:
return getattr(self.source_model_obj, "user_created", self.related_visit.user_created)

@property
def document_name(self) -> str | None:
return self.source_model_cls._meta.verbose_name
4 changes: 2 additions & 2 deletions edc_metadata/metadata_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .constants import CRF, KEYED
from .metadata_handler import MetadataHandler
from .source_model_metadata_mixin import SourceModelMetadataMixin
from .metadata_mixins import SourceModelMetadataMixin

if TYPE_CHECKING:
from edc_visit_tracking.model_mixins import VisitModelMixin as Base
Expand Down Expand Up @@ -48,7 +48,7 @@ def __repr__(self):

def get_and_update(self, entry_status: str = None) -> CrfMetadata | RequisitionMetadata:
metadata_obj = self.metadata_handler.metadata_obj
if entry_status != KEYED and self.is_keyed:
if entry_status != KEYED and self.source_model_obj_exists:
entry_status = KEYED
if metadata_obj.entry_status != entry_status:
metadata_obj.entry_status = entry_status
Expand Down
28 changes: 14 additions & 14 deletions edc_metadata/metadata_wrappers/metadata_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Type

from django.apps import apps as django_apps
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
Expand Down Expand Up @@ -30,7 +30,7 @@ def __init__(
visit: RelatedVisitProtocol,
metadata_obj: CrfMetadata | RequisitionMetadata,
) -> None:
self._model_obj = None
self._source_model_obj = None
self.metadata_obj = metadata_obj
self.visit = visit

Expand All @@ -49,27 +49,27 @@ def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.visit}, {self.metadata_obj})"

@property
def options(self) -> dict:
def options(self) -> dict[str, Any]:
"""Returns a dictionary of query options."""
return {f"{self.model_cls.related_visit_model_attr()}": self.visit}
return {f"{self.source_model_cls.related_visit_model_attr()}": self.visit}

@property
def model_obj(self) -> CrfModelMixin:
if not self._model_obj:
def source_model_obj(self) -> CrfModelMixin:
if not self._source_model_obj:
try:
self._model_obj = self.model_cls.objects.get(**self.options)
self._source_model_obj = self.source_model_cls.objects.get(**self.options)
except AttributeError as e:
if "related_visit_model_attr" not in str(e):
raise ImproperlyConfigured(f"{e} See {repr(self.model_cls)}")
raise ImproperlyConfigured(f"{e} See {repr(self.source_model_cls)}")
except ObjectDoesNotExist:
self._model_obj = None
return self._model_obj
self._source_model_obj = None
return self._source_model_obj

@model_obj.setter
def model_obj(self, value=None) -> None:
self._model_obj = value
@source_model_obj.setter
def source_model_obj(self, value=None) -> None:
self._source_model_obj = value

@property
def model_cls(self) -> CrfModelMixin:
def source_model_cls(self) -> Type[CrfModelMixin]:
"""Returns a CRF model class"""
return django_apps.get_model(self.metadata_obj.model)
Loading

0 comments on commit 6219da6

Please sign in to comment.