Skip to content

Commit

Permalink
feat: download layouts for plates as .tsv
Browse files Browse the repository at this point in the history
Download ELISA and sequencing run plate layouts as .tsv files.
  • Loading branch information
alubbock committed Oct 9, 2024
1 parent 3ef9cea commit 4e8964a
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 4 deletions.
89 changes: 89 additions & 0 deletions backend/antigenapi/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,23 @@ def update(self, instance, validated_data):
return instance


def _wells_to_tsv(wells):
UPPER_CASE_A = 65
ROW_LENGTH = 12
NUM_ROWS = 8

output = "\t".join([""] + [str(i) for i in range(1, ROW_LENGTH + 1)]) + "\n"
for row in range(NUM_ROWS):
start = row * ROW_LENGTH
row = [chr(UPPER_CASE_A + row)]
row += [
str(w) if w is not None else "" for w in wells[start : (start + ROW_LENGTH)]
]
output += "\t".join(row) + "\n"

return output


class ElisaPlateViewSet(AuditLogMixin, DeleteProtectionMixin, ModelViewSet):
"""A view set displaying all recorded elisa plates."""

Expand All @@ -426,6 +443,30 @@ class ElisaPlateViewSet(AuditLogMixin, DeleteProtectionMixin, ModelViewSet):
def perform_create(self, serializer): # noqa: D102
serializer.save(added_by=self.request.user)

@action(
detail=True,
methods=["GET"],
name="Download ELISA plate as TSV.",
url_path="tsv",
)
def download_elisa_tsv(self, request, pk):
"""Download ELISA plate as .tsv file."""
wells = list(
ElisaWell.objects.filter(
plate_id=pk,
)
.order_by("location")
.values_list("optical_density", flat=True)
)

output = _wells_to_tsv(wells)

response = HttpResponse(output, content_type="text/tab-separated-values")

response["Content-Disposition"] = f'attachment; filename="elisa_plate_{pk}.tsv"'

return response


class ElisaWellInlineSerializer(ModelSerializer):
"""A serializer to represent elisa wells by plate id and location."""
Expand Down Expand Up @@ -666,6 +707,54 @@ def download_submission_xlsx(self, request, pk, submission_idx):
)
return response

@action(
detail=True,
methods=["GET"],
name="Download sequencing run submission file (xlsx).",
url_path="submissionfile/(?P<submission_idx>[0-9]+)/tsv",
)
def download_sequencing_plate_tsv(self, request, pk, submission_idx):
"""Download sequencing run plate layout as .tsv file."""
try:
sr = SequencingRun.objects.get(id=int(pk))
except SequencingRunResults.DoesNotExist:
raise Http404

wells = {
w["location"]: w for w in sr.wells if w["plate"] == int(submission_idx)
}
plate_ids = [w["elisa_well"]["plate"] for w in wells.values()]
elisa_wells = {
(ew.plate_id, ew.location): ew
for ew in ElisaWell.objects.filter(plate_id__in=plate_ids)
}

well_dat = []
for i in range(1, 97):
try:
well = wells[i]
except KeyError:
well_dat.append("")
continue

elisa_well = elisa_wells[
(well["elisa_well"]["plate"], well["elisa_well"]["location"])
]

well_dat.append(
f"{elisa_well.plate_id}:"
f"{PlateLocations.labels[elisa_well.location]} "
f"[{elisa_well.antigen}]"
)

output = _wells_to_tsv(well_dat)

response = HttpResponse(output, content_type="text/tab-separated-values")

response["Content-Disposition"] = f'attachment; filename="elisa_plate_{pk}.tsv"'

return response

@action(
detail=True,
methods=["GET", "PUT"],
Expand Down
45 changes: 41 additions & 4 deletions frontend/src/crudtemplates/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,24 @@ export const displayFieldSingle = (field, record, context, props) => {
if (field.fkDisplayField) {
return record[field.field + "_" + field.fkDisplayField];
} else if (field.type === "elisaplate" && record[field.field]) {
return plateMapOfValues(
record[field.field].map((well) => well["optical_density"]),
return (
<>
{plateMapOfValues(
record[field.field].map((well) => well["optical_density"]),
)}
<a
href={
config.url.API_URL + schema.elisa.apiUrl + "/" + record.id + "/tsv"
}
>
<button
type="button"
className="w-full sm:w-auto mr-2 mt-2 inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
>
Download plate (.tsv)
</button>
</a>
</>
);
} else if (field.type === "sequencingplate" && record[field.field]) {
let numPlates = Math.ceil(record[field.field].length / 96);
Expand Down Expand Up @@ -226,7 +242,28 @@ export const displayFieldSingle = (field, record, context, props) => {
type="button"
className="w-full sm:w-auto mb-2 mt-2 mr-2 sm:mb-0 relative inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Download sequencing submission file
Download sequencing submission file (.xlsx)
</button>
</a>,
);
retVal.push(
<a
key={"seqPlateTsv" + p}
href={
config.url.API_URL +
schema.sequencing.apiUrl +
"/" +
record.id +
"/submissionfile/" +
p +
"/tsv"
}
>
<button
type="button"
className="w-full sm:w-auto mb-2 mt-2 mr-2 sm:mb-0 relative inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Download plate layout (.tsv)
</button>
</a>,
);
Expand Down Expand Up @@ -290,7 +327,7 @@ export const displayFieldSingle = (field, record, context, props) => {
type="button"
className="w-full sm:w-auto mb-2 mt-2 mr-2 sm:mb-0 relative inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Download IMGT AIRR file
Download IMGT AIRR file (.tsv)
</button>
</a>,
);
Expand Down

0 comments on commit 4e8964a

Please sign in to comment.