diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py index 2f2bde838..e4cda18c5 100644 --- a/api/funkwhale_api/manage/filters.py +++ b/api/funkwhale_api/manage/filters.py @@ -3,6 +3,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.users import models as users_models class ManageTrackFileFilterSet(filters.FilterSet): @@ -18,3 +19,21 @@ class ManageTrackFileFilterSet(filters.FilterSet): class Meta: model = music_models.TrackFile fields = ["q", "track__album", "track__artist", "track", "library_track"] + + +class ManageUserFilterSet(filters.FilterSet): + q = fields.SearchFilter(search_fields=["username", "email", "name"]) + + class Meta: + model = users_models.User + fields = [ + "q", + "is_active", + "privacy_level", + "is_staff", + "is_superuser", + "permission_upload", + "permission_library", + "permission_settings", + "permission_federation", + ] diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index 1c94cf553..13f886a7e 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -3,6 +3,7 @@ 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.users import models as users_models from . import filters @@ -67,3 +68,34 @@ class ManageTrackFileActionSerializer(common_serializers.ActionSerializer): @transaction.atomic def handle_delete(self, objects): return objects.delete() + + +class ManageUserSerializer(serializers.ModelSerializer): + permissions = serializers.SerializerMethodField() + + class Meta: + model = users_models.User + fields = ( + "id", + "username", + "email", + "name", + "is_active", + "is_staff", + "is_superuser", + "date_joined", + "last_activity", + "permissions", + "privacy_level", + ) + read_only_fields = [ + "id", + "email", + "privacy_level", + "username", + "date_joined", + "last_activity", + ] + + def get_permissions(self, o): + return o.get_permissions(defaults=self.context.get("default_permissions")) diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py index 60853034f..f208fb857 100644 --- a/api/funkwhale_api/manage/urls.py +++ b/api/funkwhale_api/manage/urls.py @@ -5,7 +5,10 @@ from . import views library_router = routers.SimpleRouter() library_router.register(r"track-files", views.ManageTrackFileViewSet, "track-files") +users_router = routers.SimpleRouter() +users_router.register(r"users", views.ManageUserViewSet, "users") urlpatterns = [ - url(r"^library/", include((library_router.urls, "instance"), namespace="library")) + url(r"^library/", include((library_router.urls, "instance"), namespace="library")), + url(r"^users/", include((users_router.urls, "instance"), namespace="users")), ] diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index 8511732c9..f9b78ef87 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -1,7 +1,9 @@ from rest_framework import mixins, response, viewsets 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.users import models as users_models from funkwhale_api.users.permissions import HasUserPermission from . import filters, serializers @@ -41,3 +43,22 @@ class ManageTrackFileViewSet( serializer.is_valid(raise_exception=True) result = serializer.save() return response.Response(result, status=200) + + +class ManageUserViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + viewsets.GenericViewSet, +): + queryset = users_models.User.objects.all().order_by("-id") + serializer_class = serializers.ManageUserSerializer + filter_class = filters.ManageUserFilterSet + permission_classes = (HasUserPermission,) + required_permissions = ["settings"] + ordering_fields = ["date_joined", "last_activity", "username"] + + def get_serializer_context(self): + context = super().get_serializer_context() + context["default_permissions"] = preferences.get("users__default_permissions") + return context diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py index d37656c11..055a971b3 100644 --- a/api/funkwhale_api/users/models.py +++ b/api/funkwhale_api/users/models.py @@ -82,8 +82,8 @@ class User(AbstractUser): def __str__(self): return self.username - def get_permissions(self): - defaults = preferences.get("users__default_permissions") + def get_permissions(self, defaults=None): + defaults = defaults or preferences.get("users__default_permissions") perms = {} for p in PERMISSIONS: v = ( diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index e2bfbf3a8..a72bcf5af 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -5,7 +5,11 @@ from funkwhale_api.manage import serializers, views @pytest.mark.parametrize( - "view,permissions,operator", [(views.ManageTrackFileViewSet, ["library"], "and")] + "view,permissions,operator", + [ + (views.ManageTrackFileViewSet, ["library"], "and"), + (views.ManageUserViewSet, ["settings"], "and"), + ], ) def test_permissions(assert_user_permission, view, permissions, operator): assert_user_permission(view, permissions, operator) @@ -23,3 +27,18 @@ def test_track_file_view(factories, superuser_api_client): assert response.data["count"] == len(tfs) assert response.data["results"] == expected + + +def test_user_view(factories, superuser_api_client, mocker): + mocker.patch("funkwhale_api.users.models.User.record_activity") + users = factories["users.User"].create_batch(size=5) + [superuser_api_client.user] + qs = users[0].__class__.objects.order_by("-id") + url = reverse("api:v1:manage:users:users-list") + + response = superuser_api_client.get(url, {"sort": "-id"}) + expected = serializers.ManageUserSerializer( + qs, many=True, context={"request": response.wsgi_request} + ).data + + assert response.data["count"] == len(users) + assert response.data["results"] == expected