From deb6075986bd674ea0a3b0ba961668e32f03c060 Mon Sep 17 00:00:00 2001 From: Marnanel Thurman Date: Tue, 14 May 2019 22:48:18 +0100 Subject: [PATCH] Merged CollectionView into KepiView. Split off get() from activity() because get() is usually the same for all KepiViews. --- django_kepi/views.py | 145 +++++++++++++++++++++++------------ tests/test_views.py | 32 +++++--- things_for_testing/models.py | 1 - things_for_testing/views.py | 25 +++--- 4 files changed, 130 insertions(+), 73 deletions(-) diff --git a/django_kepi/views.py b/django_kepi/views.py index 0d2ac34..f85a481 100644 --- a/django_kepi/views.py +++ b/django_kepi/views.py @@ -7,6 +7,7 @@ from django.contrib.auth.decorators import login_required from django.core.exceptions import ValidationError from django.conf import settings from django_kepi.models import Activity +from collections.abc import Iterable import logging import urllib.parse import json @@ -26,56 +27,61 @@ class KepiView(django.views.View): self.http_method_names.append('activity') -class ActivityObjectView(KepiView): - def activity(self, request, *args, **kwargs): + """ + Returns this view in a form suitable for ActivityPub. - try: - activity_object = Activity.objects.get( - uuid=kwargs['id'], - ) - except Activity.DoesNotExist: - logger.info('unknown: %s', kwargs['id']) - return None - except django.core.exceptions.ValidationError: - logger.info('invalid: %s', kwargs['id']) - return None + It may return: + - a dict, suitable for turning into JSON + - an iterable, such as a list or QuerySet; + this will be turned into an OrderedCollection. + Every member will be passed through + _modify_list_item before rendering. + - anything with a property called "activity_form"; + this value should be either an iterable or a dict, + as above. - result = activity_object.activity_form - logger.debug('found object: %s', str(result)) + This method is usually called by a KepiView's get() handler. + By default, its args and kwargs will be passed in unchanged. - return result + It's also used to retrieve the ActivityPub form within + the rest of the program, rather than as a response to + a particular HTTP request. In that case, "request" will + not be a real HttpRequest. (XXX so, what *will* it be?) + + Override this method in your subclass. In KepiView + it's abstract. + """ + raise NotImplementedError("implement activity() in a subclass") def get(self, request, *args, **kwargs): + """ + Returns a rendered HttpResult for a GET request. + By default, KepiViews call self.activity() to get + the data to render. + """ result = self.activity(request, *args, **kwargs) - return self._render(result) - def _make_query_page( - self, - request, - page_number, - ): - - fields = dict(request.GET) + logger.debug('About to render object: %s', + result) - if page_number is None: - if PAGE_FIELD in fields: - del fields[PAGE_FIELD] - else: - fields[PAGE_FIELD] = page_number + while True: + if isinstance(result, dict): + logger.debug(' -- it\'s a dict') + return self._render(result) - encoded = urllib.parse.urlencode(fields) + if isinstance(result, Iterable): + logger.debug(' -- it\'s an iterable') + return self._collection_get(result) - if encoded!='': - encoded = '?'+encoded - - return '{}://{}{}{}'.format( - request.scheme, - request.get_host(), - request.path, - encoded, - ) + try: + result = result.activity_form + logger.debug(' -- it has an activity_form, %s; recurring', + result) + except AttributeError: + result.warn("I don't know how to render objects like %s.", result) + raise ValueError("I don't know how to render objects like %s." % (result,)) def _render(self, data): result = JsonResponse( @@ -90,11 +96,8 @@ class ActivityObjectView(KepiView): return result -class CollectionView(ActivityObjectView): + def _collection_get(self, request, items): - def get(self, request, *args, **kwargs): - - items = self.get_collection_items(*args, **kwargs) # XXX assert that items.ordered our_url = request.build_absolute_uri() @@ -113,7 +116,7 @@ class CollectionView(ActivityObjectView): "type" : "OrderedCollectionPage", "id" : our_url, "totalItems" : items.count(), - "orderedItems" : [self._stringify_object(x) + "orderedItems" : [self._modify_list_item(x) for x in listed_items], "partOf": index_url, } @@ -140,26 +143,68 @@ class CollectionView(ActivityObjectView): return self._render(result) - def get_collection_items(self, *args, **kwargs): - return kwargs['items'] + def _make_query_page( + self, + request, + page_number, + ): + fields = dict(request.GET) - def _stringify_object(self, obj): + if page_number is None: + if PAGE_FIELD in fields: + del fields[PAGE_FIELD] + else: + fields[PAGE_FIELD] = page_number + + encoded = urllib.parse.urlencode(fields) + + if encoded!='': + encoded = '?'+encoded + + return '{}://{}{}{}'.format( + request.scheme, + request.get_host(), + request.path, + encoded, + ) + + def _modify_list_item(self, obj): return str(obj) -class FollowingView(CollectionView): +class ActivityObjectView(KepiView): + + def activity(self, request, *args, **kwargs): + + try: + activity_object = Activity.objects.get( + uuid=kwargs['id'], + ) + except Activity.DoesNotExist: + logger.info('unknown: %s', kwargs['id']) + return None + except django.core.exceptions.ValidationError: + logger.info('invalid: %s', kwargs['id']) + return None + + result = activity_object.activity_form + logger.debug('found object: %s', str(result)) + + return result + +class FollowingView(KepiView): def get_collection_items(self, *args, **kwargs): return Following.objects.filter(follower__url=kwargs['url']) - def _stringify_object(self, obj): + def _modify_list_item(self, obj): return obj.following.url -class FollowersView(CollectionView): +class FollowersView(KepiView): def get_collection_items(self, *args, **kwargs): return Following.objects.filter(following__url=kwargs['url']) - def _stringify_object(self, obj): + def _modify_list_item(self, obj): return obj.follower.url ######################################## diff --git a/tests/test_views.py b/tests/test_views.py index 1f5e1a5..bd458f9 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,21 +1,31 @@ -import django_kepi.activity_model +from django_kepi.activity_model import Activity from django.test import TestCase, Client +from things_for_testing.models import ThingUser import logging +import json logger = logging.getLogger(name='django_kepi') -class TestActivityObjectView(TestCase): +class TestSingleKepiView(TestCase): - def test_activity_object_view(self): + def test_single_kepi_view(self): - a = django_kepi.activity_model.Activity( - f_actor = 'https://example.net/users/fred', - f_object = 'https://example.net/articles/i-like-jam', - f_type = 'L', - identifier = 'https://example.com/obj/1234', + alice = ThingUser( + name = 'alice', + favourite_colour = 'magenta', ) - a.save() + alice.save() c = Client() - result = c.get('/obj/1234') - logger.info('Content: %s', result.content) + response = c.get('/users/alice') + logger.info('Response: %s', response.content.decode('utf-8')) + result = json.loads(response.content.decode('utf-8')) + + self.assertDictEqual( + result, + { + 'name': 'alice', + 'favourite_colour': 'magenta', + 'id': 'https://altair.example.com/users/alice', + }, + ) diff --git a/things_for_testing/models.py b/things_for_testing/models.py index 2829511..f1e0d49 100644 --- a/things_for_testing/models.py +++ b/things_for_testing/models.py @@ -38,7 +38,6 @@ class ThingUser(models.Model): return { 'id': self.url, - 'type': self.implements_activity_type, 'name': self.name, 'favourite_colour': self.favourite_colour, } diff --git a/things_for_testing/views.py b/things_for_testing/views.py index c0f5d0f..2cc98de 100644 --- a/things_for_testing/views.py +++ b/things_for_testing/views.py @@ -3,42 +3,45 @@ from django_kepi.views import * from django_kepi.models import * from things_for_testing.models import * +logger = logging.getLogger(name='things_for_testing') + class ThingUserView(KepiView): + def activity(self, *args, **kwargs): + logger.debug('ThingUserView.activity: %s', kwargs['name']) try: return ThingUser.objects.get(name=kwargs['name']) except ThingUser.DoesNotExist: + logger.debug(' -- does not exist') return None -class ThingUserCollection(CollectionView): - def get_collection_items(self, *args, **kwargs): +class ThingUserCollection(KepiView): + def activity(self, *args, **kwargs): return ThingUser.objects.all() - def _stringify_object(self, obj): + def _modify_list_item(self, obj): return obj.activity_form -class ThingUserFollowingView(CollectionView): +class ThingUserFollowingView(KepiView): - def get_collection_items(self, *args, **kwargs): + def activity(self, *args, **kwargs): return Activity.objects.filter( f_type='Follow', f_actor=ThingUser(name=kwargs['name']).activity_id, accepted=True, ) - def _stringify_object(self, obj): + def _modify_list_item(self, obj): return obj.f_object -class ThingUserFollowersView(CollectionView): +class ThingUserFollowersView(KepiView): - def get_collection_items(self, *args, **kwargs): + def activity(self, *args, **kwargs): return Activity.objects.filter( f_type='Follow', f_object=ThingUser(name=kwargs['name']).activity_id, accepted=True, ) - def _stringify_object(self, obj): + def _modify_list_item(self, obj): return obj.f_actor - -