From e19cf59a506819eddfd635f1d2c1e74176b62a3f Mon Sep 17 00:00:00 2001 From: Kalev Takkis Date: Wed, 26 Feb 2025 14:06:57 +0000 Subject: [PATCH] feat: usernames and full names in quality status API response Also, removed randomly generated quality states, on load everything gets NULL --- viewer/managers.py | 23 +++++++++++ viewer/migrations/0101_auto_20250226_1139.py | 32 +++++++++++++++ viewer/migrations/0102_auto_20250226_1146.py | 41 +++++++++++++++++++ ...nementstatustype_default_quality_status.py | 17 ++++++++ viewer/models.py | 39 ++++++++++++++++-- viewer/serializers.py | 7 ++++ viewer/target_loader.py | 22 ++++------ viewer/views.py | 2 +- 8 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 viewer/migrations/0101_auto_20250226_1139.py create mode 100644 viewer/migrations/0102_auto_20250226_1146.py create mode 100644 viewer/migrations/0103_remove_refinementstatustype_default_quality_status.py diff --git a/viewer/managers.py b/viewer/managers.py index 3bdc6273..b80dfbe5 100644 --- a/viewer/managers.py +++ b/viewer/managers.py @@ -465,3 +465,26 @@ def annotated_qs(self): 'upload_version', ) ) + + +class SiteObservationQualityStatusQueryset(QuerySet): + def annotated_qs(self): + SiteObservationQualityStatus = apps.get_model( + "viewer", + "SiteObservationQualityStatus", + ) + qs = SiteObservationQualityStatus.objects.annotate( + username=F("user__username"), + first_name=F("user__first_name"), + last_name=F("user__last_name"), + ) + + return qs + + +class SiteObservationQualityStatusDataManager(Manager): + def get_queryset(self): + return SiteObservationQualityStatusQueryset(self.model, using=self._db) + + def annotated_qs(self): + return self.get_queryset().annotated_qs() diff --git a/viewer/migrations/0101_auto_20250226_1139.py b/viewer/migrations/0101_auto_20250226_1139.py new file mode 100644 index 00000000..d0dafd93 --- /dev/null +++ b/viewer/migrations/0101_auto_20250226_1139.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.25 on 2025-02-26 11:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('viewer', '0100_auto_20250213_1039'), + ] + + operations = [ + migrations.CreateModel( + name='RefinementStatusType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.IntegerField(blank=True)), + ('description', models.TextField(blank=True)), + ('default_quality_status', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='viewer.qualitystatustype')), + ], + ), + migrations.AddField( + model_name='experiment', + name='refinement_outcome', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='viewer.refinementstatustype'), + ), + migrations.AddConstraint( + model_name='refinementstatustype', + constraint=models.UniqueConstraint(fields=('code',), name='unique_refinement_code'), + ), + ] diff --git a/viewer/migrations/0102_auto_20250226_1146.py b/viewer/migrations/0102_auto_20250226_1146.py new file mode 100644 index 00000000..247a64d2 --- /dev/null +++ b/viewer/migrations/0102_auto_20250226_1146.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.25 on 2025-02-26 11:46 + +from django.db import migrations + + + +refinement_status = ( + (0, "All Datasets"), + (1, "Analysis Pending"), + (2, "PANDDA model"), + (3, "In Refinement"), + (4, "CompChem Ready"), + (5, "Deposition Ready"), + (6, "Deposited"), + (7, "Analysed & Rejected"), +) + +class Migration(migrations.Migration): + + dependencies = [ + ('viewer', '0101_auto_20250226_1139'), + ] + + def populate_refinements(apps, schema_editor): + RefinementStatusType = apps.get_model("viewer", "RefinementStatusType") + for code, description in refinement_status: + RefinementStatusType( + code=code, + description=description, + ) + + + + def depopulate_refinements(apps, schema_editor): + RefinementStatusType = apps.get_model("viewer", "RefinementStatusType") + RefinementStatusType.objects.all().delete() + + + operations = [ + migrations.RunPython(populate_refinements, depopulate_refinements) + ] diff --git a/viewer/migrations/0103_remove_refinementstatustype_default_quality_status.py b/viewer/migrations/0103_remove_refinementstatustype_default_quality_status.py new file mode 100644 index 00000000..9eda2934 --- /dev/null +++ b/viewer/migrations/0103_remove_refinementstatustype_default_quality_status.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.25 on 2025-02-26 12:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('viewer', '0102_auto_20250226_1146'), + ] + + operations = [ + migrations.RemoveField( + model_name='refinementstatustype', + name='default_quality_status', + ), + ] diff --git a/viewer/models.py b/viewer/models.py index 64212909..14549499 100644 --- a/viewer/models.py +++ b/viewer/models.py @@ -26,6 +26,7 @@ QuatAssemblyDataManager, SessionActionsDataManager, SiteObservationDataManager, + SiteObservationQualityStatusDataManager, SnapshotActionsDataManager, SnapshotDataManager, XtalformDataManager, @@ -227,6 +228,32 @@ def get_download_path(self): ) +class QualityStatusType(models.Model): + status = models.TextField(primary_key=True) + + +class RefinementStatusType(models.Model): + code = models.IntegerField(blank=True) + description = models.TextField(blank=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=[ + "code", + ], + name="unique_refinement_code", + ), + ] + + def __repr__(self) -> str: + return "" % ( + self.id, + self.code, + self.description, + ) + + class Experiment(models.Model): experiment_upload = models.ForeignKey(ExperimentUpload, on_delete=models.CASCADE) code = models.TextField(null=True) @@ -256,6 +283,11 @@ class Experiment(models.Model): ) # need to set null=True due to the data saving order xtalform = models.ForeignKey("Xtalform", null=True, on_delete=models.CASCADE) + refinement_outcome = models.ForeignKey( + RefinementStatusType, + on_delete=models.SET_NULL, + null=True, + ) objects = models.Manager() filter_manager = ExperimentDataManager() @@ -594,10 +626,6 @@ def get_ligand_mol_file(self): return contents -class QualityStatusType(models.Model): - status = models.TextField(primary_key=True) - - class SiteObservationQualityStatus(models.Model): site_observation = models.ForeignKey(SiteObservation, on_delete=models.CASCADE) status = models.ForeignKey(QualityStatusType, on_delete=models.CASCADE) @@ -607,6 +635,9 @@ class SiteObservationQualityStatus(models.Model): main_status = models.BooleanField(default=False) comment = models.TextField() + objects = models.Manager() + filter_manager = SiteObservationQualityStatusDataManager() + class CompoundIdentifierType(models.Model): name = models.TextField(primary_key=True) diff --git a/viewer/serializers.py b/viewer/serializers.py index 4557bcbc..4af1b2a9 100644 --- a/viewer/serializers.py +++ b/viewer/serializers.py @@ -1236,6 +1236,10 @@ class MetadataUploadSerializer(serializers.Serializer): class SiteObservationQualityStatusSerializer(serializers.ModelSerializer): + username = serializers.CharField(read_only=True) + first_name = serializers.CharField(read_only=True) + last_name = serializers.CharField(read_only=True) + class Meta: model = models.SiteObservationQualityStatus fields = '__all__' @@ -1243,6 +1247,9 @@ class Meta: "auto_assigned": {"read_only": True}, "timestamp": {"read_only": True}, "user": {"read_only": True}, + "username": {"read_only": True}, + "first_name": {"read_only": True}, + "last_name": {"read_only": True}, } def create(self, validated_data): diff --git a/viewer/target_loader.py b/viewer/target_loader.py index 27ebadc4..b0f71f38 100644 --- a/viewer/target_loader.py +++ b/viewer/target_loader.py @@ -4,7 +4,8 @@ import logging import math import os -import random + +# import random import shutil import tarfile from collections.abc import Callable @@ -1533,9 +1534,9 @@ def process_site_observation( "smiles": smiles, } - index_data = { - "auto_build_score": random.random(), - } + # index_data = { + # "auto_build_score": random.random(), + # } return ProcessedObject( model_class=SiteObservation, @@ -1543,7 +1544,7 @@ def process_site_observation( defaults=defaults, key=key, versioned_key=v_key, - index_data=index_data, + index_data={}, ) def process_bundle(self): @@ -2238,7 +2239,7 @@ def process_bundle(self): if val.new: self._assign_observation_quality_status( val.instance, - val.index_data["auto_build_score"], + # val.index_data["auto_build_score"], ) def import_compound_identifiers(self, alias_file_path): @@ -2608,13 +2609,8 @@ def _get_final_path(self, path: str | None) -> Path | None: # received invalid path return None - def _assign_observation_quality_status(self, site_observation, score) -> None: - if score > 0.7: - status = QualityStatusType.objects.get(status="GOOD") - elif score <= 0.7 and score > 0.4: - status = QualityStatusType.objects.get(status="MEDIOCRE") - else: - status = QualityStatusType.objects.get(status="BAD") + def _assign_observation_quality_status(self, site_observation) -> None: + status = QualityStatusType.objects.get(status="NONE") SiteObservationQualityStatus( site_observation=site_observation, diff --git a/viewer/views.py b/viewer/views.py index 2f825b2c..90a6de76 100644 --- a/viewer/views.py +++ b/viewer/views.py @@ -2839,7 +2839,7 @@ class SiteObservationQualityStatusView( mixins.UpdateModelMixin, ISPyBSafeQuerySet, ): - queryset = models.SiteObservationQualityStatus.objects.all() + queryset = models.SiteObservationQualityStatus.filter_manager.annotated_qs() serializer_class = serializers.SiteObservationQualityStatusSerializer filterset_class = filters.SiteObservationQualityStatusFilter filter_permissions = "site_observation__experiment__experiment_upload__project"