-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathviews.py
180 lines (150 loc) · 7.23 KB
/
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
from typing import Optional
from chunked_upload.models import ChunkedUpload
from chunked_upload.views import ChunkedUploadCompleteView, ChunkedUploadView
from django.contrib.auth.base_user import AbstractBaseUser
from django.core.files.uploadedfile import UploadedFile
from django.db.models import Q, Count, QuerySet
from django.http import HttpRequest, HttpResponse, Http404
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions, filters
from rest_framework.exceptions import ValidationError
from rest_framework.views import APIView
from common.pagination import StandardResultsSetPagination
from user_files.serializers import UserFileSerializer, UserFileWithoutFileObjSerializer
from .models import UserFile
from rest_framework.response import Response
class UserFileChunkedUploadView(ChunkedUploadView):
"""API to upload a chunk of a user file"""
model = ChunkedUpload
field_name = 'file_obj'
class UserFileChunkedUploadCompleteView(ChunkedUploadCompleteView):
"""API to complete the upload"""
model = ChunkedUpload
do_md5_check = True
raised_exception: Optional[ValidationError] = None # To use in the get_response_data
def on_completion(self, uploaded_file: UploadedFile, request):
"""Callback when the upload is complete"""
data = request.POST.dict()
data['file_obj'] = uploaded_file # Assigns the uploaded file to the new object
serializer = UserFileSerializer(data=data, context={'request': request})
try:
serializer.is_valid(raise_exception=True) # is_valid() must be called
serializer.save()
except ValidationError as ex:
self.raised_exception = ex
def get_response_data(self, chunked_upload: ChunkedUpload, request):
"""Final response, returns the created UserFile object"""
if self.raised_exception is None:
return { 'ok': True }
error_msg = self.raised_exception.detail['file_obj']['status']['message']
return {
'ok': False,
'file': None,
'errorMsg': error_msg
}
def get_an_user_file(user: AbstractBaseUser, user_file_pk: int) -> UserFile:
"""
Returns the specific User's file object from DB
@param user: User to retrieve his Dataset
@param user_file_pk: ID of the UserFile to retrieve
@raise 404 HTTP error if the object isn't in the DB
@return: UserFile object
"""
queryset = get_user_files(
user,
public_only=False,
private_only=False,
with_survival_only=False
)
return get_object_or_404(queryset, pk=user_file_pk)
def get_own_or_as_admin_user_files(user: AbstractBaseUser):
"""
Returns only the User's Files which were uploaded by himself or belongs to an Institution which his is the admin of
@param user: User to retrieve his Datasets
@return: User's Files
"""
return UserFile.objects.filter(Q(user=user) | (
Q(institutions__institutionadministration__user=user)
& Q(institutions__institutionadministration__is_institution_admin=True)
)).distinct()
def get_user_files(user: AbstractBaseUser, public_only: bool, private_only: bool, with_survival_only: bool) -> QuerySet:
"""
Returns the User's files objects from DB
@param user: User to retrieve his Datasets
@param public_only: If True retrieves only the public Datasets
@param private_only: If True retrieves only the uploaded Datasets by the User, otherwise, returns all his datasets
and the Datasets of the Institutions the user belongs to. If public_only is set to True, this parameter is ignored
@param with_survival_only: If True retrieves only the clinical Datasets which have at least on survival column tuple
@return: QuerySet of UserFile objects
"""
if public_only:
# Gets public Datasets
filter_condition = Q(is_public=True)
else:
# Gets UserFiles which belongs to current user
filter_condition = Q(user=user)
if not private_only:
# Gets public datasets too
filter_condition |= Q(is_public=True)
# In some cases, gets the Datasets of the Institutions the user belongs to
filter_condition |= Q(institutions__institutionadministration__user=user)
# Filters by survival data
user_files_objects = UserFile.objects
if with_survival_only:
user_files_objects = user_files_objects.annotate(survival_columns_count=Count('survival_columns'))
filter_condition &= Q(survival_columns_count__gt=0)
return user_files_objects.filter(filter_condition).select_related('tag').distinct()
class UserFileHeaders(APIView):
permission_classes = [permissions.IsAuthenticated]
"""REST endpoint: list for UserFile header. """
@staticmethod
def get(request, pk: int) -> QuerySet:
"""
Returns the User's files headers from DB
@param user: User to retrieve his Datasets
@param pk: Id from file
@return: File's headers
"""
user = request.user
user_file = get_an_user_file(user=user, user_file_pk=pk)
list_of_header = user_file.get_column_names()
return Response(list_of_header)
class UserFileList(generics.ListAPIView):
"""REST endpoint: list for UserFile model. """
def get_queryset(self):
# Returns own Datasets if explicitly requested...
visibility = self.request.GET.get('visibility')
public_only = visibility == 'public'
private_only = visibility == 'private'
with_survival_only = self.request.GET.get('with_survival_only') == 'true'
return get_user_files(self.request.user, public_only, private_only, with_survival_only)
serializer_class = UserFileWithoutFileObjSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.OrderingFilter, filters.SearchFilter, DjangoFilterBackend]
filterset_fields = ['tag', 'file_type', 'institutions']
search_fields = ['name', 'description']
ordering_fields = ['name', 'description', 'upload_date', 'tag', 'user', 'file_type']
pagination_class = StandardResultsSetPagination
class UserFileDetail(generics.RetrieveUpdateDestroyAPIView):
"""
REST endpoint: get, modify or delete for UserFile model. A User can modify or delete ONLY the files which were
uploaded by himself or belongs to an Institution which his is the admin of
"""
def get_queryset(self):
return get_own_or_as_admin_user_files(self.request.user)
serializer_class = UserFileSerializer
permission_classes = [permissions.IsAuthenticated]
class DownloadUserFile(APIView):
permission_classes = [permissions.IsAuthenticated]
@staticmethod
def get(request: HttpRequest, pk: Optional[int] = None):
"""Downloads the specified file considering security"""
if not pk:
raise Http404()
# This function raises a 404 error in case of non-existing UserFile
user_file = get_an_user_file(user=request.user, user_file_pk=pk)
file_obj = user_file.file_obj.file
response = HttpResponse(file_obj, content_type='text/plain')
response['Content-Disposition'] = f'attachment; filename={user_file.name}'
return response