Skip to content
Snippets Groups Projects
Verified Commit 9767c8f4 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

See #190: API and serializers to manage import requests

parent a443f943
No related branches found
No related tags found
No related merge requests found
......@@ -2,10 +2,10 @@ from rest_framework import serializers
class Action(object):
def __init__(self, name, allow_all=False, filters=None):
def __init__(self, name, allow_all=False, qs_filter=None):
self.name = name
self.allow_all = allow_all
self.filters = filters or {}
self.qs_filter = qs_filter
def __repr__(self):
return "<Action {}>".format(self.name)
......@@ -65,7 +65,6 @@ class ActionSerializer(serializers.Serializer):
"You cannot apply this action on all objects"
)
final_filters = data.get("filters", {}) or {}
final_filters.update(data["action"].filters)
if self.filterset_class and final_filters:
qs_filterset = self.filterset_class(final_filters, queryset=data["objects"])
try:
......@@ -74,6 +73,9 @@ class ActionSerializer(serializers.Serializer):
raise serializers.ValidationError("Invalid filters")
data["objects"] = qs_filterset.qs
if data["action"].qs_filter:
data["objects"] = data["action"].qs_filter(data["objects"])
data["count"] = data["objects"].count()
if data["count"] < 1:
raise serializers.ValidationError("No object matching your request")
......
......@@ -2,6 +2,7 @@ from django_filters import rest_framework as filters
from funkwhale_api.common import fields
from funkwhale_api.music import models as music_models
from funkwhale_api.requests import models as requests_models
from funkwhale_api.users import models as users_models
......@@ -50,3 +51,13 @@ class ManageInvitationFilterSet(filters.FilterSet):
if value is None:
return queryset
return queryset.open(value)
class ManageImportRequestFilterSet(filters.FilterSet):
q = fields.SearchFilter(
search_fields=["user__username", "albums", "artist_name", "comment"]
)
class Meta:
model = requests_models.ImportRequest
fields = ["q", "status"]
from django.db import transaction
from django.utils import timezone
from rest_framework import serializers
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.music import models as music_models
from funkwhale_api.requests import models as requests_models
from funkwhale_api.users import models as users_models
from . import filters
......@@ -154,9 +156,79 @@ class ManageInvitationSerializer(serializers.ModelSerializer):
class ManageInvitationActionSerializer(common_serializers.ActionSerializer):
actions = [common_serializers.Action("delete", allow_all=False)]
actions = [
common_serializers.Action(
"delete", allow_all=False, qs_filter=lambda qs: qs.open()
)
]
filterset_class = filters.ManageInvitationFilterSet
@transaction.atomic
def handle_delete(self, objects):
return objects.delete()
class ManageImportRequestSerializer(serializers.ModelSerializer):
user = ManageUserSimpleSerializer(required=False)
class Meta:
model = requests_models.ImportRequest
fields = [
"id",
"status",
"creation_date",
"imported_date",
"user",
"albums",
"artist_name",
"comment",
]
read_only_fields = [
"id",
"status",
"creation_date",
"imported_date",
"user",
"albums",
"artist_name",
"comment",
]
def validate_code(self, value):
if not value:
return value
if users_models.Invitation.objects.filter(code__iexact=value).exists():
raise serializers.ValidationError(
"An invitation with this code already exists"
)
return value
class ManageImportRequestActionSerializer(common_serializers.ActionSerializer):
actions = [
common_serializers.Action(
"mark_closed",
allow_all=True,
qs_filter=lambda qs: qs.filter(status__in=["pending", "accepted"]),
),
common_serializers.Action(
"mark_imported",
allow_all=True,
qs_filter=lambda qs: qs.filter(status__in=["pending", "accepted"]),
),
common_serializers.Action("delete", allow_all=False),
]
filterset_class = filters.ManageImportRequestFilterSet
@transaction.atomic
def handle_delete(self, objects):
return objects.delete()
@transaction.atomic
def handle_mark_closed(self, objects):
return objects.update(status="closed")
@transaction.atomic
def handle_mark_imported(self, objects):
now = timezone.now()
return objects.update(status="imported", imported_date=now)
......@@ -5,6 +5,10 @@ from . import views
library_router = routers.SimpleRouter()
library_router.register(r"track-files", views.ManageTrackFileViewSet, "track-files")
requests_router = routers.SimpleRouter()
requests_router.register(
r"import-requests", views.ManageImportRequestViewSet, "import-requests"
)
users_router = routers.SimpleRouter()
users_router.register(r"users", views.ManageUserViewSet, "users")
users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations")
......@@ -12,4 +16,7 @@ users_router.register(r"invitations", views.ManageInvitationViewSet, "invitation
urlpatterns = [
url(r"^library/", include((library_router.urls, "instance"), namespace="library")),
url(r"^users/", include((users_router.urls, "instance"), namespace="users")),
url(
r"^requests/", include((requests_router.urls, "instance"), namespace="requests")
),
]
......@@ -3,6 +3,7 @@ from rest_framework.decorators import list_route
from funkwhale_api.common import preferences
from funkwhale_api.music import models as music_models
from funkwhale_api.requests import models as requests_models
from funkwhale_api.users import models as users_models
from funkwhale_api.users.permissions import HasUserPermission
......@@ -10,10 +11,7 @@ from . import filters, serializers
class ManageTrackFileViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet,
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
queryset = (
music_models.TrackFile.objects.all()
......@@ -69,7 +67,6 @@ class ManageInvitationViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet,
):
queryset = (
......@@ -96,3 +93,31 @@ class ManageInvitationViewSet(
serializer.is_valid(raise_exception=True)
result = serializer.save()
return response.Response(result, status=200)
class ManageImportRequestViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet,
):
queryset = (
requests_models.ImportRequest.objects.all()
.order_by("-id")
.select_related("user")
)
serializer_class = serializers.ManageImportRequestSerializer
filter_class = filters.ManageImportRequestFilterSet
permission_classes = (HasUserPermission,)
required_permissions = ["library"]
ordering_fields = ["creation_date", "imported_date"]
@list_route(methods=["post"])
def action(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = serializers.ManageImportRequestActionSerializer(
request.data, queryset=queryset
)
serializer.is_valid(raise_exception=True)
result = serializer.save()
return response.Response(result, status=200)
......@@ -539,7 +539,7 @@ class ImportBatch(models.Model):
related_name="import_batches",
null=True,
blank=True,
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
)
class Meta:
......
......@@ -32,7 +32,11 @@ class TestDangerousSerializer(serializers.ActionSerializer):
class TestDeleteOnlyInactiveSerializer(serializers.ActionSerializer):
actions = [serializers.Action("test", allow_all=True, filters={"is_active": False})]
actions = [
serializers.Action(
"test", allow_all=True, qs_filter=lambda qs: qs.filter(is_active=False)
)
]
filterset_class = TestActionFilterSet
def handle_test(self, objects):
......
......@@ -31,3 +31,44 @@ def test_user_update_permission(factories):
assert user.permission_upload is True
assert user.permission_library is False
assert user.permission_settings is True
def test_manage_import_request_mark_closed(factories):
affected = factories["requests.ImportRequest"].create_batch(
size=5, status="pending"
)
# we do not update imported requests
factories["requests.ImportRequest"].create_batch(size=5, status="imported")
s = serializers.ManageImportRequestActionSerializer(
queryset=affected[0].__class__.objects.all(),
data={"objects": "all", "action": "mark_closed"},
)
assert s.is_valid(raise_exception=True) is True
s.save()
assert affected[0].__class__.objects.filter(status="imported").count() == 5
for ir in affected:
ir.refresh_from_db()
assert ir.status == "closed"
def test_manage_import_request_mark_imported(factories, now):
affected = factories["requests.ImportRequest"].create_batch(
size=5, status="pending"
)
# we do not update closed requests
factories["requests.ImportRequest"].create_batch(size=5, status="closed")
s = serializers.ManageImportRequestActionSerializer(
queryset=affected[0].__class__.objects.all(),
data={"objects": "all", "action": "mark_imported"},
)
assert s.is_valid(raise_exception=True) is True
s.save()
assert affected[0].__class__.objects.filter(status="closed").count() == 5
for ir in affected:
ir.refresh_from_db()
assert ir.status == "imported"
assert ir.imported_date == now
......@@ -10,6 +10,7 @@ from funkwhale_api.manage import serializers, views
(views.ManageTrackFileViewSet, ["library"], "and"),
(views.ManageUserViewSet, ["settings"], "and"),
(views.ManageInvitationViewSet, ["settings"], "and"),
(views.ManageImportRequestViewSet, ["library"], "and"),
],
)
def test_permissions(assert_user_permission, view, permissions, operator):
......@@ -63,3 +64,15 @@ def test_invitation_view_create(factories, superuser_api_client, mocker):
assert response.status_code == 201
assert superuser_api_client.user.invitations.latest("id") is not None
def test_music_requests_view(factories, superuser_api_client, mocker):
invitations = factories["requests.ImportRequest"].create_batch(size=5)
qs = invitations[0].__class__.objects.order_by("-id")
url = reverse("api:v1:manage:requests:import-requests-list")
response = superuser_api_client.get(url, {"sort": "-id"})
expected = serializers.ManageImportRequestSerializer(qs, many=True).data
assert response.data["count"] == len(invitations)
assert response.data["results"] == expected
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment