Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
660d49a
OpenConceptLab/ocl_online#451 | FHIR requests and errors use native F…
paynejd Oct 22, 2026
986fced
OpenConceptLab/ocl_online#451 | Canonical lookups preserve registry e…
paynejd Oct 22, 2026
e7eaa21
OpenConceptLab/ocl_online#451 | FHIR search and read return the relea…
paynejd Oct 22, 2026
d007d55
OpenConceptLab/ocl_online#451 | Missing FHIR operation inputs get con…
paynejd Oct 22, 2026
ca20fa5
OpenConceptLab/ocl_online#451 | FHIR lookup and validation answer for…
paynejd Oct 22, 2026
8cddccf
OpenConceptLab/ocl_online#451 | FHIR translation returns the requeste…
paynejd Oct 22, 2026
ea7a43a
OpenConceptLab/ocl_online#451 | FHIR expansion pages the requested va…
paynejd Oct 22, 2026
7b7cadd
OpenConceptLab/ocl_online#451 | FHIR metadata describes the operation…
paynejd Oct 22, 2026
357e496
OpenConceptLab/ocl_online#451 | ConceptMap system searches use indexe…
paynejd Oct 22, 2026
33058bd
OpenConceptLab/ocl_online#451 | CodeSystem and ValueSet titles fall b…
paynejd Oct 22, 2026
0345c0d
OpenConceptLab/ocl_online#451 | A named FHIR version resolves to a vi…
paynejd Oct 22, 2026
4afaffb
OpenConceptLab/ocl_online#451 | Value set members stored as root conc…
paynejd Oct 22, 2026
7c2118c
OpenConceptLab/ocl_online#451 | FHIR operations reject parameters wit…
paynejd Oct 22, 2026
439a85c
OpenConceptLab/ocl_online#451 | Root concept members report the newes…
paynejd Oct 22, 2026
211da06
OpenConceptLab/ocl_online#451 | Filtered expansions match members onc…
paynejd Oct 22, 2026
77bd094
OpenConceptLab/ocl_online#451 | Instance operations serve named unrel…
paynejd Oct 22, 2026
ebe370c
OpenConceptLab/ocl_online#451 | Only /fhir/ and owner bases are FHIR …
paynejd Oct 22, 2026
f88a62b
OpenConceptLab/ocl_online#451 | The FHIR host serves its own Swagger …
paynejd Oct 23, 2026
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
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ FLOWER_PASSWORD=Root123
# -----------------------------------------------------------------------------
# FHIR Validator
# -----------------------------------------------------------------------------
FHIR_VALIDATOR_URL=http://fhir_validator:3500

# -----------------------------------------------------------------------------
# Error tracking (optional)
Expand Down
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[MESSAGES CONTROL]
disable=missing-docstring,too-few-public-methods,no-member,too-many-ancestors,attribute-defined-outside-init,fixme,broad-except,import-outside-toplevel,too-many-instance-attributes,unsupported-membership-test,too-many-public-methods,too-many-lines,unused-private-member,consider-using-with,duplicate-code,unspecified-encoding,invalid-name,cyclic-import,missing-timeout,too-many-positional-arguments
[MASTER]
extension-pkg-allow-list=lxml
ignore=migrations,fixtures,scripts,commands,documents.py,password_validation.py,v1_dump
[FORMAT]
max-line-length=120
Expand Down
22 changes: 18 additions & 4 deletions core/bundles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class BundleLinkSerializer(CommonSerializer):


class BundleMetaSerializer(CommonSerializer):
lastUpdated = DateTimeField(read_only=True, default=datetime.now())
lastUpdated = DateTimeField(read_only=True, default=datetime.now)


class BundleExtensionSerializer(CommonSerializer):
Expand All @@ -33,7 +33,9 @@ class BundleExtensionSerializer(CommonSerializer):


class BundleEntrySerializer(CommonSerializer):
fullUrl = CharField(required=False)
resource = JSONField()
search = JSONField(required=False)


class FHIRBundleSerializer(CommonSerializer):
Expand All @@ -53,17 +55,29 @@ def get_resource_type(cls, _obj):
return 'Bundle'

@classmethod
def convert_to_entry(cls, resources):
def convert_to_entry(cls, resources, base_url):
"""
Searchset entries: each resource a match with its fullUrl (base_url is the base and resource type), and a
resource that couldn't be represented (an OperationOutcome) an outcome.
"""
entry = []
for resource in resources:
entry.append({'resource': resource})
if resource.get('resourceType') == 'OperationOutcome':
entry.append({'resource': resource, 'search': {'mode': 'outcome'}})
else:
entry.append(
{'fullUrl': f"{base_url}/{resource['id']}", 'resource': resource, 'search': {'mode': 'match'}})
return entry

def get_link(self, _):
"""self, previous and next, for the page the paginator holds; only self without one (_count=0)"""
paginator = self.context.get('paginator')
if paginator is None:
return BundleLinkSerializer(
[{'relation': 'self', 'url': self.context['request'].build_absolute_uri()}], many=True).data
links = [{'relation': 'self', 'url': paginator.get_current_page_url()}]
if paginator.has_previous():
links.append({'relation': 'prev', 'url': paginator.get_previous_page_url()})
links.append({'relation': 'previous', 'url': paginator.get_previous_page_url()})
if paginator.has_next():
links.append({'relation': 'next', 'url': paginator.get_next_page_url()})
return BundleLinkSerializer(links, many=True).data
Expand Down
4 changes: 2 additions & 2 deletions core/code_systems/converter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from fhir.resources.codesystem import CodeSystem
from fhir.resources.R4B.codesystem import CodeSystem # R4B models are the R4 wire models

from core.settings import DEFAULT_LOCALE
from core.sources.models import Source
Expand Down Expand Up @@ -32,7 +32,7 @@ def to_fhir(source):
'language': source.default_locale,
'count': source.active_concepts,
}
return CodeSystem(**code_system).dict()
return CodeSystem(**code_system).model_dump()

@staticmethod
def from_fhir(code_system):
Expand Down
61 changes: 24 additions & 37 deletions core/code_systems/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,14 @@
from core.common.serializers import ReadSerializerMixin, StatusField, IdentifierSerializer
from core.concepts.models import Concept, ConceptName
from core.concepts.serializers import ConceptDetailSerializer
from core.parameters.serializers import ParametersSerializer
from core.fhir import search as fhir_search
from core.fhir.search import CanonicalUrlField, FhirRepositorySerializerMixin, TitleField
from core.sources.models import Source
from core.sources.serializers import SourceCreateOrUpdateSerializer

logger = logging.getLogger('oclapi')


class ValidateCodeParametersSerializer(ParametersSerializer):
allowed_input_parameters = {
'url': 'valueUri',
'code': 'valueCode',
'display': 'valueString',
'version': 'valueString',
'systemVersion': 'valueString',
'system': 'valueUri'
}

class CodeSystemConceptDesignationUseSerializer(serializers.Field):
def to_internal_value(self, data):
if 'code' in data:
Expand Down Expand Up @@ -89,10 +80,10 @@ def to_internal_value(self, data):
return {'descriptions': [{'description': data, 'locale': settings.DEFAULT_LOCALE, 'locale_preferred': True}]}

def to_representation(self, value):
descriptions = value.descriptions_for_default_locale
if descriptions and len(descriptions) > 0:
return value.descriptions_for_default_locale[0]
return None
"""The first description in the default locale, from the descriptions a read prefetches."""
descriptions = [description.name for description in value.descriptions.all()
if description.locale == settings.DEFAULT_LOCALE and not description.retired]
return descriptions[0] if descriptions else None


class CodeSystemConceptSerializer(ReadSerializerMixin, serializers.Serializer):
Expand Down Expand Up @@ -149,13 +140,9 @@ def to_internal_value(self, data):
return {'concepts': concepts.validated_data}

def to_representation(self, value):
# limit to 1000 concepts by default
# TODO: support graphQL to go around the limit
if self.context.get('has_many', False):
limit = 25
else:
limit = 1000
return CodeSystemConceptSerializer(value.concepts.order_by('id')[:limit], many=True).data
"""Every concept, retired ones included, up to the limit; none above it (content not-present)."""
concepts = fhir_search.code_system_concepts(value)[1]
return CodeSystemConceptSerializer(concepts, many=True).data if concepts else []


class TextField(ReadSerializerMixin, serializers.Serializer):
Expand All @@ -178,11 +165,12 @@ def to_representation(self, instance):
return super().to_representation(obj)


class CodeSystemDetailSerializer(serializers.ModelSerializer):
class CodeSystemDetailSerializer(FhirRepositorySerializerMixin, serializers.ModelSerializer):
content_field = 'concept'
resourceType = SerializerMethodField(method_name='get_resource_type')
id = CharField(source='mnemonic')
url = CharField(source='canonical_url', required=False)
title = CharField(source='full_name', required=False)
url = CanonicalUrlField(source='canonical_url', required=False)
title = TitleField(source='full_name', required=False)
status = StatusField(source='*')
language = CharField(source='default_locale', required=False)
count = SerializerMethodField(method_name='get_total_count')
Expand Down Expand Up @@ -210,21 +198,20 @@ class Meta:
'copyright', 'revisionDate', 'experimental', 'caseSensitive', 'compositional', 'versionNeeded',
'collectionReference', 'hierarchyMeaning', 'concept', 'text', 'filters')

def __new__(cls, *args, **kwargs):
if kwargs.get('many', False):
context = kwargs.get('context', {})
context.update({'has_many': True})
kwargs.update({'context': context})

return super().__new__(cls, *args, **kwargs)

@staticmethod
def get_resource_type(_):
return RESOURCE_TYPE

def get_total_count(self, obj):
"""A read's concept count; a search's summary has none (it only checks the limit)."""
return None if self.context.get('has_many') else fhir_search.code_system_concepts(obj)[0]

@staticmethod
def get_total_count(obj):
return obj.concepts.count()
def get_content(obj):
"""complete (or the content mode the CodeSystem was saved with) up to the concept limit, else not-present"""
if fhir_search.concepts_over_limit(obj):
return 'not-present'
return obj.content_type if obj.content_type in ('example', 'fragment', 'supplement') else 'complete'

@staticmethod
def get_property(obj):
Expand Down Expand Up @@ -264,11 +251,11 @@ def get_meta(obj):

def to_representation(self, instance):
try:
if not instance.content_type:
instance.content_type = 'complete'
rep = super().to_representation(instance)
rep['content'] = self.get_content(instance)
delete_empty_fields(rep)
IdentifierSerializer.include_ocl_identifier(instance.uri, RESOURCE_TYPE, rep)
self.with_fhir_view(instance, rep)
except (Exception, ):
msg = f'Failed to represent "{instance.uri}" as {RESOURCE_TYPE}'
logger.exception(msg)
Expand Down
Loading
Loading