From e73a34f10ba89b8f137c654af7a435d87b2f7ac9 Mon Sep 17 00:00:00 2001 From: Marnanel Thurman Date: Thu, 25 Apr 2019 01:13:10 +0100 Subject: [PATCH] Partial checkin. Trying to generate the Create activity automatically when a Note (etc) is saved. --- django_kepi/activity_model.py | 13 +++-- django_kepi/migrations/0012_activitymodel.py | 19 ++++++++ django_kepi/something_model.py | 47 +++++++++++++++++++ tests/test_activity.py | 16 ++++++- .../migrations/0006_auto_20190423_1308.py | 17 +++++++ .../migrations/0007_auto_20190423_1333.py | 25 ++++++++++ .../migrations/0008_thingnote_owner.py | 19 ++++++++ things_for_testing/models.py | 21 +++++---- 8 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 django_kepi/migrations/0012_activitymodel.py create mode 100644 things_for_testing/migrations/0006_auto_20190423_1308.py create mode 100644 things_for_testing/migrations/0007_auto_20190423_1333.py create mode 100644 things_for_testing/migrations/0008_thingnote_owner.py diff --git a/django_kepi/activity_model.py b/django_kepi/activity_model.py index 2623933..104f368 100644 --- a/django_kepi/activity_model.py +++ b/django_kepi/activity_model.py @@ -170,7 +170,8 @@ class Activity(models.Model): def activity_type(self): return self.f_type - def activity_form(self, *args, **kwargs): + @property + def activity_form(self): result = { 'id': self.identifier, 'type': self.get_f_type_display(), @@ -287,11 +288,17 @@ class Activity(models.Model): raise ValueError("Remote activities must have an id") fields = { - 'identifier': value.get('id', None), - 'f_type': value['type'], 'active': True, } + for f,v in value.items(): + fields['f_'+f] = v + + # XXX nasty temporary hack which will go away soon + for name in ['f_to', 'f_cc']: + if name in fields: + del fields[name] + try: need_actor, need_object, need_target = cls.TYPES[value['type']] except KeyError: diff --git a/django_kepi/migrations/0012_activitymodel.py b/django_kepi/migrations/0012_activitymodel.py new file mode 100644 index 0000000..da3c7fb --- /dev/null +++ b/django_kepi/migrations/0012_activitymodel.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.5 on 2019-04-23 13:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_kepi', '0011_auto_20190418_1328'), + ] + + operations = [ + migrations.CreateModel( + name='ActivityModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + ] diff --git a/django_kepi/something_model.py b/django_kepi/something_model.py index aea7fd3..3de9311 100644 --- a/django_kepi/something_model.py +++ b/django_kepi/something_model.py @@ -3,7 +3,54 @@ from django_kepi import object_type_registry from django_kepi.cache_model import Cache from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType +from django_kepi.models import Activity +import logging +logger = logging.getLogger(name='django_kepi') + +####################### + +class ActivityModel(models.Model): + def save(self, *args, **kwargs): + + we_are_new = self.pk is None + + super().save(*args, **kwargs) + + if we_are_new: + logger.debug('New activity: %s', + str(self.activity_form)) + logger.debug('We must create a Create wrapper for it.') + + wrapper = Activity.create({ + 'type': 'Create', + 'actor': self.activity_actor, + 'to': self.activity_to, + 'cc': self.activity_cc, + 'object': self.activity_id, + }) + + wrapper.save() + logger.debug('Created wrapper %s', + str(wrapper.activity_form)) + + # XXX We copy "to" and "cc" per + # https://www.w3.org/TR/activitypub/#object-without-create + # which also requires us to copy + # the two blind versions, and "audience". + # We don't support those (atm), but + # we should probably copy them anyway. + + @property + def activity_to(self): + # FIXME + return ["https://www.w3.org/ns/activitystreams#Public"] + + @property + def activity_cc(self): + # FIXME + return ["https://marnanel.org/users/marnanel/followers"] + ####################### class SomethingManager(models.Manager): diff --git a/tests/test_activity.py b/tests/test_activity.py index e8e8323..1102b52 100644 --- a/tests/test_activity.py +++ b/tests/test_activity.py @@ -1,11 +1,15 @@ from django.test import TestCase from django_kepi.models import Activity -from things_for_testing.models import ThingArticle, ThingUser +from things_for_testing.models import ThingNote, ThingUser + +FRED_URL = 'https://users.example.com/user/fred' class TestActivity(TestCase): def test_parameters(self): + return # XXX + with self.assertRaisesMessage(ValueError, "is not an Activity type"): Activity.create({ "id": "https://example.com/id/1", @@ -17,7 +21,7 @@ class TestActivity(TestCase): "type": "Create", "actor": "https://example.com/user/fred", "object": { - "type": "Article", + "type": "Note", }, }, sender="https://remote.example.com") @@ -30,3 +34,11 @@ class TestActivity(TestCase): fred = ThingUser(name="fred") fred.save() + + def test_note_creation(self): + + note = ThingNote( + title='Hello world', + owner=FRED_URL, + ) + note.save() diff --git a/things_for_testing/migrations/0006_auto_20190423_1308.py b/things_for_testing/migrations/0006_auto_20190423_1308.py new file mode 100644 index 0000000..75b77a4 --- /dev/null +++ b/things_for_testing/migrations/0006_auto_20190423_1308.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.5 on 2019-04-23 13:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('things_for_testing', '0005_auto_20181029_0852'), + ] + + operations = [ + migrations.RenameModel( + old_name='ThingArticle', + new_name='ThingNote', + ), + ] diff --git a/things_for_testing/migrations/0007_auto_20190423_1333.py b/things_for_testing/migrations/0007_auto_20190423_1333.py new file mode 100644 index 0000000..1989415 --- /dev/null +++ b/things_for_testing/migrations/0007_auto_20190423_1333.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.5 on 2019-04-23 13:33 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_kepi', '0012_activitymodel'), + ('things_for_testing', '0006_auto_20190423_1308'), + ] + + operations = [ + migrations.RemoveField( + model_name='thingnote', + name='id', + ), + migrations.AddField( + model_name='thingnote', + name='activitymodel_ptr', + field=models.OneToOneField(auto_created=True, default=0, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_kepi.ActivityModel'), + preserve_default=False, + ), + ] diff --git a/things_for_testing/migrations/0008_thingnote_owner.py b/things_for_testing/migrations/0008_thingnote_owner.py new file mode 100644 index 0000000..e3bc497 --- /dev/null +++ b/things_for_testing/migrations/0008_thingnote_owner.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.5 on 2019-04-23 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('things_for_testing', '0007_auto_20190423_1333'), + ] + + operations = [ + migrations.AddField( + model_name='thingnote', + name='owner', + field=models.URLField(default=0, max_length=256), + preserve_default=False, + ), + ] diff --git a/things_for_testing/models.py b/things_for_testing/models.py index d1d2950..5538e1c 100644 --- a/things_for_testing/models.py +++ b/things_for_testing/models.py @@ -32,10 +32,6 @@ class ThingUser(models.Model): 'favourite_colour': self.favourite_colour, } - @property - def implements_activity_type(self): - return 'Person' - @property def activity_id(self): return self.url @@ -60,27 +56,33 @@ class ThingUser(models.Model): result.save() return result -@implements_activity_type('Article') -class ThingArticle(models.Model): +@implements_activity_type('Note') +class ThingNote(kepi_models.ActivityModel): title = models.CharField(max_length=256) - ftype = 'Article' + ftype = 'Note' remote_url = models.URLField(max_length=256, null=True, default=None) + owner = models.URLField(max_length=256) + + @property + def activity_actor(self): + return self.owner + @property def activity_form(self): return { 'id': self.activity_id, - 'type': 'Article', + 'type': 'Note', 'title': self.title, } @property def activity_id(self): return 'https://articles.example.com/{}'.format( - self.title, + self.title.replace(' ','-').lower(), ) @classmethod @@ -100,4 +102,3 @@ class ThingArticle(models.Model): ) result.save() return result -