diff --git a/atlas/forms.py b/atlas/forms.py index 0bf69ada..accf5850 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -180,6 +180,60 @@ class ConditionForm(ModelForm): pass +class ConditionRelationshipForm(Form): + parents = ModelMultipleChoiceField( + required=False, + queryset=Condition.objects.none(), + widget=autocomplete.ModelSelect2Multiple(url="atlas:condition-autocomplete"), + ) + children = ModelMultipleChoiceField( + required=False, + queryset=Condition.objects.none(), + widget=autocomplete.ModelSelect2Multiple(url="atlas:condition-autocomplete"), + ) + + def __init__(self, *args, **kwargs): + self.instance = kwargs.pop("instance", None) + super().__init__(*args, **kwargs) + + condition_qs = Condition.objects.all().order_by("name") + if self.instance is not None and getattr(self.instance, "pk", None): + condition_qs = condition_qs.exclude(pk=self.instance.pk) + + self.fields["parents"].queryset = condition_qs + self.fields["children"].queryset = condition_qs + + self.helper = FormHelper() + self.helper.form_method = "post" + self.helper.form_tag = False + self.helper.layout = Layout( + Fieldset( + "Condition relationships", + "parents", + "children", + ) + ) + + def save(self, commit=True): + if self.instance is None: + raise ValueError("ConditionRelationshipForm requires an instance") + + parents = list(self.cleaned_data.get("parents", [])) + children = list(self.cleaned_data.get("children", [])) + + self.instance.parent.set(parents) + + current_children = list(self.instance.child.all()) + for child in current_children: + if child not in children: + self.instance.child.remove(child) + for child in children: + if child not in current_children: + self.instance.child.add(child) + + return self.instance + + class CaseCollectionForm(ModelForm): class Meta: model = CaseCollection @@ -211,6 +265,19 @@ class CaseCollectionForm(ModelForm): label=label, ) + if "prerequisites" in self.fields: + authored_collections = CaseCollection.objects.filter(author=self.user).distinct().order_by("name") + if getattr(self.instance, "pk", None): + authored_collections = authored_collections.exclude(pk=self.instance.pk) + self.fields["prerequisites"].queryset = authored_collections + self.fields["prerequisites"].widget = autocomplete.ModelSelect2Multiple( + url="atlas:casecollection-prerequisite-autocomplete", + attrs={ + "data-placeholder": "Search authored collections...", + "data-minimum-input-length": 0, + }, + ) + # Identify show_ fields show_fields = [f for f in self.fields if f.startswith("show_")] show_pre_fields = [f for f in show_fields if f.endswith("_pre")] @@ -630,8 +697,9 @@ class CaseForm(ModelForm): # This is populated if we create the case from an exam/collection self.collection = None - if kwargs["initial"] is not None and "exams" in kwargs["initial"]: - collections = kwargs["initial"].pop("exams") + initial = kwargs.setdefault("initial", {}) + if initial is not None and "exams" in initial: + collections = initial.pop("exams") logger.debug(collections) self.collection = get_object_or_404(CaseCollection, pk=collections[0]) diff --git a/atlas/models.py b/atlas/models.py index 7a2d46c2..c5c30958 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -138,7 +138,10 @@ def _series_declared_groups_cache_key(series_id): def invalidate_series_declared_groups_cache(series_id): if not series_id: return - cache.delete(_series_declared_groups_cache_key(series_id)) + try: + cache.delete(_series_declared_groups_cache_key(series_id)) + except Exception: + pass def _build_declared_series_groups(series, series_images_with_urls): @@ -155,7 +158,10 @@ def _build_declared_series_groups(series, series_images_with_urls): for image_obj, image_url in series_images_with_urls ], } - cached = cache.get(cache_key) + try: + cached = cache.get(cache_key) + except Exception: + cached = None if isinstance(cached, dict): if cached.get("signature") == signature: return cached.get("groups", []) @@ -257,7 +263,10 @@ def _build_declared_series_groups(series, series_images_with_urls): "groupValues": {}, } ] - cache.set(cache_key, {"signature": signature, "groups": result}, timeout=60 * 60) + try: + cache.set(cache_key, {"signature": signature, "groups": result}, timeout=60 * 60) + except Exception: + pass return result grouped = {} @@ -313,7 +322,10 @@ def _build_declared_series_groups(series, series_images_with_urls): } ) - cache.set(cache_key, {"signature": signature, "groups": ordered_groups}, timeout=60 * 60) + try: + cache.set(cache_key, {"signature": signature, "groups": ordered_groups}, timeout=60 * 60) + except Exception: + pass return ordered_groups diff --git a/atlas/tasks.py b/atlas/tasks.py index a605469f..fe4c9769 100644 --- a/atlas/tasks.py +++ b/atlas/tasks.py @@ -173,6 +173,10 @@ def series_downsample_task(context, series_id, user_id, downsample_pct): series.source_series_instance_uid = source_series_uid series.series_instance_uid = replacement_series_uid series.save(update_fields=["modified", "source_series_instance_uid", "series_instance_uid"]) + try: + series.get_total_image_size(refresh=True) + except Exception as exc: + logger.warning("Failed to refresh total_image_size for series {}: {}", series.pk, exc) cache.set( progress_key, diff --git a/atlas/templates/atlas/case_admin_overview.html b/atlas/templates/atlas/case_admin_overview.html new file mode 100644 index 00000000..1eb5cbc3 --- /dev/null +++ b/atlas/templates/atlas/case_admin_overview.html @@ -0,0 +1,112 @@ +{% extends 'atlas/base.html' %} + +{% block content %} +
| User | +Case count | +
|---|---|
|
+ {% if row.get_full_name %}{{ row.get_full_name }}{% else %}{{ row.username }}{% endif %}
+ {{ row.username }}
+ |
+ {{ row.case_count }} | +
| No case authors found. | +|
| Case | +Series | +Total size | +
|---|---|---|
|
+ {{ row.case.title }}
+ #{{ row.case.pk }}
+ |
+ {{ row.series_count }} | +{{ row.human_total }} | +
| Case | +Created | +Authors | +
|---|---|---|
|
+ {{ case.title }}
+ #{{ case.pk }}
+ |
+ {{ case.created_date|date:"Y-m-d H:i" }} | ++ {% for author in case.author.all %} + {{ author.username }} + {% empty %} + None + {% endfor %} + | +