Skip to content
Snippets Groups Projects
views.py 10.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • from django import forms
    from django.conf import settings
    
    from django.core import paginator
    
    from django.http import HttpResponse
    
    from django.urls import reverse
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from rest_framework import mixins
    
    from rest_framework import permissions as rest_permissions
    
    from rest_framework import response
    
    from rest_framework import views
    from rest_framework import viewsets
    
    from rest_framework.decorators import list_route, detail_route
    
    from rest_framework.serializers import ValidationError
    
    from funkwhale_api.common import preferences
    
    from funkwhale_api.common import utils as funkwhale_utils
    
    from funkwhale_api.music.models import TrackFile
    
    from funkwhale_api.users.permissions import HasUserPermission
    
    from . import actors
    
    from . import authentication
    
    from . import library
    from . import models
    
    from . import permissions
    
    from . import renderers
    
    from . import serializers
    
    from . import utils
    
    from . import webfinger
    
    
    class FederationMixin(object):
        def dispatch(self, request, *args, **kwargs):
    
            if not preferences.get('federation__enabled'):
    
                return HttpResponse(status=405)
            return super().dispatch(request, *args, **kwargs)
    
    
    
    class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
        lookup_field = 'actor'
        lookup_value_regex = '[a-z]*'
    
        authentication_classes = [
            authentication.SignatureAuthentication]
    
        permission_classes = []
    
        renderer_classes = [renderers.ActivityPubRenderer]
    
        def get_object(self):
            try:
                return actors.SYSTEM_ACTORS[self.kwargs['actor']]
            except KeyError:
                raise Http404
    
        def retrieve(self, request, *args, **kwargs):
    
            system_actor = self.get_object()
            actor = system_actor.get_actor_instance()
    
            data = actor.system_conf.serialize()
            return response.Response(data, status=200)
    
        @detail_route(methods=['get', 'post'])
    
        def inbox(self, request, *args, **kwargs):
    
            system_actor = self.get_object()
            handler = getattr(system_actor, '{}_inbox'.format(
                request.method.lower()
            ))
    
            try:
                data = handler(request.data, actor=request.actor)
            except NotImplementedError:
                return response.Response(status=405)
    
            return response.Response({}, status=200)
    
    
        @detail_route(methods=['get', 'post'])
    
        def outbox(self, request, *args, **kwargs):
    
            system_actor = self.get_object()
            handler = getattr(system_actor, '{}_outbox'.format(
                request.method.lower()
            ))
            try:
                data = handler(request.data, actor=request.actor)
            except NotImplementedError:
                return response.Response(status=405)
    
            return response.Response({}, status=200)
    
    class WellKnownViewSet(viewsets.GenericViewSet):
    
        authentication_classes = []
        permission_classes = []
    
        renderer_classes = [renderers.JSONRenderer, renderers.WebfingerRenderer]
    
        @list_route(methods=['get'])
        def nodeinfo(self, request, *args, **kwargs):
            if not preferences.get('instance__nodeinfo_enabled'):
                return HttpResponse(status=404)
            data = {
                'links': [
                    {
                        'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
                        'href': utils.full_url(
                            reverse('api:v1:instance:nodeinfo-2.0')
                        )
                    }
                ]
            }
            return response.Response(data)
    
    
        @list_route(methods=['get'])
        def webfinger(self, request, *args, **kwargs):
    
            if not preferences.get('federation__enabled'):
                return HttpResponse(status=405)
    
            try:
                resource_type, resource = webfinger.clean_resource(
                    request.GET['resource'])
                cleaner = getattr(webfinger, 'clean_{}'.format(resource_type))
                result = cleaner(resource)
            except forms.ValidationError as e:
                return response.Response({
                    'errors': {
                        'resource': e.message
                    }
                }, status=400)
            except KeyError:
                return response.Response({
                    'errors': {
                        'resource': 'This field is required',
                    }
                }, status=400)
    
            handler = getattr(self, 'handler_{}'.format(resource_type))
            data = handler(result)
    
    
            return response.Response(data)
    
    
        def handler_acct(self, clean_result):
            username, hostname = clean_result
    
            actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
    
            return serializers.ActorWebfingerSerializer(actor).data
    
    
    
    class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
        authentication_classes = [
            authentication.SignatureAuthentication]
        permission_classes = [permissions.LibraryFollower]
        renderer_classes = [renderers.ActivityPubRenderer]
    
        def list(self, request, *args, **kwargs):
            page = request.GET.get('page')
            library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
    
            qs = TrackFile.objects.order_by('-creation_date').select_related(
                'track__artist',
                'track__album__artist'
    
            ).filter(library_track__isnull=True)
    
            if page is None:
                conf = {
                    'id': utils.full_url(reverse('federation:music:files-list')),
    
                    'page_size': preferences.get(
                        'federation__collection_page_size'),
    
                    'item_serializer': serializers.AudioSerializer,
    
                    'actor': library,
                }
                serializer = serializers.PaginatedCollectionSerializer(conf)
                data = serializer.data
            else:
                try:
                    page_number = int(page)
                except:
                    return response.Response(
                        {'page': ['Invalid page number']}, status=400)
                p = paginator.Paginator(
    
                    qs, preferences.get('federation__collection_page_size'))
    
                try:
                    page = p.page(page_number)
    
                    conf = {
                        'id': utils.full_url(reverse('federation:music:files-list')),
                        'page': page,
                        'item_serializer': serializers.AudioSerializer,
                        'actor': library,
                    }
                    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
    class LibraryViewSet(
    
            mixins.RetrieveModelMixin,
            mixins.UpdateModelMixin,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            mixins.ListModelMixin,
            viewsets.GenericViewSet):
    
        permission_classes = (HasUserPermission,)
        required_permissions = ['federation']
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        queryset = models.Library.objects.all().select_related(
            'actor',
            'follow',
        )
    
        lookup_field = 'uuid'
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        filter_class = filters.LibraryFilter
        serializer_class = serializers.APILibrarySerializer
        ordering_fields = (
            'id',
            'creation_date',
            'fetched_date',
            'actor__domain',
    
            'tracks_count',
    
    
        @list_route(methods=['get'])
    
        def fetch(self, request, *args, **kwargs):
    
            account = request.GET.get('account')
            if not account:
                return response.Response(
                    {'account': 'This field is mandatory'}, status=400)
    
            data = library.scan_from_account_name(account)
            return response.Response(data)
    
        @detail_route(methods=['post'])
        def scan(self, request, *args, **kwargs):
            library = self.get_object()
            serializer = serializers.APILibraryScanSerializer(
                data=request.data
            )
            serializer.is_valid(raise_exception=True)
    
            result = tasks.scan_library.delay(
    
                library_id=library.pk,
    
                until=serializer.validated_data.get('until')
    
            return response.Response({'task': result.id})
    
        @list_route(methods=['get'])
        def following(self, request, *args, **kwargs):
            library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
            queryset = models.Follow.objects.filter(
                actor=library_actor
            ).select_related(
    
                'target',
            ).order_by('-creation_date')
            filterset = filters.FollowFilter(request.GET, queryset=queryset)
    
            final_qs = filterset.qs
            serializer = serializers.APIFollowSerializer(final_qs, many=True)
    
            data = {
                'results': serializer.data,
    
                'count': len(final_qs),
    
        @list_route(methods=['get', 'patch'])
    
        def followers(self, request, *args, **kwargs):
    
            if request.method.lower() == 'patch':
                serializer = serializers.APILibraryFollowUpdateSerializer(
                    data=request.data)
                serializer.is_valid(raise_exception=True)
                follow = serializer.save()
                return response.Response(
                    serializers.APIFollowSerializer(follow).data
                )
    
    
            library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
            queryset = models.Follow.objects.filter(
                target=library_actor
            ).select_related(
    
                'target',
            ).order_by('-creation_date')
            filterset = filters.FollowFilter(request.GET, queryset=queryset)
    
            final_qs = filterset.qs
            serializer = serializers.APIFollowSerializer(final_qs, many=True)
    
            data = {
                'results': serializer.data,
    
                'count': len(final_qs),
    
        @transaction.atomic
        def create(self, request, *args, **kwargs):
    
            serializer = serializers.APILibraryCreateSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            library = serializer.save()
            return response.Response(serializer.data, status=201)
    
    
    
    class LibraryTrackViewSet(
            mixins.ListModelMixin,
            viewsets.GenericViewSet):
    
        permission_classes = (HasUserPermission,)
        required_permissions = ['federation']
    
        queryset = models.LibraryTrack.objects.all().select_related(
            'library__actor',
            'library__follow',
            'local_track_file',
        )
        filter_class = filters.LibraryTrackFilter
        serializer_class = serializers.APILibraryTrackSerializer
        ordering_fields = (
            'id',
            'artist_name',
            'title',
            'album_title',
            'creation_date',
            'modification_date',
            'fetched_date',
            'published_date',
        )