Skip to content

Commit 7bb3702

Browse files
authored
Merge pull request #167 from PROCOLLAB-github/dev
Dev
2 parents 38855e4 + 3e34174 commit 7bb3702

14 files changed

Lines changed: 210 additions & 274 deletions

File tree

.github/workflows/django-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v2
1515

16-
- name: Set up Python 3.9
16+
- name: Set up Python 3.11
1717
uses: actions/setup-python@v2
1818
with:
19-
python-version: 3.9
19+
python-version: 3.11
2020

2121
- name: cache poetry install
2222
uses: actions/cache@v2

.github/workflows/lints.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
python-version: [3.9]
16+
python-version: [3.11]
1717
steps:
1818
- uses: actions/checkout@v3
1919
- name: Set up Python ${{ matrix.python-version }}

.github/workflows/release-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
steps:
1212
- uses: actions/checkout@v2
1313

14-
- name: Set up Python 3.9
14+
- name: Set up Python 3.11
1515
uses: actions/setup-python@v2
1616
with:
17-
python-version: 3.9
17+
python-version: 3.11
1818

1919
- name: cache poetry install
2020
uses: actions/cache@v2

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.9
1+
FROM python:3.11
22

33
RUN apt update --no-install-recommends -y
44

files/admin.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1+
import reprlib
2+
13
from django.contrib import admin
4+
from django.forms import ModelForm, FileField
25

6+
from files.helpers import FileAPI
37
from files.models import UserFile
48

59

10+
class UserFileForm(ModelForm):
11+
file = FileField(required=True)
12+
13+
class Meta:
14+
model = UserFile
15+
fields = "__all__"
16+
17+
618
@admin.register(UserFile)
719
class UserFileAdmin(admin.ModelAdmin):
20+
form = UserFileForm
821
list_display = (
922
"short_link",
1023
"filename",
@@ -34,4 +47,30 @@ def filename(self, obj):
3447

3548
@admin.display(empty_value="Empty link")
3649
def short_link(self, obj):
37-
return obj.link[15:40] + "..." + obj.link[-20:]
50+
return reprlib.repr(obj.link.lstrip("https://")).strip("'")
51+
52+
def get_fieldsets(self, request, obj=None):
53+
fieldsets = super().get_fieldsets(request, obj)
54+
file_field_index = fieldsets[0][1]["fields"].index("file")
55+
if obj:
56+
# remove file field if file is already uploaded
57+
del fieldsets[0][1]["fields"][file_field_index]
58+
else:
59+
# remove other field
60+
fieldsets[0][1]["fields"] = ["file"]
61+
return fieldsets
62+
63+
def save_model(self, request, obj, form, change):
64+
file_api = FileAPI(request.FILES["file"], request.user)
65+
url, info = file_api.upload()
66+
obj.link = url
67+
obj.user = request.user
68+
obj.name = info.name
69+
obj.size = info.size
70+
obj.extension = info.extension
71+
obj.mime_type = info.mime_type
72+
super().save_model(request, obj, form, change)
73+
74+
def delete_model(self, request, obj):
75+
FileAPI.delete(obj.link)
76+
obj.delete()

files/helpers.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from typing import Union
2+
13
import requests
24
import time
35
import magic
4-
from django.core.files.uploadedfile import TemporaryUploadedFile
6+
from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
57

68
from files.exceptions import SelectelUploadError
7-
from files.models import UserFile
9+
from files.typings import UserFileInfo
810

911
from procollab.settings import (
1012
DEBUG,
@@ -16,7 +18,10 @@
1618

1719

1820
class FileAPI:
19-
def __init__(self, file: TemporaryUploadedFile, user) -> None:
21+
# fixme: looks terrible
22+
def __init__(
23+
self, file: Union[TemporaryUploadedFile, InMemoryUploadedFile], user
24+
) -> None:
2025
self.file = file # it's TemporaryUploadedFile, and it will be
2126
# removed after first .close() call, so we must read this file only once
2227
self.user = user
@@ -29,19 +34,25 @@ def delete(url: str) -> int:
2934
response = requests.delete(url, headers={"X-Auth-Token": token})
3035
return response.status_code
3136

32-
def upload(self) -> str:
37+
def upload(self) -> tuple[str, UserFileInfo]:
3338
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-
)
39+
info = self.get_file_info(self.file)
4340
self.file_object.close()
44-
return url
41+
return url, info
42+
43+
def get_file_info(
44+
self, file: Union[TemporaryUploadedFile, InMemoryUploadedFile]
45+
) -> UserFileInfo:
46+
name, ext = file.name.split(".")
47+
return UserFileInfo(
48+
size=file.size, name=name, extension=ext, mime_type=self.get_file_mime_type()
49+
)
50+
51+
def get_file_mime_type(self):
52+
if isinstance(self.file, InMemoryUploadedFile):
53+
return magic.from_buffer(self.file_object.read(), mime=True)
54+
else:
55+
return magic.from_file(self.file.temporary_file_path(), mime=True)
4556

4657
def _upload_via_selectel_swift(self) -> str:
4758
token = self._get_selectel_swift_token()
@@ -102,18 +113,3 @@ def _generate_selectel_swift_file_url(self) -> str:
102113
link
103114
+ f"{abs(hash(self.user.email))}/{abs(hash(self.file.name))}_{abs(hash(time.time()))}{extension}"
104115
)
105-
106-
107-
def get_file_info(file: TemporaryUploadedFile) -> dict:
108-
name, ext = file.name.split(".")
109-
110-
return {
111-
"size": file.size,
112-
"name": name,
113-
"extension": ext,
114-
# "mime_type": get_file_mime_type(file),
115-
}
116-
117-
118-
def get_file_mime_type(file: TemporaryUploadedFile):
119-
return magic.from_file(file.name, mime=True)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 4.1.3 on 2023-07-09 20:14
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("files", "0004_userfile_mime_type"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="userfile",
15+
options={"verbose_name": "Файл", "verbose_name_plural": "Файлы"},
16+
),
17+
]

files/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ class UserFile(models.Model):
2727

2828
def __str__(self):
2929
return f"UserFile by {self.user}, {self.link}"
30+
31+
class Meta:
32+
verbose_name = "Файл"
33+
verbose_name_plural = "Файлы"

files/typings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dataclasses import dataclass
2+
from typing import TypeAlias
3+
4+
Bytes: TypeAlias = int
5+
6+
7+
@dataclass(slots=True, frozen=True)
8+
class UserFileInfo:
9+
size: Bytes
10+
name: str
11+
extension: str
12+
mime_type: str

files/views.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,21 @@ 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-
url = file_api.upload()
22-
return Response({"url": url}, status=status.HTTP_201_CREATED)
20+
try:
21+
file_api = FileAPI(request.FILES["file"], request.user)
22+
url, info = file_api.upload()
23+
UserFile.objects.create(
24+
user=request.user,
25+
link=url,
26+
name=info["name"],
27+
size=info["size"],
28+
extension=info["extension"],
29+
mime_type=info["mime_type"],
30+
)
31+
return Response({"url": url}, status=status.HTTP_201_CREATED)
32+
except Exception as error:
33+
print(error)
34+
return Response("Failed to upload file", status=status.HTTP_409_CONFLICT)
2335

2436

2537
def delete(self, request, *args, **kwargs):

0 commit comments

Comments
 (0)