Skip to content
Snippets Groups Projects
views.py 9.23 KiB
Newer Older
  • Learn to ignore specific revisions
  • from django import forms
    
    from django.core import paginator
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from django.http import HttpResponse
    
    from django.urls import reverse
    
    from rest_framework import exceptions, mixins, response, viewsets
    
    from rest_framework.decorators import action
    
    from funkwhale_api.common import preferences
    
    from funkwhale_api.music import models as music_models
    
    from funkwhale_api.music import utils as music_utils
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from . import activity, authentication, models, renderers, serializers, utils, webfinger
    
    
    
    class FederationMixin(object):
        def dispatch(self, request, *args, **kwargs):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if not preferences.get("federation__enabled"):
    
                return HttpResponse(status=405)
            return super().dispatch(request, *args, **kwargs)
    
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    class SharedViewSet(FederationMixin, viewsets.GenericViewSet):
        permission_classes = []
        authentication_classes = [authentication.SignatureAuthentication]
        renderer_classes = [renderers.ActivityPubRenderer]
    
    
        @action(methods=["post"], detail=False)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def inbox(self, request, *args, **kwargs):
            if request.method.lower() == "post" and request.actor is None:
                raise exceptions.AuthenticationFailed(
                    "You need a valid signature to send an activity"
                )
            if request.method.lower() == "post":
                activity.receive(activity=request.data, on_behalf_of=request.actor)
            return response.Response({}, status=200)
    
    
    
    class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    
        lookup_field = "preferred_username"
    
        authentication_classes = [authentication.SignatureAuthentication]
        permission_classes = []
        renderer_classes = [renderers.ActivityPubRenderer]
        queryset = models.Actor.objects.local().select_related("user")
        serializer_class = serializers.ActorSerializer
    
    
        @action(methods=["get", "post"], detail=True)
    
        def inbox(self, request, *args, **kwargs):
    
            if request.method.lower() == "post" and request.actor is None:
                raise exceptions.AuthenticationFailed(
                    "You need a valid signature to send an activity"
                )
            if request.method.lower() == "post":
    
                activity.receive(activity=request.data, on_behalf_of=request.actor)
    
            return response.Response({}, status=200)
    
    
        @action(methods=["get", "post"], detail=True)
    
        def outbox(self, request, *args, **kwargs):
            return response.Response({}, status=200)
    
    
        @action(methods=["get"], detail=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def followers(self, request, *args, **kwargs):
            self.get_object()
            # XXX to implement
            return response.Response({})
    
    
        @action(methods=["get"], detail=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def following(self, request, *args, **kwargs):
            self.get_object()
            # XXX to implement
            return response.Response({})
    
    
    class EditViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
        lookup_field = "uuid"
        authentication_classes = [authentication.SignatureAuthentication]
        permission_classes = []
        renderer_classes = [renderers.ActivityPubRenderer]
        # queryset = common_models.Mutation.objects.local().select_related()
        # serializer_class = serializers.ActorSerializer
    
    
    
    class WellKnownViewSet(viewsets.GenericViewSet):
    
        authentication_classes = []
        permission_classes = []
    
        renderer_classes = [renderers.JSONRenderer, renderers.WebfingerRenderer]
    
        @action(methods=["get"], detail=False)
    
        def nodeinfo(self, request, *args, **kwargs):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if not preferences.get("instance__nodeinfo_enabled"):
    
                return HttpResponse(status=404)
            data = {
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "links": [
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                        "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
                        "href": utils.full_url(reverse("api:v1:instance:nodeinfo-2.0")),
    
                    }
                ]
            }
            return response.Response(data)
    
    
        @action(methods=["get"], detail=False)
    
        def webfinger(self, request, *args, **kwargs):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            if not preferences.get("federation__enabled"):
    
                return HttpResponse(status=405)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                resource_type, resource = webfinger.clean_resource(request.GET["resource"])
                cleaner = getattr(webfinger, "clean_{}".format(resource_type))
    
                result = cleaner(resource)
    
                handler = getattr(self, "handler_{}".format(resource_type))
                data = handler(result)
    
            except forms.ValidationError as e:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                return response.Response({"errors": {"resource": e.message}}, status=400)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                return response.Response(
                    {"errors": {"resource": "This field is required"}}, status=400
                )
    
            return response.Response(data)
    
    
        def handler_acct(self, clean_result):
            username, hostname = clean_result
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            try:
                actor = models.Actor.objects.local().get(preferred_username=username)
            except models.Actor.DoesNotExist:
                raise forms.ValidationError("Invalid username")
    
            return serializers.ActorWebfingerSerializer(actor).data
    
    def has_library_access(request, library):
        if library.privacy_level == "everyone":
            return True
        if request.user.is_authenticated and request.user.is_superuser:
            return True
    
        try:
            actor = request.actor
        except AttributeError:
            return False
    
        return library.received_follows.filter(actor=actor, approved=True).exists()
    
    
    class MusicLibraryViewSet(
        FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
    ):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        authentication_classes = [authentication.SignatureAuthentication]
    
        permission_classes = []
    
        renderer_classes = [renderers.ActivityPubRenderer]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        serializer_class = serializers.LibrarySerializer
    
        queryset = music_models.Library.objects.all().select_related("actor")
        lookup_field = "uuid"
    
        def retrieve(self, request, *args, **kwargs):
            lb = self.get_object()
    
            conf = {
                "id": lb.get_federation_id(),
                "actor": lb.actor,
                "name": lb.name,
                "summary": lb.description,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "items": lb.uploads.for_federation().order_by("-creation_date"),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "item_serializer": serializers.UploadSerializer,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            page = request.GET.get("page")
    
                serializer = serializers.LibrarySerializer(lb)
    
                data = serializer.data
            else:
    
                # if actor is requesting a specific page, we ensure library is public
                # or readable by the actor
                if not has_library_access(request, lb):
                    raise exceptions.AuthenticationFailed(
                        "You do not have access to this library"
                    )
    
                try:
                    page_number = int(page)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    return response.Response({"page": ["Invalid page number"]}, status=400)
    
                conf["page_size"] = preferences.get("federation__collection_page_size")
                p = paginator.Paginator(conf["items"], conf["page_size"])
    
                try:
                    page = p.page(page_number)
    
                    conf["page"] = page
    
                    serializer = serializers.CollectionPageSerializer(conf)
                    data = serializer.data
    
                except paginator.EmptyPage:
                    return response.Response(status=404)
    
            return response.Response(data)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
        @action(methods=["get"], detail=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        def followers(self, request, *args, **kwargs):
            self.get_object()
            # XXX Implement this
            return response.Response({})
    
    
    class MusicUploadViewSet(
        FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
    ):
        authentication_classes = [authentication.SignatureAuthentication]
        permission_classes = []
        renderer_classes = [renderers.ActivityPubRenderer]
    
        queryset = music_models.Upload.objects.local().select_related(
            "library__actor", "track__artist", "track__album__artist"
        )
        serializer_class = serializers.UploadSerializer
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        lookup_field = "uuid"
    
    
        def get_queryset(self):
            queryset = super().get_queryset()
            actor = music_utils.get_actor_from_request(self.request)
            return queryset.playable_by(actor)
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    class MusicArtistViewSet(
        FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
    ):
        authentication_classes = [authentication.SignatureAuthentication]
        permission_classes = []
        renderer_classes = [renderers.ActivityPubRenderer]
    
        queryset = music_models.Artist.objects.local()
        serializer_class = serializers.ArtistSerializer
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        lookup_field = "uuid"
    
    
    class MusicAlbumViewSet(
        FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
    ):
        authentication_classes = [authentication.SignatureAuthentication]
        permission_classes = []
        renderer_classes = [renderers.ActivityPubRenderer]
    
        queryset = music_models.Album.objects.local().select_related("artist")
        serializer_class = serializers.AlbumSerializer
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        lookup_field = "uuid"
    
    
    class MusicTrackViewSet(
        FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
    ):
        authentication_classes = [authentication.SignatureAuthentication]
        permission_classes = []
        renderer_classes = [renderers.ActivityPubRenderer]
    
        queryset = music_models.Track.objects.local().select_related(
            "album__artist", "artist"
        )
        serializer_class = serializers.TrackSerializer
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        lookup_field = "uuid"