mirror of
https://github.com/leaders-of-digital-9-task/backend.git
synced 2024-11-22 01:16:33 +03:00
added archive and list of files upload
This commit is contained in:
parent
e3aa26a7df
commit
a40bc6ad5b
|
@ -5,6 +5,7 @@ from dicom.api.views import (
|
||||||
RetrieveUpdateDeleteCircleApi,
|
RetrieveUpdateDeleteCircleApi,
|
||||||
RetrieveUpdateDeleteDicomApi,
|
RetrieveUpdateDeleteDicomApi,
|
||||||
RetrieveUpdateDeletePolygonApi,
|
RetrieveUpdateDeletePolygonApi,
|
||||||
|
SmartFileUploadApi,
|
||||||
)
|
)
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
||||||
|
@ -26,6 +27,7 @@ urlpatterns = [
|
||||||
include(
|
include(
|
||||||
[
|
[
|
||||||
path("", ListCreateDicomApi.as_view(), name="list_create_dicom"),
|
path("", ListCreateDicomApi.as_view(), name="list_create_dicom"),
|
||||||
|
path("upload", SmartFileUploadApi.as_view(), name="upload_dicom_api"),
|
||||||
path(
|
path(
|
||||||
"<str:slug>",
|
"<str:slug>",
|
||||||
RetrieveUpdateDeleteDicomApi.as_view(),
|
RetrieveUpdateDeleteDicomApi.as_view(),
|
||||||
|
|
|
@ -142,6 +142,10 @@ MIDDLEWARE = [
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
FILE_UPLOAD_HANDLERS = [
|
||||||
|
"django.core.files.uploadhandler.TemporaryFileUploadHandler",
|
||||||
|
]
|
||||||
|
|
||||||
# STATIC
|
# STATIC
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
||||||
|
|
|
@ -112,3 +112,7 @@ class CircleSerializer(serializers.ModelSerializer):
|
||||||
obj.radius = validated_data["radius"]
|
obj.radius = validated_data["radius"]
|
||||||
obj.save()
|
obj.save()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class SmartFileUploadSerializer(serializers.ModelSerializer):
|
||||||
|
file = serializers.FileField()
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
from rest_framework import generics
|
from rest_framework import generics, status
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.parsers import FormParser, MultiPartParser
|
from rest_framework.parsers import FormParser, MultiPartParser
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from ..models import Circle, Dicom, Polygon
|
from ..models import Circle, Dicom, Polygon
|
||||||
|
from ..services import process_files
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
CircleSerializer,
|
CircleSerializer,
|
||||||
DicomSerializer,
|
DicomSerializer,
|
||||||
|
@ -19,18 +23,6 @@ class ListCreateDicomApi(generics.ListCreateAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Dicom.objects.filter(user=self.request.user)
|
return Dicom.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
operation_id="upload_file",
|
|
||||||
request={
|
|
||||||
"multipart/form-data": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {"file": {"type": "string", "format": "binary"}},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
return self.create(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDeleteDicomApi(generics.RetrieveUpdateDestroyAPIView):
|
class RetrieveUpdateDeleteDicomApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -80,3 +72,16 @@ class RetrieveUpdateDeleteCircleApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
@extend_schema(description="Note: coordinated are dropped on update")
|
@extend_schema(description="Note: coordinated are dropped on update")
|
||||||
def put(self, request, *args, **kwargs):
|
def put(self, request, *args, **kwargs):
|
||||||
return self.update(request, *args, **kwargs)
|
return self.update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SmartFileUploadApi(APIView):
|
||||||
|
parser_classes = [MultiPartParser, FormParser]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
if "file" not in request.data:
|
||||||
|
raise ValidationError("no files")
|
||||||
|
d_list = process_files(request.FILES.getlist("file"), request.user)
|
||||||
|
return Response(
|
||||||
|
DicomSerializer(d_list.files.all(), many=True).data,
|
||||||
|
status=status.HTTP_201_CREATED,
|
||||||
|
)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
from .base import Dicom
|
from .base import Dicom, ListOfDicom
|
||||||
from .blocks import BaseShape, Circle, Coordinate, Polygon
|
from .blocks import BaseShape, Circle, Coordinate, Polygon
|
||||||
|
|
|
@ -6,6 +6,10 @@ from utils.files import media_upload_path
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class ListOfDicom(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Dicom(models.Model):
|
class Dicom(models.Model):
|
||||||
class PathologyType(models.IntegerChoices):
|
class PathologyType(models.IntegerChoices):
|
||||||
no_pathology = 0, "Без патологий"
|
no_pathology = 0, "Без патологий"
|
||||||
|
@ -17,7 +21,10 @@ class Dicom(models.Model):
|
||||||
file = models.FileField(upload_to=media_upload_path)
|
file = models.FileField(upload_to=media_upload_path)
|
||||||
uploaded = models.DateTimeField(auto_now_add=True)
|
uploaded = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
pathology_type = models.IntegerField(choices=PathologyType.choices)
|
pathology_type = models.IntegerField(choices=PathologyType.choices, default=0)
|
||||||
|
list = models.ForeignKey(
|
||||||
|
ListOfDicom, related_name="files", null=True, on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.file.name
|
return self.file.name
|
||||||
|
|
|
@ -19,8 +19,8 @@ class BaseShape(PolymorphicModel):
|
||||||
|
|
||||||
|
|
||||||
class Coordinate(models.Model):
|
class Coordinate(models.Model):
|
||||||
x = models.IntegerField()
|
x = models.FloatField()
|
||||||
y = models.IntegerField()
|
y = models.FloatField()
|
||||||
|
|
||||||
shape = models.ForeignKey(
|
shape = models.ForeignKey(
|
||||||
to=BaseShape,
|
to=BaseShape,
|
||||||
|
|
39
image_markuper/dicom/services.py
Normal file
39
image_markuper/dicom/services.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import magic
|
||||||
|
from dicom.models import Dicom, ListOfDicom
|
||||||
|
from django.core.files import File
|
||||||
|
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
|
||||||
|
from utils.generators import generate_charset
|
||||||
|
|
||||||
|
|
||||||
|
def process_files(files: list[TemporaryUploadedFile | InMemoryUploadedFile], user):
|
||||||
|
d_list = ListOfDicom.objects.create()
|
||||||
|
for file in files:
|
||||||
|
content_type = magic.from_file(file.temporary_file_path())
|
||||||
|
if content_type == "DICOM medical imaging data":
|
||||||
|
Dicom.objects.create(file=file, list=d_list, user=user)
|
||||||
|
elif "Zip" in content_type:
|
||||||
|
dit_path = f"/tmp/{generate_charset(10)}"
|
||||||
|
os.mkdir(dit_path)
|
||||||
|
with zipfile.ZipFile(file.temporary_file_path(), "r") as zip_ref:
|
||||||
|
zip_ref.extractall(dit_path)
|
||||||
|
files = glob.glob(dit_path + "/**/*", recursive=True)
|
||||||
|
|
||||||
|
for file_in_d in files:
|
||||||
|
if not os.path.isdir(file_in_d):
|
||||||
|
content_type = magic.from_file(file_in_d)
|
||||||
|
if content_type == "DICOM medical imaging data":
|
||||||
|
path = Path(file_in_d)
|
||||||
|
with path.open(mode="rb") as f:
|
||||||
|
Dicom.objects.create(
|
||||||
|
file=File(f, name=file_in_d.split("/")[-1]),
|
||||||
|
list=d_list,
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
shutil.rmtree(dit_path)
|
||||||
|
return d_list
|
|
@ -1,4 +1,5 @@
|
||||||
pytz==2022.5 # https://github.com/stub42/pytz
|
pytz==2022.5 # https://github.com/stub42/pytz
|
||||||
|
python-magic==0.4.27
|
||||||
python-slugify==6.1.2 # https://github.com/un33k/python-slugify
|
python-slugify==6.1.2 # https://github.com/un33k/python-slugify
|
||||||
Pillow==9.2.0 # https://github.com/python-pillow/Pillow
|
Pillow==9.2.0 # https://github.com/python-pillow/Pillow
|
||||||
argon2-cffi==21.3.0 # https://github.com/hynek/argon2_cffi
|
argon2-cffi==21.3.0 # https://github.com/hynek/argon2_cffi
|
||||||
|
|
Loading…
Reference in New Issue
Block a user