Skip to content

Commit 9caeb2d

Browse files
authored
Merge pull request #21 from PROCOLLAB-github/feature/files
Feature/files
2 parents d70e7a2 + 141ab38 commit 9caeb2d

17 files changed

Lines changed: 624 additions & 2 deletions

.env.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,9 @@ DATABASE_NAME=
1212
DATABASE_USER=
1313
DATABASE_PASSWORD=
1414
DATABASE_HOST=
15-
DATABASE_PORT=
15+
DATABASE_PORT=
16+
17+
SELECTEL_ACCOUNT_ID=
18+
SELECTEL_CONTAINER_NAME=
19+
SELECTEL_CONTAINER_PASSWORD=
20+
SELECTEL_CONTAINER_USERNAME=

files/__init__.py

Whitespace-only changes.

files/admin.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.contrib import admin
2+
3+
from files.models import UserFile
4+
5+
6+
@admin.register(UserFile)
7+
class UserFileAdmin(admin.ModelAdmin):
8+
list_display = (
9+
"link",
10+
"user",
11+
"datetime_uploaded",
12+
)
13+
list_display_links = ("link",)

files/apps.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class FilesConfig(AppConfig):
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "files"

files/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class SelectelUploadError(Exception):
2+
pass

files/helpers.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import requests
2+
import time
3+
from files.exceptions import SelectelUploadError
4+
5+
from procollab.settings import (
6+
DEBUG,
7+
SELECTEL_ACCOUNT_ID,
8+
SELECTEL_CONTAINER_NAME,
9+
SELECTEL_CONTAINER_PASSWORD,
10+
SELECTEL_CONTAINER_USERNAME,
11+
)
12+
13+
14+
class FileAPI:
15+
def __init__(self, file, user) -> None:
16+
self.file = file
17+
self.user = user
18+
19+
@staticmethod
20+
def delete(url: str) -> int:
21+
"""Deletes file from selcdn"""
22+
token = FileAPI._get_selectel_swift_token()
23+
response = requests.delete(url, headers={"X-Auth-Token": token})
24+
return response.status_code
25+
26+
def upload(self):
27+
return self._upload_via_selectel_swift()
28+
29+
def _upload_via_selectel_swift(self) -> tuple[int, str]:
30+
token = self._get_selectel_swift_token()
31+
url = self._generate_selectel_swift_file_url()
32+
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+
)
39+
40+
return response.status_code, url
41+
42+
def _generate_selectel_swift_link(sefl):
43+
link = f"https://api.selcdn.ru/v1/SEL_{SELECTEL_ACCOUNT_ID}/{SELECTEL_CONTAINER_NAME}/"
44+
if DEBUG:
45+
link += "debug/"
46+
return link
47+
48+
@staticmethod
49+
def _get_selectel_swift_token():
50+
"""Returns auth token for selcdn"""
51+
data = {
52+
"auth": {
53+
"identity": {
54+
"methods": ["password"],
55+
"password": {
56+
"user": {
57+
"id": SELECTEL_CONTAINER_USERNAME,
58+
"password": SELECTEL_CONTAINER_PASSWORD,
59+
}
60+
},
61+
}
62+
}
63+
}
64+
r = requests.post("https://api.selcdn.ru/v3/auth/tokens", json=data)
65+
if r.status_code not in [200, 201]:
66+
raise SelectelUploadError("Couldn't generate a token for selcdn")
67+
return r.headers["x-subject-token"]
68+
69+
def _get_file_extension(self) -> str:
70+
if len(self.file.name.split(".")) > 1:
71+
return "." + self.file.name.split(".")[1]
72+
return ""
73+
74+
def _generate_selectel_swift_file_url(self) -> str:
75+
"""
76+
Generates url for selcdn
77+
78+
Returns:
79+
url: str looks like /hashedEmail/hashedFilename_hashedTime.extension
80+
"""
81+
link = self._generate_selectel_swift_link()
82+
extension = self._get_file_extension()
83+
return (
84+
link
85+
+ f"{abs(hash(self.user.email))}/{abs(hash(self.file.name))}_{abs(hash(time.time()))}{extension}"
86+
)

files/migrations/0001_initial.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Generated by Django 4.1.2 on 2022-11-11 22:37
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name="UserFile",
19+
fields=[
20+
(
21+
"id",
22+
models.BigAutoField(
23+
auto_created=True,
24+
primary_key=True,
25+
serialize=False,
26+
verbose_name="ID",
27+
),
28+
),
29+
("link", models.URLField()),
30+
("datetime_uploaded", models.DateTimeField(auto_now_add=True)),
31+
(
32+
"user",
33+
models.ForeignKey(
34+
null=True,
35+
on_delete=django.db.models.deletion.SET_NULL,
36+
to=settings.AUTH_USER_MODEL,
37+
),
38+
),
39+
],
40+
),
41+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 4.1.2 on 2022-11-12 13:56
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("files", "0001_initial"),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name="userfile",
15+
name="id",
16+
),
17+
migrations.AlterField(
18+
model_name="userfile",
19+
name="link",
20+
field=models.URLField(primary_key=True, serialize=False),
21+
),
22+
]

files/migrations/__init__.py

Whitespace-only changes.

files/models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from django.contrib.auth import get_user_model
2+
from django.db import models
3+
4+
User = get_user_model()
5+
6+
7+
class UserFile(models.Model):
8+
"""
9+
UserFile model
10+
11+
Attributes:
12+
user: User who uploaded the file
13+
link: Link to the file on the CDN
14+
datetime_uploaded: Datetime when the file was uploaded
15+
"""
16+
17+
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
18+
link = models.URLField(primary_key=True, null=False)
19+
datetime_uploaded = models.DateTimeField(auto_now_add=True)
20+
21+
def __str__(self):
22+
return f"UserFile by {self.user}, {self.link}"

0 commit comments

Comments
 (0)