Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Image Gallery Functionality & Endpoints #3312

Merged
merged 2 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lego/api/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
)
router.register(r"feed-personal", PersonalFeedViewSet, basename="feed-personal")
router.register(r"feed-user/(?P<user_pk>\d+)", UserFeedViewSet, basename="feed-user")
router.register(r"files", FileViewSet)
router.register(r"files", FileViewSet, basename="files")
router.register(r"followers-company", FollowCompanyViewSet)
router.register(r"followers-event", FollowEventViewSet)
router.register(r"followers-user", FollowUserViewSet)
Expand Down
23 changes: 22 additions & 1 deletion lego/apps/events/serializers/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
RegistrationReadDetailedSerializer,
RegistrationReadSerializer,
)
from lego.apps.files.fields import ImageField
from lego.apps.files.fields import File, ImageField
from lego.apps.tags.serializers import TagSerializerMixin
from lego.apps.users.constants import GROUP_GRADE
from lego.apps.users.fields import AbakusGroupField
Expand Down Expand Up @@ -196,6 +196,27 @@ def get_attended_count(self, event):
return event.registrations.filter(presence=PRESENCE_CHOICES.PRESENT).count()


class ImageGallerySerializer(BasisModelSerializer):
cover = ImageField(source="key", required=True, options={"height": 500})
cover_placeholder = ImageField(
source="key", required=False, options={"height": 50, "filters": ["blur(20)"]}
)

jonasdeluna marked this conversation as resolved.
Show resolved Hide resolved
class Meta:
model = File
fields = (
"key",
"file_type",
"state",
"public",
"save_for_use",
"cover",
"token",
"cover_placeholder",
)
read_only = True


class EventUserRegSerializer(EventReadSerializer):
user_reg = serializers.SerializerMethodField()

Expand Down
18 changes: 18 additions & 0 deletions lego/apps/events/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
EventReadSerializer,
EventReadUserDetailedSerializer,
EventUserRegSerializer,
ImageGallerySerializer,
populate_event_registration_users_with_grade,
)
from lego.apps.events.serializers.pools import PoolCreateAndUpdateSerializer
Expand All @@ -66,6 +67,8 @@
save_and_notify_payment,
)
from lego.apps.events.websockets import notify_event_registration
from lego.apps.files.constants import IMAGE
from lego.apps.files.models import File
from lego.apps.permissions.api.filters import LegoPermissionFilter
from lego.apps.permissions.api.views import AllowedPermissionsMixin
from lego.apps.permissions.constants import EDIT, VIEW
Expand Down Expand Up @@ -345,6 +348,21 @@
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

@decorators.action(
detail=False,
serializer_class=ImageGallerySerializer,
permission_classes=[permissions.IsAuthenticated],
jonasdeluna marked this conversation as resolved.
Show resolved Hide resolved
)
def cover_image_gallery(self, request):
if request.user.has_perm(EDIT, Event) is False:
raise PermissionDenied()
queryset = File.objects.filter(

Check warning on line 359 in lego/apps/events/views.py

View check run for this annotation

Codecov / codecov/patch

lego/apps/events/views.py#L357-L359

Added lines #L357 - L359 were not covered by tests
file_type=IMAGE,
save_for_use=True,
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

Check warning on line 364 in lego/apps/events/views.py

View check run for this annotation

Codecov / codecov/patch

lego/apps/events/views.py#L363-L364

Added lines #L363 - L364 were not covered by tests


class PoolViewSet(
mixins.CreateModelMixin,
Expand Down
7 changes: 6 additions & 1 deletion lego/apps/files/fixtures/test_files.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
token: token
user: 1
public: true
save_for_use: false

- pk: abakus1.png
model: files.File
Expand All @@ -15,6 +16,7 @@
token: token
user: 1
public: true
save_for_use: false

- pk: abakus2.png
model: files.File
Expand All @@ -24,15 +26,17 @@
token: token
user: 1
public: true
save_for_use: false

- pk: default_male_avatar.png
model: files.File
fields:
state: ready
file_type: image
token: token
token: token4
user: 1
public: true
save_for_use: false

- pk: default_female_avatar.png
model: files.File
Expand All @@ -42,3 +46,4 @@
token: token
user: 1
public: true
save_for_use: false
17 changes: 17 additions & 0 deletions lego/apps/files/migrations/0005_file_save_for_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.0.8 on 2022-11-09 18:33

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("files", "0004_auto_20200907_2209"),
]

operations = [
migrations.AddField(
model_name="file",
name="save_for_use",
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions lego/apps/files/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class File(TimeStampModel):
state = models.CharField(max_length=24, choices=FILE_STATES, default=PENDING_UPLOAD)
file_type = models.CharField(max_length=24, choices=FILE_TYPES)
token = models.CharField(max_length=32)
save_for_use = models.BooleanField(default=False, null=False)
user = models.ForeignKey(
"users.User",
related_name="uploaded_files",
Expand Down
21 changes: 21 additions & 0 deletions lego/apps/files/serializers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied, ValidationError

from lego.apps.files.models import File

from .validators import KEY_REGEX


class FileUploadSerializer(serializers.Serializer):
key = serializers.RegexField(regex=KEY_REGEX)
public = serializers.BooleanField(required=True)


class FileSaveForUseSerializer(serializers.ModelSerializer):
class Meta:
model = File
save_for_use = serializers.BooleanField(required=True)

fields = (
"token",
"save_for_use",
)

def validate(self, data):
if self.instance.token != data["token"]:
raise PermissionDenied()
if "save_for_use" not in data:
raise ValidationError()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to include an error message. but not critical

return data
132 changes: 131 additions & 1 deletion lego/apps/files/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from unittest import mock

from rest_framework import status
from rest_framework.reverse import reverse

from lego.apps.users.models import User
from lego.apps.users.models import AbakusGroup, User
from lego.utils.test_utils import BaseAPITestCase


Expand Down Expand Up @@ -44,3 +45,132 @@ def test_post_create_file_call(self, mock_create_file):
except Exception:
pass
mock_create_file.assert_called_with(key, self.user, False)


def _get_detail_url(key):
return reverse("api:v1:files-imagegallery", kwargs={"key": key})


class SetSaveForUserTest(BaseAPITestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a super clean and thorough test! Love it!! 💯

fixtures = ["test_users.yaml", "test_files.yaml", "test_abakus_groups.yaml"]
key = "abakus.png"
token = "token"
url = _get_detail_url(key)

def setUp(self):
self.webkom_user = User.objects.get(pk=9)
self.user_no_perms = User.objects.get(pk=1)
self.bedkom_user = User.objects.get(pk=2)
AbakusGroup.objects.get(name="Webkom").add_user(self.webkom_user)
AbakusGroup.objects.get(name="Bedkom").add_user(self.bedkom_user)

def test_update_file_no_auth(self):
res = self.client.patch(f"{self.url}", data={})
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)

def test_update_file_no_token(self):
self.client.force_authenticate(self.webkom_user)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": True,
"token": "wrgon_token",
},
)
self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)

def test_update_file_success(self):
self.client.force_authenticate(self.webkom_user)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": True,
"token": self.token,
},
)
self.assertEqual(res.status_code, status.HTTP_200_OK)

def test_update_file_success_bedkom(self):
self.client.force_authenticate(self.bedkom_user)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": True,
"token": self.token,
},
)
self.assertEqual(res.status_code, status.HTTP_200_OK)

def test_update_file_bedkom_no_value(self):
self.client.force_authenticate(self.bedkom_user)
res = self.client.patch(
f"{self.url}",
data={
"token": self.token,
},
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

def test_update_file_bedkom_bad_value(self):
self.client.force_authenticate(self.bedkom_user)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": "1",
},
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

def test_update_file_bad_url(self):
self.client.force_authenticate(self.webkom_user)
res = self.client.patch(
"/api/v1/files/error.png/set_save_for_use/",
data={
"token": self.token,
"save_for_use": True,
},
)
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)

def test_update_file_no_event_perm(self):
self.client.force_authenticate(self.user_no_perms)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": True,
"token": self.token,
},
)
self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)

def test_update_file_no_event_perm_no_token(self):
self.client.force_authenticate(self.user_no_perms)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": True,
"token": "wrong_token",
},
)
self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN)

def test_update_file_no_auth_correct_token(self):
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": True,
"token": self.token,
},
)
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)

def test_update_file_set_false_bedkom(self):
self.client.force_authenticate(self.bedkom_user)
res = self.client.patch(
f"{self.url}",
data={
"save_for_use": False,
"token": self.token,
},
)
self.assertEqual(res.status_code, status.HTTP_200_OK)
21 changes: 20 additions & 1 deletion lego/apps/files/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
status,
viewsets,
)
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response

from lego.apps.events.models import Event
from lego.apps.files.exceptions import UnknownFileType
from lego.apps.permissions.constants import EDIT

from .models import File
from .serializers import FileUploadSerializer
from .serializers import FileSaveForUseSerializer, FileUploadSerializer
from .utils import prepare_file_upload
from .validators import KEY_REGEX_RAW

Expand Down Expand Up @@ -46,6 +49,22 @@
status=status.HTTP_201_CREATED,
)

@decorators.action(
detail=True,
methods=["PATCH"],
permission_classes=[permissions.IsAuthenticated],
)
def imagegallery(self, request, *args, **kwargs):
if request.user.has_perm(EDIT, Event) is False:
raise PermissionDenied()
file = self.get_object()
serializer = FileSaveForUseSerializer(file, data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(data=serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Check warning on line 66 in lego/apps/files/views.py

View check run for this annotation

Codecov / codecov/patch

lego/apps/files/views.py#L66

Added line #L66 was not covered by tests
Comment on lines +62 to +66
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you have raise_exception in is_valid(), you won't execute the code path in the else block no?

Not an issue though :)


@decorators.action(
detail=True,
methods=["GET"],
Expand Down