bowler's Collection views refactored. It feels a bit like being the redactor of the book of Genesis.

The refactoring is more complicated than it sounds. The file activitypub.py
was created by merging two sets of views from the old bowler_heavy
branch. This resulted in two classes called InboxView, and of course
the second one in the file won out. I have merged them and fixed
the errors therein.

As part of this, I have discovered that d-r-f adds the requirement
that view methods return HttpResponse objects. Therefore we've had
to add a new class, ActivityResponse, whose only purpose is to
get around this requirement.

fetch() has been updated as appropriate.
status-serialisers
Marnanel Thurman 2020-09-16 23:14:38 +01:00
rodzic c586eecfcb
commit d02042f247
3 zmienionych plików z 75 dodań i 78 usunięć

Wyświetl plik

@ -0,0 +1,30 @@
# activityresponse.py
#
# Part of kepi.
# Copyright (c) 2018-2020 Marnanel Thurman.
# Licensed under the GNU Public License v2.
from django.http import HttpResponse
import logging
logger = logging.getLogger(name="kepi")
class ActivityResponse(HttpResponse):
"""
This is a workaround.
Methods in ordinary Django views can return anything at all,
but d-r-f adds an assertion that they return HttpResponse objects.
We need the activity_get() methods of our views to return the
object in question. So, if the view is based on a d-r-f class,
we must wrap the result with this class.
"""
def __init__(self, activity_value):
super().__init__()
self.activity_value = None
def __str__(self):
return '<ActivityResponse, wrapping %s>' % (self.activity_value,)

Wyświetl plik

@ -10,6 +10,7 @@ from kepi.bowler_pub.utils import *
from django.shortcuts import render, get_object_or_404
import django.views
from django.http import HttpResponse, JsonResponse, Http404
from kepi.bowler_pub.activityresponse import ActivityResponse
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.conf import settings
@ -72,6 +73,9 @@ class KepiView(django.views.View):
logger.debug(' -- activity_get returned %s (%s)',
result, type(result))
if isinstance(result, ActivityResponse):
result = result.activity_value
if isinstance(result, HttpResponse):
logger.info('self.activity_get() returned HttpResponse %s',
result)
@ -245,78 +249,6 @@ class AllUsersView(KepiView):
########################################
# TODO: UserCollectionView is obsolescent
class UserCollectionView(KepiView):
_default_to_existing = False
def activity_get(self, request,
username,
listname,
*args, **kwargs):
from kepi.trilby_api.models import LocalPerson, Status
logger.debug('Finding user %s\'s %s collection',
username, listname)
result = None
try:
user = LocalPerson.objects.get(local_user__username = username)
except LocalPerson.DoesNotExist:
logger.debug(' -- user does not exist')
user = None
if user is not None:
method_name = f'get_{listname}_collection'
if hasattr(user, method_name):
method = getattr(user, method_name)
result = method()
else:
logger.warn(
"user does not have a %s method; this is weird",
method_name,
)
if result is None:
if self._default_to_existing:
logger.debug(' -- does not exist; returning empty')
return Status.objects.none()
else:
logger.debug(' -- does not exist; 404')
raise Http404()
return result
def _modify_list_item(self, obj):
return obj
class InboxView(UserCollectionView):
_default_to_existing = True
# username can be None for the shared inbox.
# FIXME: Only externally visible to the owner
def activity_get(self, request, username=None, *args, **kwargs):
if username is None:
logger.info('Attempt to read from the shared inbox')
return HttpResponse(
status = 403,
reason = 'The shared inbox is write-only',
)
return super().activity_get(
request,
username = username,
listname = 'inbox',
)
class CollectionView(generics.GenericAPIView):
permission_classes = ()
@ -325,12 +257,15 @@ class CollectionView(generics.GenericAPIView):
listname = None
default_to_existing = True
def _modify_list_item(self, obj):
return obj
def get(self, request,
username,
listname = None,
*args, **kwargs):
items = self._get_items(username, listname,
items = self.activity_get(username, listname,
*args, **kwargs)
# XXX assert that items.ordered
@ -395,13 +330,15 @@ class CollectionView(generics.GenericAPIView):
return self._to_httpresponse(result)
def _get_items(self,
username,
listname = None,
def activity_get(self,
request,
*args, **kwargs):
from kepi.trilby_api.models import LocalPerson, Status
username = kwargs.get('username', None)
listname = kwargs.get('listname')
logger.debug('Finding user %s\'s %s collection',
username, self.listname)
@ -437,13 +374,13 @@ class CollectionView(generics.GenericAPIView):
if self.default_to_existing:
logger.debug(' -- does not exist; returning empty')
return Status.objects.none()
return ActivityResponse(Status.objects.none())
else:
logger.debug(' -- does not exist; 404')
raise Http404()
return result
return ActivityResponse(result)
def _to_httpresponse(self, data):
@ -507,3 +444,27 @@ class OutboxView(CollectionView):
class InboxView(CollectionView):
listname = 'inbox'
# FIXME: Only externally visible to the owner
def activity_get(self, request, username=None, *args, **kwargs):
if username is None:
logger.info('Attempt to read from the shared inbox')
return HttpResponse(
status = 403,
reason = 'The shared inbox is write-only',
)
return super().activity_get(
request,
username = username,
listname = 'inbox',
)
def post(self,
*args, **kwargs):
return HttpResponse(
status = 410,
reason = 'See https://gitlab.com/marnanel/kepi/-/issues/37',
)

Wyświetl plik

@ -14,8 +14,10 @@ from django.conf import settings
from urllib.parse import urlparse
from kepi.trilby_api.models import *
from kepi.bowler_pub.utils import log_one_message
from kepi.bowler_pub.activityresponse import ActivityResponse
from kepi.sombrero_sendpub.webfinger import get_webfinger
import kepi.sombrero_sendpub.collections as sombrero_collections
from django.http import HttpResponse, JsonResponse, Http404
def fetch(address,
expected_type,
@ -152,11 +154,15 @@ def _fetch_local_by_url(address, wanted):
path=wanted['path'],
)
result = resolved.func(request,
*resolved.args,
**resolved.kwargs)
logger.info("%s: result from handler was %s",
address, result)
if isinstance(result, ActivityResponse):
result = result.activity_value
if result is not None and not isinstance(result, wanted['type']):
logger.info("%s: type mismatch (%s vs %s); discarding",
address, type(result), wanted['type'],