See #190: API and serializers to manage import requests
This commit is contained in:
parent
a443f9431e
commit
9767c8f415
|
@ -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"]
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue