Skip to content

Commit

Permalink
Add sendfile
Browse files Browse the repository at this point in the history
  • Loading branch information
kovacspe committed May 21, 2024
1 parent 1677dd1 commit 6083380
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 13 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dj-rest-auth = "~=5.0.1"
django = "~=3.2.23"
django-allauth = "~=0.58.2"
django-filter = "~=23.5"
django-sendfile2 = "~=0.7.1"
djangorestframework = "~=3.14.0"
drf-writable-nested = "~=0.7.0"
pillow = "~=10.3.0"
Expand Down
25 changes: 20 additions & 5 deletions competition/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
from personal.models import Profile, School
from user.models import User

private_storage = FileSystemStorage(location=settings.PRIVATE_STORAGE_ROOT)
private_storage = FileSystemStorage(location=settings.PRIVATE_STORAGE_ROOT,
base_url='/protected/'
)


class CompetitionType(models.Model):
Expand Down Expand Up @@ -620,11 +622,11 @@ class Vote(models.IntegerChoices):


def get_solution_path(instance, filename):
return instance.get_solution_path()
return instance.get_solution_file_path()


def get_corrected_solution_path(instance, filename):
return instance.get_corrected_solution_path()
return instance.get_corrected_solution_file_path()


class Solution(models.Model):
Expand Down Expand Up @@ -673,18 +675,31 @@ def get_solution_file_name(self):
f'-{self.problem.id}-{self.semester_registration.id}.pdf'

def get_solution_file_path(self):
return f'solutions/user_solutions/{self.get_solution_file_path()}'
return f'solutions/user_solutions/{self.get_solution_file_name()}'

def get_corrected_solution_file_name(self):
return f'{self.semester_registration.profile.user.get_full_name_camel_case()}'\
f'-{self.problem.id}-{self.semester_registration.id}_corrected.pdf'

def get_corrected_solution_file_path(self):
return f'solutions/corrected/{self.get_corrected_solution_file_path()}'
return f'solutions/corrected/{self.get_corrected_solution_file_name()}'

def can_user_modify(self, user):
return self.problem.can_user_modify(user)

def can_access(self, user):
return self.semester_registration.profile.user == user or self.can_user_modify(user)

@classmethod
def get_by_filepath(cls, path):
try:
return cls.objects.get(solution=path)
except cls.DoesNotExist:
try:
return cls.objects.get(corrected_solution=path)
except cls.DoesNotExist:
return None

@classmethod
def can_user_create(cls, user: User, data: dict) -> bool:
problem = Problem.objects.get(pk=data['problem'])
Expand Down
13 changes: 7 additions & 6 deletions competition/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,11 @@ def upload_solution(self, request, pk=None):
problem=problem,
semester_registration=event_registration,
late_tag=late_tag,
is_online=True
is_online=True,
solution=file
)
solution.solution.save(
solution.get_solution_file_name(), file, save=True)
# solution.solution.save(
# solution.get_solution_file_name(), file, save=True)

return Response(status=status.HTTP_201_CREATED)

Expand Down Expand Up @@ -394,7 +395,7 @@ def upload_solutions_with_points(self, request, pk=None):
except (IndexError, ValueError, AssertionError):
errors.append({
'filename': filename,
'status': 'Nedá sa prečítať názov súboru. Skontroluj, že názov súboru'
'status': 'Nedá sa prečítať názov súboru. Skontroluj, že názov súboru'
'je v tvare BODY-MENO-ID_ULOHY-ID_REGISTRACIE_USERA.pdf'
})
continue
Expand Down Expand Up @@ -583,7 +584,7 @@ def upload_solution_file(self, request, pk=None):
raise exceptions.ParseError(
detail='Riešenie nie je vo formáte pdf')
solution.solution.save(
solution.get_solution_file_name(), file, save=True
solution.get_solution_file_path(), file, save=True
)
return Response(status=status.HTTP_201_CREATED)

Expand All @@ -600,7 +601,7 @@ def upload_corrected_solution_file(self, request, pk=None):
raise exceptions.ParseError(
detail='Riešenie nie je vo formáte pdf')
solution.corrected_solution.save(
solution.get_corrected_solution_file_name(), file, save=True
solution.get_corrected_solution_file_path(), file, save=True
)
return Response(status=status.HTTP_201_CREATED)

Expand Down
Empty file added downloads/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions downloads/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class DownloadsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'downloads'
Empty file.
3 changes: 3 additions & 0 deletions downloads/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
3 changes: 3 additions & 0 deletions downloads/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
18 changes: 18 additions & 0 deletions downloads/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.conf.urls import url
from django.utils.translation import ugettext_lazy as _

from competition.models import Solution

from .views import download_protected_file

urlpatterns = [
# Include non-translated versions only since Admin ignores lang prefix
url(r'solutions/(?P<path>.*)$', download_protected_file,
{'path_prefix': 'solutions/', 'model_class': Solution},
name='download_solution'),
url(r'corrected_solutions/(?P<path>.*)$', download_protected_file,
{'path_prefix': 'corrected_solutions/',
'model_class': Solution},
name='download_corrected_solution'),

]
29 changes: 29 additions & 0 deletions downloads/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django_sendfile import sendfile
from rest_framework.request import Request


@login_required
def download_protected_file(request: Request, model_class, path_prefix, path):
"""
This view allows download of the file at the specified path, if the user
is allowed to. This is checked by calling the model's can_access_files
method.
"""
filepath = os.path.join(settings.PRIVATE_STORAGE_ROOT, path_prefix, path)
filepath_mediapath = path_prefix + path

if request.user.is_authenticated:
# Superusers can access all files
if request.user.is_superuser:
return sendfile(request, filepath)
obj = model_class.get_by_filepath(filepath_mediapath)

if obj is not None and obj.can_access(request.user):
return sendfile(request, filepath)

raise PermissionDenied
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Django==3.2.25
django-allauth==0.58.2
django-filter==23.5
django-rest-swagger==2.2.0
django-sendfile2==0.7.1
django-typomatic==2.5.0
djangorestframework==3.14.0
drf-writable-nested==0.7.0
Expand Down
5 changes: 3 additions & 2 deletions webstrom/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

MEDIA_URL = '/media/'
PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, 'protected_media')

PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, 'protected_media/')
SENDFILE_ROOT = PRIVATE_STORAGE_ROOT
SENDFILE_BACKEND = "django_sendfile.backends.simple"
# Email backend

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
Expand Down
1 change: 1 addition & 0 deletions webstrom/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
path('cms/', include('cms.urls')),
path('personal/', include('personal.urls')),
path('base/', include('base.urls')),
path('protected/', include('downloads.urls')),
# Dočasná cesta pre allauth bez rest frameworku
path('accounts/', include('allauth.urls')),
]
Expand Down

0 comments on commit 6083380

Please sign in to comment.