diff --git a/atlas/models.py b/atlas/models.py index 38e81b25..92a2cb17 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -10,6 +10,8 @@ from rad.settings import REMOTE_URL from django.db.models.fields.files import ImageField from django.db.models.fields.related import ForeignKey from django.db import models +from django.db.models.signals import pre_save, post_save, post_delete +from django.dispatch import receiver from django.shortcuts import get_object_or_404 from django.utils import timezone from django.core.cache import cache @@ -129,11 +131,34 @@ def _normalise_group_rule_value(value): return text +def _series_declared_groups_cache_key(series_id): + return f"atlas:series_declared_groups:{series_id}" + + +def invalidate_series_declared_groups_cache(series_id): + if not series_id: + return + cache.delete(_series_declared_groups_cache_key(series_id)) + + def _build_declared_series_groups(series, series_images_with_urls): - cache_key = f"atlas:series_declared_groups:{series.pk}:{int(series.modified_date.timestamp())}:{len(series_images_with_urls)}" + cache_key = _series_declared_groups_cache_key(series.pk) + signature = { + "series_modified": int(series.modified_date.timestamp()) if getattr(series, "modified_date", None) else 0, + "image_count": len(series_images_with_urls), + "images": [ + { + "pk": getattr(image_obj, "pk", None), + "position": getattr(image_obj, "position", None), + "url": image_url, + } + for image_obj, image_url in series_images_with_urls + ], + } cached = cache.get(cache_key) - if cached is not None: - return cached + if isinstance(cached, dict): + if cached.get("signature") == signature: + return cached.get("groups", []) rules = [ "dwiBValue", @@ -232,7 +257,7 @@ def _build_declared_series_groups(series, series_images_with_urls): "groupValues": {}, } ] - cache.set(cache_key, result, timeout=60 * 60) + cache.set(cache_key, {"signature": signature, "groups": result}, timeout=60 * 60) return result grouped = {} @@ -288,7 +313,7 @@ def _build_declared_series_groups(series, series_images_with_urls): } ) - cache.set(cache_key, ordered_groups, timeout=60 * 60) + cache.set(cache_key, {"signature": signature, "groups": ordered_groups}, timeout=60 * 60) return ordered_groups @@ -1274,6 +1299,42 @@ class SeriesImage(SeriesImageBase): # }} +@receiver(pre_save, sender=SeriesImage) +def _capture_previous_series_for_series_image(sender, instance, **kwargs): + if not getattr(instance, "pk", None): + instance._previous_series_id_for_cache = None + return + previous_series_id = ( + sender.objects.filter(pk=instance.pk).values_list("series_id", flat=True).first() + ) + instance._previous_series_id_for_cache = previous_series_id + + +def _invalidate_and_touch_series(series_id): + if not series_id: + return + invalidate_series_declared_groups_cache(series_id) + Series.objects.filter(pk=series_id).update(modified_date=timezone.now()) + + +@receiver(post_save, sender=SeriesImage) +def _invalidate_declared_groups_on_series_image_save(sender, instance, **kwargs): + affected_series_ids = set() + if instance.series_id: + affected_series_ids.add(instance.series_id) + previous_series_id = getattr(instance, "_previous_series_id_for_cache", None) + if previous_series_id: + affected_series_ids.add(previous_series_id) + + for series_id in affected_series_ids: + _invalidate_and_touch_series(series_id) + + +@receiver(post_delete, sender=SeriesImage) +def _invalidate_declared_groups_on_series_image_delete(sender, instance, **kwargs): + _invalidate_and_touch_series(getattr(instance, "series_id", None)) + + class SeriesFinding(FindingBase): series = models.ForeignKey( "Series", related_name="findings", on_delete=models.SET_NULL, null=True diff --git a/atlas/templates/atlas/series_viewer.html b/atlas/templates/atlas/series_viewer.html index b7c340cd..e2c6aad9 100755 --- a/atlas/templates/atlas/series_viewer.html +++ b/atlas/templates/atlas/series_viewer.html @@ -32,10 +32,10 @@
No detected grouping values for this series.
+ {% endif %} +