From 31c1eb6fd5faa4beb69faa0c42aa6d9ebc52018e Mon Sep 17 00:00:00 2001 From: Marnanel Thurman Date: Mon, 5 Aug 2019 15:25:20 +0100 Subject: [PATCH] Removing d_k/types.py and doing the whole thing via polymorphic models. Model names are their ActivityPub types. As a result, Thing is renamed to Object. --- django_kepi/admin.py | 16 +-- django_kepi/create.py | 32 ++--- django_kepi/delivery.py | 4 +- django_kepi/embellish.py | 86 -------------- django_kepi/find.py | 6 +- django_kepi/management/commands/kepilist.py | 2 +- django_kepi/migrations/0001_initial.py | 111 ------------------ django_kepi/migrations/0002_mention.py | 22 ---- .../migrations/0003_auto_20190704_1404.py | 24 ---- .../migrations/0004_auto_20190704_1410.py | 19 --- .../migrations/0005_auto_20190708_1751.py | 24 ---- .../migrations/0006_auto_20190709_0441.py | 18 --- .../0007_actor_f_preferredusername.py | 19 --- .../migrations/0008_auto_20190711_1703.py | 18 --- .../migrations/0009_auto_20190711_1708.py | 37 ------ .../migrations/0010_auto_20190711_1711.py | 23 ---- .../migrations/0011_auto_20190718_1219.py | 23 ---- .../0012_incomingmessage_is_local_user.py | 18 --- django_kepi/migrations/0013_thing_f_actor.py | 18 --- .../migrations/0014_auto_20190726_1515.py | 34 ------ .../0015_incomingmessage_target_collection.py | 18 --- ...emove_incomingmessage_target_collection.py | 17 --- .../migrations/0017_auto_20190805_0003.py | 31 ----- django_kepi/models/__init__.py | 21 ++-- django_kepi/models/activity.py | 38 +++++- django_kepi/models/actor.py | 21 +++- django_kepi/models/audience.py | 4 +- django_kepi/models/collection.py | 2 +- django_kepi/models/item.py | 39 +++++- django_kepi/models/thing.py | 24 ++-- django_kepi/models/thingfield.py | 2 +- django_kepi/side_effects.py | 28 +---- django_kepi/views.py | 8 +- tests/test_activity.py | 18 +-- tests/test_deliver.py | 10 +- tests/test_embellish.py | 52 -------- tests/test_find.py | 1 - tests/test_send_to_inbox.py | 1 - tests/test_send_to_outbox.py | 8 +- tests/test_validation.py | 6 +- 40 files changed, 169 insertions(+), 734 deletions(-) delete mode 100644 django_kepi/embellish.py delete mode 100644 django_kepi/migrations/0001_initial.py delete mode 100644 django_kepi/migrations/0002_mention.py delete mode 100644 django_kepi/migrations/0003_auto_20190704_1404.py delete mode 100644 django_kepi/migrations/0004_auto_20190704_1410.py delete mode 100644 django_kepi/migrations/0005_auto_20190708_1751.py delete mode 100644 django_kepi/migrations/0006_auto_20190709_0441.py delete mode 100644 django_kepi/migrations/0007_actor_f_preferredusername.py delete mode 100644 django_kepi/migrations/0008_auto_20190711_1703.py delete mode 100644 django_kepi/migrations/0009_auto_20190711_1708.py delete mode 100644 django_kepi/migrations/0010_auto_20190711_1711.py delete mode 100644 django_kepi/migrations/0011_auto_20190718_1219.py delete mode 100644 django_kepi/migrations/0012_incomingmessage_is_local_user.py delete mode 100644 django_kepi/migrations/0013_thing_f_actor.py delete mode 100644 django_kepi/migrations/0014_auto_20190726_1515.py delete mode 100644 django_kepi/migrations/0015_incomingmessage_target_collection.py delete mode 100644 django_kepi/migrations/0016_remove_incomingmessage_target_collection.py delete mode 100644 django_kepi/migrations/0017_auto_20190805_0003.py delete mode 100644 tests/test_embellish.py diff --git a/django_kepi/admin.py b/django_kepi/admin.py index ec65def..b8b92bd 100644 --- a/django_kepi/admin.py +++ b/django_kepi/admin.py @@ -2,24 +2,24 @@ from django.contrib import admin from polymorphic.admin import * from django_kepi.models import * -class ThingChildAdmin(PolymorphicChildModelAdmin): - base_model = Thing +class ObjectChildAdmin(PolymorphicChildModelAdmin): + base_model = Object @admin.register(Actor) -class ActorChildAdmin(ThingChildAdmin): +class ActorChildAdmin(ObjectChildAdmin): base_model = Actor @admin.register(Item) -class ItemChildAdmin(ThingChildAdmin): +class ItemChildAdmin(ObjectChildAdmin): base_model = Item @admin.register(Activity) -class ActivityChildAdmin(ThingChildAdmin): +class ActivityChildAdmin(ObjectChildAdmin): base_model = Activity -@admin.register(Thing) -class ThingParentAdmin(PolymorphicParentModelAdmin): - base_model = Thing +@admin.register(Object) +class ObjectParentAdmin(PolymorphicParentModelAdmin): + base_model = Object child_models = ( Actor, Item, diff --git a/django_kepi/create.py b/django_kepi/create.py index 6f5be66..d1c64a0 100644 --- a/django_kepi/create.py +++ b/django_kepi/create.py @@ -1,4 +1,4 @@ -import django_kepi.types as types +from django_kepi.models import * import logging logger = logging.getLogger(name='django_kepi') @@ -19,7 +19,7 @@ def create( value, is_local_user, run_side_effects) if value is None: - logger.warn(" -- it's ludicrous to create an object with no value") + logger.warn(" -- it's ludicrous to create an Object with no value") return None # Remove the "f_" prefix, which exists so that we can write @@ -33,40 +33,28 @@ def create( for k,v in value.copy().items(): if not isinstance(k, str): - logger.warn('Things can only have keys which are strings: %s', + logger.warn('Objects can only have keys which are strings: %s', str(k)) continue if 'type' not in value: - logger.warn("Things must have a type; dropping message") + logger.warn("Objects must have a type; dropping message") return None value['type'] = value['type'].title() if 'id' in value: if is_local_user: - logger.warn('Removing "id" field at Thing creation') + logger.warn('Removing "id" field at local Object creation') del value['id'] else: if not is_local_user: - logger.warn("Remote things must have an id; dropping message") + logger.warn("Remote Objects must have an id; dropping message") return None - try: - type_spec = types.ACTIVITYPUB_TYPES[value['type']] - except KeyError: - logger.info('Unknown thing type: %s; dropping message', - value['type']) - return None - - if 'class' not in type_spec: - logger.info('Type %s can\'t be instantiated', - value['type']) - return None - try: import django_kepi.models as kepi_models - cls = getattr(locals()['kepi_models'], type_spec['class']) + cls = getattr(locals()['kepi_models'], value['type']) except KeyError: # shouldn't happen! logger.warn("The class '%s' wasn't exported properly", @@ -74,11 +62,7 @@ def create( return None logger.debug('Class for %s is %s', value['type'], cls) - - ######################## - - # XXX Check what fields we need, based on type_spec. - # XXX implement this. + del value['type'] ######################## diff --git a/django_kepi/delivery.py b/django_kepi/delivery.py index 8a8ad77..71e7558 100644 --- a/django_kepi/delivery.py +++ b/django_kepi/delivery.py @@ -278,8 +278,8 @@ def deliver( incoming = False, ): try: - activity = django_kepi.models.Thing.objects.get(number=activity_id) - except django_kepi.models.Thing.DoesNotExist: + activity = django_kepi.models.Object.objects.get(number=activity_id) + except django_kepi.models.Object.DoesNotExist: logger.warn("Can't deliver activity %s because it doesn't exist", activity_id) return None diff --git a/django_kepi/embellish.py b/django_kepi/embellish.py deleted file mode 100644 index 2f7ee8b..0000000 --- a/django_kepi/embellish.py +++ /dev/null @@ -1,86 +0,0 @@ -import logging -from django.conf import settings -import datetime - -logger = logging.getLogger(name='django_kepi') - -def _embellish_Note(thing, user=None): - - if 'summary' not in thing: - thing['summary'] = None - if 'sensitive' not in thing: - thing['sensitive'] = False - if 'attachment' not in thing: - thing['attachment'] = [] - if 'tag' not in thing: - thing['tag'] = [] - if 'to' not in thing: - thing['to'] = ['https://www.w3.org/ns/activitystreams#Public'] - if 'cc' not in thing: - thing['cc'] = [user.activity_followers] - if 'attributedTo' not in thing: - thing['attributedTo'] = user.activity_id - - ## Conversation structure - - if 'inReplyTo' not in thing: - thing['inReplyTo'] = None - - # XXX Not sure about the 'conversation' tag. - # See https://github.com/tootsuite/mastodon/issues/4213 . - - ## Content map - - if 'contentMap' not in thing and 'content' in thing: - thing['contentMap'] = { - settings.LANGUAGE_CODE: thing['content'], - } - elif 'content' not in thing and 'contentMap' in thing: - # just pick one - thing['content'] = thing['contentMap'].values()[0] - - ## Atom feeds - - if 'atomUri' not in thing: - thing['atomUri'] = thing['url'] - if 'inReplyToUri' not in thing: - thing['inReplyToAtomUri'] = thing['inReplyTo'] - - ## All done - - return thing - -def embellish(thing, user=None): - - if 'type' not in thing: - logger.debug('embellish: object does not contain a type: %s', - str(thing)) - raise ValueError('object does not contain a type!') - - f_type = thing['type'] - logger.debug('embellish: received thing of type %s', f_type) - - ###### general embellishments - - if 'id' not in thing: - logger.debug('embellish: object does not contain a type: %s', - str(thing)) - raise ValueError('object does not contain an id!') - - if 'url' not in thing: - thing['url'] = thing['id'] - - if 'published' not in thing: - thing['published'] = datetime.datetime.now().isoformat( - timespec='seconds', - )+'Z' - - ###### special embellishments per "type" - - if f_type=='Note': - thing = _embellish_Note(thing, user) - else: - logger.warn('don\'t know how to embellish things of type %s', - f_type) - - return thing diff --git a/django_kepi/find.py b/django_kepi/find.py index d42da29..1c8d70a 100644 --- a/django_kepi/find.py +++ b/django_kepi/find.py @@ -74,7 +74,7 @@ def find_local(path, def find_remote(url, do_not_fetch=False): - from django_kepi.models.thing import Thing + from django_kepi.models.thing import Object logger.debug('%s: find remote', url) @@ -89,14 +89,14 @@ def find_remote(url, # We fetched it in the past. try: - result = Thing.objects.get( + result = Object.objects.get( remote_url = url, ) logger.debug('%s: already fetched, and it\'s %s', url, result) return result - except Thing.DoesNotExist: + except Object.DoesNotExist: logger.debug('%s: we already know it wasn\'t there', url) diff --git a/django_kepi/management/commands/kepilist.py b/django_kepi/management/commands/kepilist.py index b47a688..9126562 100644 --- a/django_kepi/management/commands/kepilist.py +++ b/django_kepi/management/commands/kepilist.py @@ -22,7 +22,7 @@ class Command(BaseCommand): # This can certainly be optimised a lot count = 0 - for thing in django_kepi.models.Thing.objects.all(): + for thing in django_kepi.models.Object.objects.all(): usable = True for f,v in filters: diff --git a/django_kepi/migrations/0001_initial.py b/django_kepi/migrations/0001_initial.py deleted file mode 100644 index 8228fe9..0000000 --- a/django_kepi/migrations/0001_initial.py +++ /dev/null @@ -1,111 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-02 15:53 - -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ] - - operations = [ - migrations.CreateModel( - name='CachedRemoteText', - fields=[ - ('address', models.URLField(primary_key=True, serialize=False)), - ('content', models.TextField(default=None, null=True)), - ], - ), - migrations.CreateModel( - name='Following', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('follower', models.URLField(max_length=255)), - ('following', models.URLField(max_length=255)), - ('pending', models.BooleanField(default=True)), - ], - ), - migrations.CreateModel( - name='IncomingMessage', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('received_date', models.DateTimeField(auto_now_add=True)), - ('content_type', models.CharField(default='', max_length=255)), - ('date', models.CharField(default='', max_length=255)), - ('digest', models.CharField(default='', max_length=255)), - ('host', models.CharField(default='', max_length=255)), - ('path', models.CharField(default='', max_length=255)), - ('signature', models.CharField(default='', max_length=255)), - ('body', models.TextField(default='')), - ('waiting_for', models.URLField(default=None, null=True)), - ], - ), - migrations.CreateModel( - name='Thing', - fields=[ - ('number', models.CharField(default='', max_length=8, primary_key=True, serialize=False, unique=True)), - ('f_type', models.CharField(choices=[('Accept', 'Accept'), ('Add', 'Add'), ('Create', 'Create'), ('Delete', 'Delete'), ('Follow', 'Follow'), ('Like', 'Like'), ('Reject', 'Reject'), ('Remove', 'Remove'), ('Undo', 'Undo'), ('Update', 'Update')], max_length=255)), - ('remote_url', models.URLField(default=None, max_length=255, null=True, unique=True)), - ('f_actor', models.URLField(blank=True, max_length=255)), - ('f_name', models.CharField(blank=True, max_length=255)), - ('active', models.BooleanField(default=True)), - ('other_fields', models.TextField(default='')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_django_kepi.thing_set+', to='contenttypes.ContentType')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - ), - migrations.CreateModel( - name='Activity', - fields=[ - ('thing_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_kepi.Thing')), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('django_kepi.thing',), - ), - migrations.CreateModel( - name='Actor', - fields=[ - ('thing_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_kepi.Thing')), - ('privateKey', models.TextField()), - ('publicKey', models.TextField()), - ('auto_follow', models.BooleanField(default=True)), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('django_kepi.thing',), - ), - migrations.CreateModel( - name='Item', - fields=[ - ('thing_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_kepi.Thing')), - ('f_content', models.CharField(blank=True, max_length=255)), - ], - options={ - 'abstract': False, - 'base_manager_name': 'objects', - }, - bases=('django_kepi.thing',), - ), - migrations.CreateModel( - name='Audience', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('field', models.PositiveSmallIntegerField(choices=[(1, 'audience'), (2, 'to'), (4, 'cc'), (114, 'bto'), (116, 'bcc')])), - ('recipient', models.CharField(max_length=255)), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_kepi.Thing')), - ], - ), - ] diff --git a/django_kepi/migrations/0002_mention.py b/django_kepi/migrations/0002_mention.py deleted file mode 100644 index a597763..0000000 --- a/django_kepi/migrations/0002_mention.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-03 15:59 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Mention', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('from_status', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='django_kepi.Item')), - ('to_actor', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='django_kepi.Actor')), - ], - ), - ] diff --git a/django_kepi/migrations/0003_auto_20190704_1404.py b/django_kepi/migrations/0003_auto_20190704_1404.py deleted file mode 100644 index fe89088..0000000 --- a/django_kepi/migrations/0003_auto_20190704_1404.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-04 14:04 - -import datetime -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0002_mention'), - ] - - operations = [ - migrations.CreateModel( - name='Fetch', - fields=[ - ('url', models.URLField(primary_key=True, serialize=False)), - ('date', models.DateField(default=datetime.datetime)), - ], - ), - migrations.DeleteModel( - name='CachedRemoteText', - ), - ] diff --git a/django_kepi/migrations/0004_auto_20190704_1410.py b/django_kepi/migrations/0004_auto_20190704_1410.py deleted file mode 100644 index be9b169..0000000 --- a/django_kepi/migrations/0004_auto_20190704_1410.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-04 14:10 - -import datetime -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0003_auto_20190704_1404'), - ] - - operations = [ - migrations.AlterField( - model_name='fetch', - name='date', - field=models.DateTimeField(default=datetime.datetime), - ), - ] diff --git a/django_kepi/migrations/0005_auto_20190708_1751.py b/django_kepi/migrations/0005_auto_20190708_1751.py deleted file mode 100644 index 099cbad..0000000 --- a/django_kepi/migrations/0005_auto_20190708_1751.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-08 17:51 - -import datetime -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0004_auto_20190704_1410'), - ] - - operations = [ - migrations.AddField( - model_name='item', - name='f_attributedTo', - field=models.CharField(blank=True, max_length=255), - ), - migrations.AlterField( - model_name='fetch', - name='date', - field=models.DateTimeField(default=datetime.datetime.now), - ), - ] diff --git a/django_kepi/migrations/0006_auto_20190709_0441.py b/django_kepi/migrations/0006_auto_20190709_0441.py deleted file mode 100644 index 4e6bd37..0000000 --- a/django_kepi/migrations/0006_auto_20190709_0441.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-09 04:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0005_auto_20190708_1751'), - ] - - operations = [ - migrations.AlterField( - model_name='mention', - name='to_actor', - field=models.CharField(max_length=255), - ), - ] diff --git a/django_kepi/migrations/0007_actor_f_preferredusername.py b/django_kepi/migrations/0007_actor_f_preferredusername.py deleted file mode 100644 index 40c67f8..0000000 --- a/django_kepi/migrations/0007_actor_f_preferredusername.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-11 11:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0006_auto_20190709_0441'), - ] - - operations = [ - migrations.AddField( - model_name='actor', - name='f_preferredUsername', - field=models.CharField(default='', max_length=255), - preserve_default=False, - ), - ] diff --git a/django_kepi/migrations/0008_auto_20190711_1703.py b/django_kepi/migrations/0008_auto_20190711_1703.py deleted file mode 100644 index ff1f86b..0000000 --- a/django_kepi/migrations/0008_auto_20190711_1703.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-11 17:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0007_actor_f_preferredusername'), - ] - - operations = [ - migrations.AlterField( - model_name='thing', - name='f_type', - field=models.CharField(choices=[('Accept', 'Accept'), ('Activity', 'Activity'), ('Actor', 'Actor'), ('Add', 'Add'), ('Announce', 'Announce'), ('Application', 'Application'), ('Article', 'Article'), ('Audio', 'Audio'), ('Create', 'Create'), ('Delete', 'Delete'), ('Document', 'Document'), ('Event', 'Event'), ('Follow', 'Follow'), ('Group', 'Group'), ('Image', 'Image'), ('Like', 'Like'), ('Note', 'Note'), ('Object', 'Object'), ('Organization', 'Organization'), ('Page', 'Page'), ('Person', 'Person'), ('Place', 'Place'), ('Profile', 'Profile'), ('Reject', 'Reject'), ('Relationship', 'Relationship'), ('Remove', 'Remove'), ('Service', 'Service'), ('Undo', 'Undo'), ('Update', 'Update'), ('Video', 'Video')], max_length=255), - ), - ] diff --git a/django_kepi/migrations/0009_auto_20190711_1708.py b/django_kepi/migrations/0009_auto_20190711_1708.py deleted file mode 100644 index fbc36ae..0000000 --- a/django_kepi/migrations/0009_auto_20190711_1708.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-11 17:08 - -from django.db import migrations, models -import django_kepi.models.thing - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0008_auto_20190711_1703'), - ] - - operations = [ - migrations.RemoveField( - model_name='thing', - name='f_actor', - ), - migrations.RemoveField( - model_name='thing', - name='f_name', - ), - migrations.AlterField( - model_name='thing', - name='number', - field=models.CharField(default=django_kepi.models.thing._new_number, max_length=8, primary_key=True, serialize=False, unique=True), - ), - migrations.AlterField( - model_name='thing', - name='other_fields', - field=models.TextField(blank=True, default=''), - ), - migrations.AlterField( - model_name='thing', - name='remote_url', - field=models.URLField(blank=True, default=None, max_length=255, null=True, unique=True), - ), - ] diff --git a/django_kepi/migrations/0010_auto_20190711_1711.py b/django_kepi/migrations/0010_auto_20190711_1711.py deleted file mode 100644 index 5b4ec79..0000000 --- a/django_kepi/migrations/0010_auto_20190711_1711.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-11 17:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0009_auto_20190711_1708'), - ] - - operations = [ - migrations.AlterField( - model_name='actor', - name='privateKey', - field=models.TextField(blank=True, null=True), - ), - migrations.AlterField( - model_name='actor', - name='publicKey', - field=models.TextField(blank=True, null=True), - ), - ] diff --git a/django_kepi/migrations/0011_auto_20190718_1219.py b/django_kepi/migrations/0011_auto_20190718_1219.py deleted file mode 100644 index 9a467c9..0000000 --- a/django_kepi/migrations/0011_auto_20190718_1219.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-18 12:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0010_auto_20190711_1711'), - ] - - operations = [ - migrations.RenameField( - model_name='actor', - old_name='privateKey', - new_name='f_privateKey', - ), - migrations.RenameField( - model_name='actor', - old_name='publicKey', - new_name='f_publicKey', - ), - ] diff --git a/django_kepi/migrations/0012_incomingmessage_is_local_user.py b/django_kepi/migrations/0012_incomingmessage_is_local_user.py deleted file mode 100644 index 8681bb5..0000000 --- a/django_kepi/migrations/0012_incomingmessage_is_local_user.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-18 13:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0011_auto_20190718_1219'), - ] - - operations = [ - migrations.AddField( - model_name='incomingmessage', - name='is_local_user', - field=models.BooleanField(default=False), - ), - ] diff --git a/django_kepi/migrations/0013_thing_f_actor.py b/django_kepi/migrations/0013_thing_f_actor.py deleted file mode 100644 index b303016..0000000 --- a/django_kepi/migrations/0013_thing_f_actor.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-21 16:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0012_incomingmessage_is_local_user'), - ] - - operations = [ - migrations.AddField( - model_name='thing', - name='f_actor', - field=models.CharField(blank=True, default=None, max_length=255, null=True), - ), - ] diff --git a/django_kepi/migrations/0014_auto_20190726_1515.py b/django_kepi/migrations/0014_auto_20190726_1515.py deleted file mode 100644 index ce0c5a8..0000000 --- a/django_kepi/migrations/0014_auto_20190726_1515.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-26 15:15 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0013_thing_f_actor'), - ] - - operations = [ - migrations.CreateModel( - name='Collection', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.URLField(max_length=255)), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='django_kepi.Actor')), - ], - ), - migrations.CreateModel( - name='CollectionMember', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('member', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='django_kepi.Thing')), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_kepi.Collection')), - ], - ), - migrations.AddConstraint( - model_name='collection', - constraint=models.UniqueConstraint(fields=('owner', 'name'), name='owner and name'), - ), - ] diff --git a/django_kepi/migrations/0015_incomingmessage_target_collection.py b/django_kepi/migrations/0015_incomingmessage_target_collection.py deleted file mode 100644 index 3632647..0000000 --- a/django_kepi/migrations/0015_incomingmessage_target_collection.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-26 16:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0014_auto_20190726_1515'), - ] - - operations = [ - migrations.AddField( - model_name='incomingmessage', - name='target_collection', - field=models.CharField(default='', max_length=255), - ), - ] diff --git a/django_kepi/migrations/0016_remove_incomingmessage_target_collection.py b/django_kepi/migrations/0016_remove_incomingmessage_target_collection.py deleted file mode 100644 index 7851de4..0000000 --- a/django_kepi/migrations/0016_remove_incomingmessage_target_collection.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.1 on 2019-07-30 15:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0015_incomingmessage_target_collection'), - ] - - operations = [ - migrations.RemoveField( - model_name='incomingmessage', - name='target_collection', - ), - ] diff --git a/django_kepi/migrations/0017_auto_20190805_0003.py b/django_kepi/migrations/0017_auto_20190805_0003.py deleted file mode 100644 index 2f80fac..0000000 --- a/django_kepi/migrations/0017_auto_20190805_0003.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2.1 on 2019-08-05 00:03 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_kepi', '0016_remove_incomingmessage_target_collection'), - ] - - operations = [ - migrations.RemoveField( - model_name='thing', - name='other_fields', - ), - migrations.CreateModel( - name='ThingField', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('field', models.CharField(max_length=255)), - ('value', models.CharField(blank=True, default=None, max_length=255, null=True)), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='django_kepi.Thing')), - ], - ), - migrations.AddConstraint( - model_name='thingfield', - constraint=models.UniqueConstraint(fields=('parent', 'field'), name='parent_field'), - ), - ] diff --git a/django_kepi/models/__init__.py b/django_kepi/models/__init__.py index 9b113f3..5222118 100644 --- a/django_kepi/models/__init__.py +++ b/django_kepi/models/__init__.py @@ -1,22 +1,27 @@ -from .thing import Thing, ThingField -from .activity import Activity -from .actor import Actor +from .thing import Object +from .thingfield import ThingField +from .activity import Activity, Create, Update, Delete, Follow, Add, Remove, \ + Like, Undo, Accept, Reject, Announce +from .actor import Actor, Application, Group, Organization, Person, Service from .audience import Audience, AUDIENCE_FIELD_NAMES from .following import Following from .mention import Mention -from .item import Item +from .item import Item, Article, Audio, Document, Event, Image, Note, Page, \ + Place, Profile, Relationship, Video from .collection import Collection, CollectionMember __all__ = [ - 'Thing', + 'Object', 'ThingField', - 'Activity', - 'Actor', + 'Activity', 'Create', 'Update', 'Delete', 'Follow', 'Add', 'Remove', \ + 'Like', 'Undo', 'Accept', 'Reject', 'Announce', 'Actor', + 'Application', 'Group', 'Organization', 'Person', 'Service', 'Audience', 'AUDIENCE_FIELD_NAMES', 'Following', 'Mention', - 'Item', + 'Item', 'Article', 'Audio', 'Document', 'Event', 'Image', 'Note', \ + 'Page', 'Place', 'Profile', 'Relationship', 'Video', 'Collection', 'CollectionMember', ] diff --git a/django_kepi/models/activity.py b/django_kepi/models/activity.py index bb9f65a..dbc176d 100644 --- a/django_kepi/models/activity.py +++ b/django_kepi/models/activity.py @@ -5,7 +5,7 @@ logger = logging.getLogger(name='django_kepi') ###################### -class Activity(thing.Thing): +class Activity(thing.Object): @property def activity_form(self): @@ -38,3 +38,39 @@ class Activity(thing.Thing): outbox = find_local(outbox_url, object_to_store=self) + +############################## + +class Create(Activity): + pass + +class Update(Activity): + pass + +class Delete(Activity): + pass + +class Follow(Activity): + pass + +class Add(Activity): + pass + +class Remove(Activity): + pass + +class Like(Activity): + pass + +class Undo(Activity): + pass + +class Accept(Activity): + pass + +class Reject(Activity): + pass + +class Announce(Activity): + # aka "boost" + pass diff --git a/django_kepi/models/actor.py b/django_kepi/models/actor.py index 6aff092..712f40c 100644 --- a/django_kepi/models/actor.py +++ b/django_kepi/models/actor.py @@ -8,9 +8,9 @@ logger = logging.getLogger(name='django_kepi') ###################### -class Actor(thing.Thing): +class Actor(thing.Object): """ - An Actor is a kind of Thing representing a person, + An Actor is a kind of Object representing a person, an organisation, a bot, or anything else that can post stuff and interact with other Actors. @@ -76,3 +76,20 @@ class Actor(thing.Thing): return self.list_path(name) return super().__getitem__(name) + +############################## + +class Application(Actor): + pass + +class Group(Actor): + pass + +class Organization(Actor): + pass + +class Person(Actor): + pass + +class Service(Actor): + pass diff --git a/django_kepi/models/audience.py b/django_kepi/models/audience.py index 66454f5..8db2064 100644 --- a/django_kepi/models/audience.py +++ b/django_kepi/models/audience.py @@ -28,7 +28,7 @@ AUDIENCE_FIELD_NAMES = dict([(v,f) for (f,v) in FIELD_CHOICES]) class Audience(models.Model): parent = models.ForeignKey( - 'django_kepi.Thing', + 'django_kepi.Object', on_delete = models.CASCADE, ) @@ -56,7 +56,7 @@ class Audience(models.Model): field, value): """ - Add new Audiences for a given Thing. + Add new Audiences for a given Object. "value" is a list of strings. This function only adds Audiences of diff --git a/django_kepi/models/collection.py b/django_kepi/models/collection.py index 8e3f051..931d303 100644 --- a/django_kepi/models/collection.py +++ b/django_kepi/models/collection.py @@ -133,7 +133,7 @@ class CollectionMember(models.Model): ) member = models.ForeignKey( - 'django_kepi.Thing', + 'django_kepi.Object', on_delete = models.DO_NOTHING, ) diff --git a/django_kepi/models/item.py b/django_kepi/models/item.py index cd87bb9..2be5555 100644 --- a/django_kepi/models/item.py +++ b/django_kepi/models/item.py @@ -6,7 +6,7 @@ logger = logging.getLogger(name='django_kepi') ###################### -class Item(thing.Thing): +class Item(thing.Object): f_content = models.CharField( max_length=255, @@ -118,3 +118,40 @@ class Item(thing.Thing): def conversation(self): # FIXME I really don't understand conversation values return None + +############################## + +class Article(Item): + pass + +class Audio(Item): + pass + +class Document(Item): + pass + +class Event(Item): + pass + +class Image(Item): + pass + +class Note(Item): + # Why isn't it a subclass of Document? + pass + +class Page(Item): + # i.e. a web page + pass + +class Place(Item): + pass + +class Profile(Item): + pass + +class Relationship(Item): + pass + +class Video(Item): + pass diff --git a/django_kepi/models/thing.py b/django_kepi/models/thing.py index 82d6c27..1d94254 100644 --- a/django_kepi/models/thing.py +++ b/django_kepi/models/thing.py @@ -10,22 +10,17 @@ import random import json import datetime import warnings -from django_kepi.types import ACTIVITYPUB_TYPES logger = logging.getLogger(name='django_kepi') ###################### -ACTIVITY_TYPES = sorted(ACTIVITYPUB_TYPES.keys()) - -ACTIVITY_TYPE_CHOICES = [(x,x) for x in ACTIVITY_TYPES] - def _new_number(): return '%08x' % (random.randint(0, 0xffffffff),) ###################### -class Thing(PolymorphicModel): +class Object(PolymorphicModel): number = models.CharField( max_length=8, @@ -34,11 +29,6 @@ class Thing(PolymorphicModel): default=_new_number, ) - f_type = models.CharField( - max_length=255, - choices=ACTIVITY_TYPE_CHOICES, - ) - f_actor = models.CharField( max_length=255, default=None, @@ -131,14 +121,14 @@ class Thing(PolymorphicModel): return result @property - def activity_type(self): - return self.f_type + def f_type(self): + return self.__class__.__name__ @property def activity_form(self): result = { 'id': self.url, - 'type': self.get_f_type_display(), + 'type': self.f_type, } for name in dir(self): @@ -283,7 +273,7 @@ class Thing(PolymorphicModel): try: self.delete() - except AssertionError: + except: logger.info(' -- deletion failed, probably because of '+\ 'https://code.djangoproject.com/ticket/23076') @@ -336,8 +326,8 @@ def _normalise_type_for_thing(v): return v # also booleans elif isinstance(v, list): return v # and lists as well - elif isinstance(v, Thing): - return v.url # Things can deal with themselves + elif isinstance(v, Object): + return v.url # Objects can deal with themselves # okay, it's something weird diff --git a/django_kepi/models/thingfield.py b/django_kepi/models/thingfield.py index 51eb4c8..fcf66cb 100644 --- a/django_kepi/models/thingfield.py +++ b/django_kepi/models/thingfield.py @@ -26,7 +26,7 @@ class ThingField(models.Model): ) parent = models.ForeignKey( - 'django_kepi.Thing', + 'django_kepi.Object', on_delete = models.DO_NOTHING, ) diff --git a/django_kepi/side_effects.py b/django_kepi/side_effects.py index 440169e..9ce4bfe 100644 --- a/django_kepi/side_effects.py +++ b/django_kepi/side_effects.py @@ -2,7 +2,6 @@ import logging from django.conf import settings from django_kepi.find import find from django_kepi.delivery import deliver -from django_kepi.types import ACTIVITYPUB_TYPES logger = logging.getLogger(name='django_kepi') @@ -85,35 +84,16 @@ def reject(activity): def create(activity): + import django_kepi.models as kepi_models from django_kepi.create import create as kepi_create raw_material = dict([('f_'+f, v) for f,v in activity['object'].items()]) - if 'f_type' not in raw_material: - logger.warn('Attempt to use Create to create '+\ - 'something without a type. '+\ - 'Deleting original Create.') + if issubclass(getattr(kepi_models, + raw_material['f_type']), + kepi_models.Activity): - return False - - if raw_material['f_type'] not in ACTIVITYPUB_TYPES: - logger.warn('Attempt to use Create to create '+\ - 'an object of type %s, which is unknown. '+\ - 'Deleting original Create.', - raw_material['f_type']) - - return False - - if 'class' not in ACTIVITYPUB_TYPES[raw_material['f_type']]: - logger.warn('Attempt to use Create to create '+\ - 'an object of type %s, which is abstract. '+\ - 'Deleting original Create.', - raw_material['f_type']) - - return False - - if ACTIVITYPUB_TYPES[raw_material['f_type']]['class']=='Activity': logger.warn('Attempt to use Create to create '+\ 'an object of type %s. '+\ 'Create can only create non-activities. '+\ diff --git a/django_kepi/views.py b/django_kepi/views.py index ccd7944..3fa669e 100644 --- a/django_kepi/views.py +++ b/django_kepi/views.py @@ -217,13 +217,13 @@ class ThingView(KepiView): def activity_get(self, request, *args, **kwargs): try: - logger.debug('Looking up Thing by id==%s', + logger.debug('Looking up Object by id==%s', kwargs['id']) - activity_object = Thing.objects.get( + activity_object = Object.objects.get( number=kwargs['id'], ) - except Thing.DoesNotExist: + except Object.DoesNotExist: logger.info(' -- unknown: %s', kwargs) return None except django.core.exceptions.ValidationError: @@ -325,7 +325,7 @@ class AllUsersView(KepiView): logger.debug('Finding all users.') - return Thing.objects.filter(f_type='Person') + return Object.objects.filter(f_type='Person') def _modify_list_item(self, obj): return obj.activity_form diff --git a/tests/test_activity.py b/tests/test_activity.py index f7808a5..2ce998a 100644 --- a/tests/test_activity.py +++ b/tests/test_activity.py @@ -1,5 +1,5 @@ from django.test import TestCase -from django_kepi.models import Thing +from django_kepi.models import Object REMOTE_ID_1 = 'https://users.example.com/activity/1' @@ -9,12 +9,12 @@ SAMPLE_NOTE = { "type": "Note", } -class TestThing(TestCase): +class TestObject(TestCase): def test_bad_type(self): with self.assertRaisesMessage(ValueError, "is not a thing type"): - Thing.create( + Object.create( f_id = REMOTE_ID_1, f_type = "Wombat", ) @@ -22,7 +22,7 @@ class TestThing(TestCase): def test_remote_no_id(self): with self.assertRaisesMessage(ValueError, "Remote things must have an id"): - Thing.create( + Object.create( f_type = "Create", f_actor = "https://example.com/user/fred", f_object = SAMPLE_NOTE, @@ -31,27 +31,27 @@ class TestThing(TestCase): def test_create_create_wrong_params(self): with self.assertRaisesMessage(ValueError, "Wrong parameters for thing type"): - Thing.create( + Object.create( f_id = REMOTE_ID_1, f_type = "Create", ) with self.assertRaisesMessage(ValueError, "Wrong parameters for thing type"): - Thing.create( + Object.create( f_id = REMOTE_ID_1, f_actor = REMOTE_FRED, f_type = "Create", ) with self.assertRaisesMessage(ValueError, "Wrong parameters for thing type"): - Thing.create( + Object.create( f_id = REMOTE_ID_1, f_target = REMOTE_FRED, f_type = "Create", ) def test_create_create(self): - Thing.create( + Object.create( f_id = REMOTE_ID_1, f_type = "Create", f_actor = REMOTE_FRED, @@ -59,6 +59,6 @@ class TestThing(TestCase): ) self.assertEqual( - Thing.objects.filter(remote_url=REMOTE_ID_1).count(), + Object.objects.filter(remote_url=REMOTE_ID_1).count(), 1, ) diff --git a/tests/test_deliver.py b/tests/test_deliver.py index 75dbc38..4a33e62 100644 --- a/tests/test_deliver.py +++ b/tests/test_deliver.py @@ -1,6 +1,6 @@ from django.test import TestCase, Client from django_kepi.delivery import deliver -from django_kepi.models import Thing +from django_kepi.models import Object import django_kepi.views from unittest.mock import Mock, patch from . import * @@ -22,9 +22,9 @@ REMOTE_PATH_NAMES = { def _message_became_activity(url=ACTIVITY_ID): try: - result = Thing.objects.get(remote_url=url) + result = Object.objects.get(remote_url=url) return True - except Thing.DoesNotExist: + except Object.DoesNotExist: return False class TestDeliverTasks(TestCase): @@ -36,7 +36,7 @@ class TestDeliverTasks(TestCase): remote_user_endpoints, ): - a = Thing.create(**activity_fields) + a = Object.create(**activity_fields) a.save() for who, what in remote_user_details.items(): @@ -211,7 +211,7 @@ class TestDelivery(TestCase): self._set_up_remote_request_mocks() self._set_up_local_user_mocks() - like = Thing.create( + like = Object.create( f_type = 'Like', f_actor = LOCAL_ALICE, f_object = REMOTE_FRED, diff --git a/tests/test_embellish.py b/tests/test_embellish.py deleted file mode 100644 index b612ea9..0000000 --- a/tests/test_embellish.py +++ /dev/null @@ -1,52 +0,0 @@ -from django.test import TestCase -from unittest import skip -from django_kepi.embellish import embellish -from . import * -import logging - -logger = logging.getLogger(name='django_kepi') - -@skip("Decide whether we're keeping this") -class TestEmbellish(TestCase): - def test_embellish_note(self): - - SOURCE = { - 'id': 'https://example.com/users/fred/status/1234', - 'type': 'Note', - 'content': 'Hello world', - } - - EXPECTING = { - 'id': 'https://example.com/users/fred/status/1234', - 'url': 'https://example.com/users/fred/status/1234', - 'atomUri': 'https://example.com/users/fred/status/1234', - 'type': 'Note', - 'content': 'Hello world', - 'summary': None, - "attributedTo": "https://example.com/users/fred", - "to":["https://www.w3.org/ns/activitystreams#Public"], - "cc":["https://example.com/users/fred/followers"], - "sensitive": False, - "inReplyTo": None, - "inReplyToAtomUri": None, - "contentMap":{"en-us": 'Hello world',}, - "attachment":[], - "tag":[], - } - # (plus "published") - # (plus "conversation", wtf?) - - user = create_local_person( - name = 'Fred', - url = 'https://example.com/users/fred', - ) - - result = embellish(SOURCE, - user=user) - - self.maxDiff = None - self.assertDictContainsSubset( - EXPECTING, - result, - ) - diff --git a/tests/test_find.py b/tests/test_find.py index 0829b98..5689d0d 100644 --- a/tests/test_find.py +++ b/tests/test_find.py @@ -1,6 +1,5 @@ from django.test import TestCase from django_kepi.find import find -from django_kepi.models import Thing from django_kepi.create import create from django.conf import settings from . import * diff --git a/tests/test_send_to_inbox.py b/tests/test_send_to_inbox.py index d33e03b..a69b962 100644 --- a/tests/test_send_to_inbox.py +++ b/tests/test_send_to_inbox.py @@ -5,7 +5,6 @@ from django_kepi.create import create from django_kepi.models.audience import Audience, AUDIENCE_FIELD_NAMES from django_kepi.models.mention import Mention from django_kepi.models.item import Item -from django_kepi.models.thing import Thing from django_kepi.models.following import Following from django_kepi.models.activity import Activity from django.test import Client diff --git a/tests/test_send_to_outbox.py b/tests/test_send_to_outbox.py index 5c3a1b0..1fe2933 100644 --- a/tests/test_send_to_outbox.py +++ b/tests/test_send_to_outbox.py @@ -5,7 +5,7 @@ from django_kepi.create import create from django_kepi.models.audience import Audience, AUDIENCE_FIELD_NAMES from django_kepi.models.mention import Mention from django_kepi.models.item import Item -from django_kepi.models.thing import Thing +from django_kepi.models.thing import Object from django_kepi.models.activity import Activity from django.test import Client from urllib.parse import urlparse @@ -231,13 +231,13 @@ class TestOutbox(TestCase): @httpretty.activate def test_unwrapped_object(self): - items_before = list(Thing.objects.all()) + items_before = list(Object.objects.all()) self._send( content = OBJECT_FORM, ) - items_after = list(Thing.objects.all()) + items_after = list(Object.objects.all()) # This should have created two objects: # the Note we sent, and an implict Create. @@ -279,7 +279,7 @@ class TestOutbox(TestCase): ) self.assertEqual( - len(Thing.objects.filter(f_actor=json.dumps(ALICE_ID))), + len(Object.objects.filter(f_actor=json.dumps(ALICE_ID))), 1) # TODO When Actors have liked() and Things have likes(), diff --git a/tests/test_validation.py b/tests/test_validation.py index 00f1885..c4dfa91 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,6 +1,6 @@ from django.test import TestCase, Client from django_kepi.validation import IncomingMessage, validate -from django_kepi.models import Thing +from django_kepi.models import Object from unittest import skip from unittest.mock import Mock, patch from . import * @@ -52,9 +52,9 @@ MESSAGE_CONTEXT = ["https://www.w3.org/ns/activitystreams", def _message_became_activity(url=ACTIVITY_ID): try: - result = Thing.objects.get(remote_url=url) + result = Object.objects.get(remote_url=url) return True - except Thing.DoesNotExist: + except Object.DoesNotExist: return False class ResultWrapper(object):