Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update AssessmentValue global API #955

Merged
merged 14 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
5 changes: 4 additions & 1 deletion hawc/apps/animal/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
DoseUnitsViewSet,
)
from ..assessment.constants import AssessmentViewSetPermissions
from ..common.api.filters import filtered_qs
from ..common.helper import FlatExport, cacheable
from ..common.renderers import PandasRenderers
from ..common.serializers import HeatmapQuerySerializer, UnusedSerializer
from ..common.views import create_object_log
from . import exports, models, serializers
from .actions.model_metadata import AnimalMetadata
from .actions.term_check import term_check
from .filterset import EndpointFilterSet


class AnimalAssessmentViewSet(BaseAssessmentViewSet):
Expand All @@ -43,8 +45,9 @@ def full_export(self, request, pk):
Retrieve complete animal data
"""
self.assessment = self.get_object()
qs = self.get_endpoint_queryset()
exporter = exports.EndpointGroupFlatComplete(
self.get_endpoint_queryset(),
filtered_qs(qs, EndpointFilterSet, request, assessment=self.assessment),
filename=f"{self.assessment}-bioassay-complete",
assessment=self.assessment,
)
Expand Down
111 changes: 105 additions & 6 deletions hawc/apps/assessment/exports.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,108 @@
import pandas as pd
from ..common.exports import Exporter, ModelExport
from ..common.models import sql_display
from ..study.exports import StudyExport
from . import constants

from ..common.helper import FlatFileExporter
from .models import AssessmentValue

class AssessmentValueExport(ModelExport):
def get_value_map(self) -> dict:
return {
"evaluation_type": "evaluation_type_display",
"system": "system",
"value_type": "value_type_display",
"value": "value",
"value_unit": "value_unit",
"adaf": "adaf",
"confidence": "confidence",
"duration": "duration",
"basis": "basis",
"pod_type": "pod_type",
"pod_value": "pod_value",
"pod_unit": "pod_unit",
"uncertainty": "uncertainty_display",
"species_studied": "species_studied",
"evidence": "evidence",
"tumor_type": "tumor_type",
"extrapolation_method": "extrapolation_method",
"comments": "comments",
"extra": "extra",
"created": "created",
"last_updated": "last_updated",
}

class ValuesListExport(FlatFileExporter):
def build_df(self) -> pd.DataFrame:
return AssessmentValue.objects.get_df()
def get_annotation_map(self, query_prefix: str) -> dict:
return {
"evaluation_type_display": sql_display(
query_prefix + "evaluation_type", constants.EvaluationType
),
"value_type_display": sql_display(query_prefix + "value_type", constants.ValueType),
"uncertainty_display": sql_display(
query_prefix + "uncertainty", constants.UncertaintyChoices
),
}

def prepare_df(self, df):
return self.format_time(df)


class AssessmentExport(ModelExport):
def get_value_map(self) -> dict:
return {
"id": "id",
"name": "name",
"cas": "cas",
"year": "year",
"created": "created",
"last_updated": "last_updated",
}

def prepare_df(self, df):
return self.format_time(df)


class AssessmentDetailExport(ModelExport):
def get_value_map(self) -> dict:
return {
"project_type": "project_type",
"project_status": "project_status_display",
"project_url": "project_url",
"peer_review_status": "peer_review_status_display",
"qa_id": "qa_id",
"qa_url": "qa_url",
"report_id": "report_id",
"report_url": "report_url",
"extra": "extra",
"created": "created",
"last_updated": "last_updated",
}

def prepare_df(self, df):
return self.format_time(df)

def get_annotation_map(self, query_prefix: str) -> dict:
return {
"project_status_display": sql_display(
query_prefix + "project_status", constants.Status
),
"peer_review_status_display": sql_display(
query_prefix + "peer_review_status", constants.PeerReviewType
),
}


class AssessmentExporter(Exporter):
def build_modules(self) -> list[ModelExport]:
return [
AssessmentExport("assessment", "assessment"),
AssessmentDetailExport("assessment_detail", "assessment__details"),
AssessmentValueExport("assessment_value", ""),
dannypeterson marked this conversation as resolved.
Show resolved Hide resolved
StudyExport(
"study",
"study",
include=(
"id",
"short_citation",
"published",
),
),
]
37 changes: 37 additions & 0 deletions hawc/apps/assessment/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
BaseFilterSet,
ExpandableFilterForm,
InlineFilterForm,
OrderingFilter,
PaginationFilter,
)
from ..common.helper import new_window_a
from ..myuser.models import HAWCUser
Expand Down Expand Up @@ -165,3 +167,38 @@ def create_form(self):

class EffectTagFilterSet(df.FilterSet):
name = df.CharFilter(lookup_expr="icontains")


class AssessmentValueFilterSet(df.FilterSet):
name = df.CharFilter(
field_name="assessment__name",
lookup_expr="icontains",
label="Assessment name",
)
cas = df.CharFilter(
field_name="assessment__cas",
label="Assessment CAS",
)
project_type = df.CharFilter(
field_name="assessment__details__project_type",
lookup_expr="icontains",
label="Assessment project type",
)
year = df.CharFilter(field_name="assessment__year", label="Assessment year")

order_by = OrderingFilter(
fields=(
(
"assessment__name",
"name",
),
("assessment__id", "assessment_id"),
),
initial="name",
)

paginate_by = PaginationFilter()

class Meta:
model = models.AssessmentValue
fields = ["value_type"]
25 changes: 25 additions & 0 deletions hawc/apps/common/api/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from django.db.models import QuerySet
from django_filters.filterset import FilterSet
from django_filters.utils import translate_validation
from rest_framework import exceptions, filters
from rest_framework.request import Request

from ..helper import try_parse_list_ints

Expand Down Expand Up @@ -30,3 +34,24 @@ def filter_queryset(self, request, queryset, view):
raise exceptions.PermissionDenied()

return queryset


def filtered_qs(
queryset: QuerySet, filterset_cls: type[FilterSet], request: Request, **kw
) -> QuerySet:
"""
Filter a queryset based on a FilterSet and a DRF request.

This is a utility method to add filtering to custom ViewSet actions, it is adapted from the
`django_filters.rest_framework.FilterSet` used for standard ViewSet operations.

Args:
queryset (QuerySet): the queryset to filter
filterset_cls (type[filters.FilterSet]): a FilterSet class
request (Request): a rest framework request
**kw: any additional arguments provided to FilterSet class constructor
"""
filterset = filterset_cls(data=request.query_params, queryset=queryset, request=request, **kw)
if not filterset.is_valid():
raise translate_validation(filterset.errors)
return filterset.qs
29 changes: 17 additions & 12 deletions hawc/apps/common/exports.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pandas as pd
from django.db.models import QuerySet
from django.utils import timezone

from .helper import FlatExport

Expand All @@ -14,18 +15,10 @@ def __init__(
include: tuple[str, ...] | None = None,
exclude: tuple[str, ...] | None = None,
):
"""Instantiate an exporter instance for a given django model.

Args:
key_prefix (str, optional): The model name to prepend to data frame columns.
query_prefix (str, optional): The model prefix in the ORM.
include (tuple | None, optional): If included, only these items are added.
exclude (tuple | None, optional): If specified, items are removed from base.
"""
self.key_prefix = key_prefix + "-" if key_prefix else key_prefix
self.query_prefix = query_prefix + "__" if query_prefix else query_prefix
self.include = (key_prefix + field for field in include) if include else tuple()
self.exclude = (key_prefix + field for field in exclude) if exclude else tuple()
self.include = tuple(self.key_prefix + field for field in include) if include else tuple()
self.exclude = tuple(self.key_prefix + field for field in exclude) if exclude else tuple()

@property
def value_map(self) -> dict:
Expand Down Expand Up @@ -153,6 +146,15 @@ def prepare_df(self, df: pd.DataFrame) -> pd.DataFrame:
"""
return df

def format_time(self, df: pd.DataFrame) -> pd.DataFrame:
if df.shape[0] == 0:
return df
tz = timezone.get_default_timezone()
for key in [self.get_column_name("created"), self.get_column_name("last_updated")]:
if key in df.columns:
df[key] = df[key].dt.tz_convert(tz).dt.strftime("%Y-%m-%dT%H:%M:%S.%f%z")
dannypeterson marked this conversation as resolved.
Show resolved Hide resolved
return df

def get_df(self, qs: QuerySet) -> pd.DataFrame:
"""Get dataframe export from queryset.

Expand Down Expand Up @@ -208,13 +210,16 @@ def get_df(self, qs: QuerySet) -> pd.DataFrame:
df = module.prepare_df(df)
return df

@classmethod
def build_metadata(cls, df: pd.DataFrame) -> pd.DataFrame | None:
return None

@classmethod
def flat_export(cls, qs: QuerySet, filename: str) -> FlatExport:
"""Return an instance of a FlatExport.

Args:
qs (QuerySet): the initial QuerySet
filename (str): the filename for the export
"""
df = cls().get_df(qs)
return FlatExport(df=df, filename=filename)
return FlatExport(df=df, filename=filename, metadata=cls.build_metadata(df))
Loading
Loading