# trilby_api/views/persons.py # # Part of kepi. # Copyright (c) 2018-2021 Marnanel Thurman. # Licensed under the GNU Public License v2. import logging logger = logging.getLogger(name='kepi') from django.db import IntegrityError, transaction from django.shortcuts import render, get_object_or_404 from django.views import View from django.http import HttpResponse, JsonResponse, Http404 from django.contrib.auth.decorators import login_required from django.contrib.auth.models import AnonymousUser from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.datastructures import MultiValueDictKeyError from django.core.exceptions import SuspiciousOperation from django.conf import settings import kepi.trilby_api.models as trilby_models import kepi.trilby_api.utils as trilby_utils from kepi.trilby_api.serializers import * from rest_framework import generics, response, mixins from rest_framework.permissions import IsAuthenticated, \ IsAuthenticatedOrReadOnly from rest_framework.response import Response from rest_framework.renderers import JSONRenderer import kepi.trilby_api.receivers from kepi.bowler_pub.utils import uri_to_url import json import re import random class DoSomethingWithPerson(generics.GenericAPIView): serializer_class = UserSerializer queryset = trilby_models.Person.objects.all() def _do_something_with(self, the_person, request): raise NotImplementedError() def post(self, request, *args, **kwargs): if request.user is None: logger.debug(' -- user not logged in') return error_response(401, 'Not logged in') try: the_person = get_object_or_404( self.get_queryset(), id = int(kwargs['user']), ) except ValueError: return error_response(404, 'Non-decimal ID') result = self._do_something_with(the_person, request) if result is None: result = the_person serializer = UserSerializer( result, context = { 'request': request, }, ) return JsonResponse( serializer.data, status = 200, reason = 'Done', ) class Follow(DoSomethingWithPerson): def _do_something_with(self, the_person, request): try: if the_person.auto_follow: offer = None else: number = random.randint(0, 0xffffffff) offer = uri_to_url(settings.KEPI['FOLLOW_REQUEST_LINK'] % { 'username': request.user.username, 'number': number, }) follow = trilby_models.Follow( follower = request.user.localperson, following = the_person, offer = offer, ) with transaction.atomic(): follow.save( send_signal = True, ) logger.info(' -- follow: %s', follow) logger.debug(' -- offer ID: %s', offer) if the_person.auto_follow: follow_back = trilby_models.Follow( follower = the_person, following = request.user.localperson, offer = None, ) with transaction.atomic(): follow_back.save( send_signal = True, ) logger.info(' -- follow back: %s', follow_back) return the_person except IntegrityError: logger.info(' -- not creating a follow; it already exists') class Unfollow(DoSomethingWithPerson): def _do_something_with(self, the_person, request): try: follow = trilby_models.Follow.objects.get( follower = request.user.localperson, following = the_person, ) logger.info(' -- unfollowing: %s', follow) with transaction.atomic(): follow.delete( send_signal = True, ) return the_person except trilby_models.Follow.DoesNotExist: logger.info(' -- not unfollowing; they weren\'t following '+\ 'in the first place') class UpdateCredentials(generics.GenericAPIView): def patch(self, request, *args, **kwargs): if request.user is None: logger.debug(' -- user not logged in') return error_response(401, 'Not logged in') who = request.user.localperson # The Mastodon spec doesn't say what to do # if the user submits field names which don't # exist! unknown_fields = [] # FIXME: the data in "v" needs cleaning. logger.info('-- updating user: %s', who) for f,v in request.data.items(): logger.info(' -- setting %s = %s', f, v) if f=='discoverable': raise Http404("discoverable is not yet supported") elif f=='bot': who.bot = v elif f=='display_name': who.display_name = v elif f=='note': who.note = v elif f=='avatar': raise Http404("images are not yet supported") elif f=='header': raise Http404("images are not yet supported") elif f=='locked': who.locked = v elif f=='source[privacy]': who.default_visibility = v elif f=='source[sensitive]': who.default_sensitive = v elif f=='source[language]': who.language = v elif f=='fields_attributes': raise Http404("fields are not yet supported") else: logger.info(' -- field does not exist') unknown_fields.append(f) if unknown_fields: logger.info(' -- aborting because of unknown fields') raise Http404(f"some fields do not exist: {unknown_fields}") who.save() logger.info(' -- done.') serializer = UserSerializerWithSource( who, context = { 'request': request, }, ) return JsonResponse( serializer.data, status = 200, reason = 'Done', ) ########################### class VerifyCredentials(generics.GenericAPIView): queryset = TrilbyUser.objects.all() def get(self, request, *args, **kwargs): serializer = UserSerializerWithSource(request.user.localperson) return JsonResponse(serializer.data) ########################### class User(generics.GenericAPIView): queryset = trilby_models.Person.objects.all() def get(self, request, *args, **kwargs): try: whoever = get_object_or_404( self.get_queryset(), id = int(kwargs['user']), ) except ValueError: return error_response(404, 'Non-decimal ID') serializer = UserSerializer(whoever) return JsonResponse(serializer.data) ####################################### class Followers_or_Following(generics.GenericAPIView): serializer_class = UserSerializer queryset = trilby_models.Person.objects.all() def get(self, request, *args, **kwargs): params = request.data if request.user.localperson is None: logger.debug(' -- user not logged in') return error_response(401, 'Not logged in') try: the_person = get_object_or_404( self.get_queryset(), id = int(kwargs['user']), ) except ValueError: return error_response(404, 'Non-decimal ID') queryset = self._get_list_for(the_person) if 'max_id' in params: queryset = queryset.filter( id__le = params['max_id'], ) if 'since_id' in params: queryset = queryset.filter( id__gt = params['since_id'], ) if 'limit' in params: queryset = queryset[:params['limit']] serializer = UserSerializer( queryset, many = True, context = { 'request': request, }, ) return JsonResponse( serializer.data, safe = False, # it's a list status = 200, reason = 'Done', ) class Followers(Followers_or_Following): def _get_list_for(self, the_person): return the_person.followers class Following(Followers_or_Following): def _get_list_for(self, the_person): return the_person.following ########################### class UpdateCredentials(generics.GenericAPIView): def patch(self, request, *args, **kwargs): if request.user is None: logger.debug(' -- user not logged in') return error_response(401, 'Not logged in') who = request.user.localperson # The Mastodon spec doesn't say what to do # if the user submits field names which don't # exist! unknown_fields = [] # FIXME: the data in "v" needs cleaning. logger.info('-- updating user: %s', who) for f,v in request.data.items(): logger.info(' -- setting %s = %s', f, v) if f=='discoverable': raise Http404("discoverable is not yet supported") elif f=='bot': who.bot = v elif f=='display_name': who.display_name = v elif f=='note': who.note = v elif f=='avatar': raise Http404("images are not yet supported") elif f=='header': raise Http404("images are not yet supported") elif f=='locked': who.locked = v elif f=='source[privacy]': who.default_visibility = v elif f=='source[sensitive]': who.default_sensitive = v elif f=='source[language]': who.language = v elif f=='fields_attributes': raise Http404("fields are not yet supported") else: logger.info(' -- field does not exist') unknown_fields.append(f) if unknown_fields: logger.info(' -- aborting because of unknown fields') raise Http404(f"some fields do not exist: {unknown_fields}") who.save() logger.info(' -- done.') serializer = UserSerializerWithSource( who, context = { 'request': request, }, ) return JsonResponse( serializer.data, status = 200, reason = 'Done', )