Skip to content

Commit 7efe6f2

Browse files
authored
Merge pull request #158 from PROCOLLAB-github/feature/mime-types
Add mime_type field to UserFile model
2 parents 984563a + 26f951f commit 7efe6f2

10 files changed

Lines changed: 216 additions & 231 deletions

File tree

chats/consumers/chat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async def connect(self):
6565
await self.accept()
6666

6767
async def disconnect(self, close_code):
68-
"""User disconnected from websocket, Don't have to do anything here"""
68+
"""User disconnected from websocket"""
6969
pass
7070

7171
async def receive_json(self, content, **kwargs):

files/helpers.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import requests
22
import time
3+
import magic
4+
from django.core.files.uploadedfile import TemporaryUploadedFile
5+
36
from files.exceptions import SelectelUploadError
7+
from files.models import UserFile
48

59
from procollab.settings import (
610
DEBUG,
@@ -12,9 +16,11 @@
1216

1317

1418
class FileAPI:
15-
def __init__(self, file, user) -> None:
16-
self.file = file
19+
def __init__(self, file: TemporaryUploadedFile, user) -> None:
20+
self.file = file # it's TemporaryUploadedFile, and it will be
21+
# removed after first .close() call, so we must read this file only once
1722
self.user = user
23+
self.file_object = self.file.open(mode="rb")
1824

1925
@staticmethod
2026
def delete(url: str) -> int:
@@ -23,21 +29,34 @@ def delete(url: str) -> int:
2329
response = requests.delete(url, headers={"X-Auth-Token": token})
2430
return response.status_code
2531

26-
def upload(self):
27-
return self._upload_via_selectel_swift()
32+
def upload(self) -> str:
33+
url = self._upload_via_selectel_swift()
34+
info = get_file_info(self.file)
35+
UserFile.objects.create(
36+
user=self.user,
37+
link=url,
38+
name=info["name"],
39+
size=info["size"],
40+
extension=info["extension"],
41+
mime_type=info["mime_type"],
42+
)
43+
self.file_object.close()
44+
return url
2845

29-
def _upload_via_selectel_swift(self) -> tuple[int, str]:
46+
def _upload_via_selectel_swift(self) -> str:
3047
token = self._get_selectel_swift_token()
3148
url = self._generate_selectel_swift_file_url()
3249

33-
with self.file.open(mode="rb") as file_object:
34-
response = requests.put(
35-
url,
36-
headers={"X-Auth-Token": token, "Content-Type": file_object.content_type},
37-
data=file_object.read(),
38-
)
50+
requests.put(
51+
url,
52+
headers={
53+
"X-Auth-Token": token,
54+
"Content-Type": self.file_object.content_type,
55+
},
56+
data=self.file_object.read(),
57+
)
3958

40-
return response.status_code, url
59+
return url
4160

4261
def _generate_selectel_swift_link(sefl):
4362
link = f"https://api.selcdn.ru/v1/SEL_{SELECTEL_ACCOUNT_ID}/{SELECTEL_CONTAINER_NAME}/"
@@ -61,10 +80,10 @@ def _get_selectel_swift_token():
6180
}
6281
}
6382
}
64-
r = requests.post("https://api.selcdn.ru/v3/auth/tokens", json=data)
65-
if r.status_code not in [200, 201]:
83+
response = requests.post("https://api.selcdn.ru/v3/auth/tokens", json=data)
84+
if response.status_code not in [200, 201]:
6685
raise SelectelUploadError("Couldn't generate a token for selcdn")
67-
return r.headers["x-subject-token"]
86+
return response.headers["x-subject-token"]
6887

6988
def _get_file_extension(self) -> str:
7089
if len(self.file.name.split(".")) > 1:
@@ -85,11 +104,16 @@ def _generate_selectel_swift_file_url(self) -> str:
85104
)
86105

87106

88-
def get_file_info(request):
89-
name, ext = request.name.split(".")
107+
def get_file_info(file: TemporaryUploadedFile) -> dict:
108+
name, ext = file.name.split(".")
90109

91110
return {
92-
"size": request.size,
111+
"size": file.size,
93112
"name": name,
94113
"extension": ext,
114+
"mime_type": get_file_mime_type(file),
95115
}
116+
117+
118+
def get_file_mime_type(file: TemporaryUploadedFile):
119+
return magic.from_file(file.temporary_file_path(), mime=True)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.1.3 on 2023-07-08 17:58
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("files", "0003_userfile_extension_userfile_name_userfile_size"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="userfile",
15+
name="mime_type",
16+
field=models.CharField(default="", max_length=256),
17+
),
18+
]

files/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class UserFile(models.Model):
2222
datetime_uploaded = models.DateTimeField(auto_now_add=True)
2323
name = models.TextField(blank=False, default="file")
2424
extension = models.TextField(blank=True, default="")
25+
mime_type = models.CharField(max_length=256, default="")
2526
size = models.PositiveBigIntegerField(null=False, blank=True, default=1)
2627

2728
def __str__(self):

files/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Meta:
99
fields = [
1010
"name",
1111
"extension",
12+
"mime_type",
1213
"size",
1314
"link",
1415
"user",

files/views.py

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from rest_framework.generics import get_object_or_404
55
from rest_framework.response import Response
66

7-
from files.helpers import FileAPI, get_file_info
7+
from files.helpers import FileAPI
88
from files.models import UserFile
99
from files.serializers import UserFileSerializer
1010

@@ -17,40 +17,33 @@ class FileView(generics.GenericAPIView):
1717
@transaction.atomic
1818
def post(self, request):
1919
"""creates a UserFile object and uploads the file to selectel"""
20-
file_api = FileAPI(request.FILES["file"], request.user)
21-
status_code, url = file_api.upload()
22-
23-
if status_code == 201:
24-
info = get_file_info(request.FILES["file"])
25-
UserFile.objects.create(
26-
user=request.user,
27-
link=url,
28-
name=info["name"],
29-
size=info["size"],
30-
extension=info["extension"],
31-
)
20+
try:
21+
file_api = FileAPI(request.FILES["file"], request.user)
22+
url = file_api.upload()
3223
return Response({"url": url}, status=status.HTTP_201_CREATED)
33-
34-
return Response("Failed to upload file", status=status.HTTP_409_CONFLICT)
35-
36-
def delete(self, request, *args, **kwargs):
37-
"""deletes the file (only if the request is sent by the user who owns it!)
38-
The link has to be specified in the JSON body, not in the URL arguments.
39-
"""
40-
# get the link from the query
41-
if request.query_params and (request.query_params.get("link") is not None):
42-
link = request.query_params.get("link")
43-
else:
44-
return Response(
45-
{
46-
"error": "you have to pass the link of the object you want to delete in query parameters"
47-
},
48-
status=status.HTTP_400_BAD_REQUEST,
49-
)
50-
instance = get_object_or_404(self.get_queryset(), link=link)
51-
if instance.user != request.user:
52-
return Response(status=status.HTTP_403_FORBIDDEN)
53-
FileAPI.delete(instance.link) # delete the file via api
54-
instance.delete() # delete the UserFile object
55-
56-
return Response(status=status.HTTP_204_NO_CONTENT)
24+
except Exception as error:
25+
print(error)
26+
return Response("Failed to upload file", status=status.HTTP_409_CONFLICT)
27+
28+
29+
def delete(self, request, *args, **kwargs):
30+
"""deletes the file (only if the request is sent by the user who owns it!)
31+
The link has to be specified in the JSON body, not in the URL arguments.
32+
"""
33+
# get the link from the query
34+
if request.query_params and (request.query_params.get("link") is not None):
35+
link = request.query_params.get("link")
36+
else:
37+
return Response(
38+
{
39+
"error": "you have to pass the link of the object you want to delete in query parameters"
40+
},
41+
status=status.HTTP_400_BAD_REQUEST,
42+
)
43+
instance = get_object_or_404(self.get_queryset(), link=link)
44+
if instance.user != request.user:
45+
return Response(status=status.HTTP_403_FORBIDDEN)
46+
FileAPI.delete(instance.link) # delete the file via api
47+
instance.delete() # delete the UserFile object
48+
49+
return Response(status=status.HTTP_204_NO_CONTENT)

0 commit comments

Comments
 (0)