From 98520cd0ad614920c433ecd4c81f0a03d13c9ba5 Mon Sep 17 00:00:00 2001 From: Marnanel Thurman Date: Sat, 29 Jun 2019 16:40:47 +0100 Subject: [PATCH] create() produces instances of subtypes of Thing as appropriate. --- django_kepi/create.py | 32 ++++++++++++--- django_kepi/migrations/0001_initial.py | 55 +++++++++++++++++++++++++- django_kepi/models/__init__.py | 6 +++ django_kepi/models/activity.py | 9 +++++ django_kepi/models/actor.py | 2 +- django_kepi/models/item.py | 11 ++++++ tests/test_polymorph.py | 33 ++++++++++++++++ 7 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 django_kepi/models/activity.py create mode 100644 django_kepi/models/item.py create mode 100644 tests/test_polymorph.py diff --git a/django_kepi/create.py b/django_kepi/create.py index 93628cf..e73bca9 100644 --- a/django_kepi/create.py +++ b/django_kepi/create.py @@ -1,6 +1,5 @@ -import django_kepi.models.thing as thing -import django_kepi.models.audience as audience -import django_kepi.models.actor as actor +from django_kepi.models import * +import django_kepi.types as types import logging import json @@ -62,6 +61,30 @@ def create( } other_fields = value.copy() + 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: + cls = globals()[type_spec['class']] + except KeyError: + # shouldn't happen! + logger.warn("The class '%s' wasn't imported into create.py", + type_spec['class']) + return None + + logger.debug('Class for %s is %s', value['type'], cls) + + # XXX get the record from "types", and see what fields we need + try: need_actor, need_object, need_target = TYPES[value['type']] except KeyError: @@ -131,9 +154,6 @@ def create( if f in other_fields: del other_fields[f] - # XXX decide "cls" here - cls = thing.Thing - result = cls(**record_fields) result.save() logger.debug('Created %s (%s): ----', diff --git a/django_kepi/migrations/0001_initial.py b/django_kepi/migrations/0001_initial.py index c903c5f..820ee45 100644 --- a/django_kepi/migrations/0001_initial.py +++ b/django_kepi/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.1 on 2019-05-24 18:32 +# Generated by Django 2.2.1 on 2019-06-29 10:26 from django.db import migrations, models import django.db.models.deletion @@ -10,6 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), ] operations = [ @@ -48,11 +49,61 @@ class Migration(migrations.Migration): name='Thing', fields=[ ('number', models.CharField(default='', max_length=8, primary_key=True, serialize=False, unique=True)), - ('f_type', models.CharField(choices=[('Reject', 'Reject'), ('Add', 'Add'), ('Follow', 'Follow'), ('Like', 'Like'), ('Undo', 'Undo'), ('Update', 'Update'), ('Accept', 'Accept'), ('Create', 'Create'), ('Remove', 'Remove'), ('Delete', 'Delete')], max_length=255)), + ('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)), + ('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')), + ], + 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')), ], ), migrations.CreateModel( diff --git a/django_kepi/models/__init__.py b/django_kepi/models/__init__.py index e69de29..0dfeb86 100644 --- a/django_kepi/models/__init__.py +++ b/django_kepi/models/__init__.py @@ -0,0 +1,6 @@ +from .thing import Thing, ThingField +from .activity import Activity +from .actor import Actor +from .audience import Audience +from .following import Following +from .item import Item diff --git a/django_kepi/models/activity.py b/django_kepi/models/activity.py new file mode 100644 index 0000000..f1841a9 --- /dev/null +++ b/django_kepi/models/activity.py @@ -0,0 +1,9 @@ +from . import thing +import logging + +logger = logging.getLogger(name='django_kepi') + +###################### + +class Activity(thing.Thing): + pass diff --git a/django_kepi/models/actor.py b/django_kepi/models/actor.py index 9ea58d6..e9ac9c2 100644 --- a/django_kepi/models/actor.py +++ b/django_kepi/models/actor.py @@ -1,5 +1,5 @@ from django.db import models -import django_kepi.models.thing as thing +from . import thing import django_kepi.crypto import logging diff --git a/django_kepi/models/item.py b/django_kepi/models/item.py new file mode 100644 index 0000000..89e5959 --- /dev/null +++ b/django_kepi/models/item.py @@ -0,0 +1,11 @@ +from django.db import models +from . import thing +import logging + +logger = logging.getLogger(name='django_kepi') + +###################### + +class Item(thing.Thing): + pass + diff --git a/tests/test_polymorph.py b/tests/test_polymorph.py new file mode 100644 index 0000000..8786847 --- /dev/null +++ b/tests/test_polymorph.py @@ -0,0 +1,33 @@ +from django.test import TestCase +from django_kepi.create import create +from django_kepi.models import * +import logging + +logger = logging.getLogger(name='tests') + +class TestPolymorph(TestCase): + + def test_invalid_type(self): + t = create( + f_type = 'Wombat', + ) + self.assertIsNone(t) + + def test_note(self): + t = create( + f_type = 'Note', + ) + self.assertIsInstance(t, Item) + + def test_person(self): + t = create( + f_type = 'Person', + ) + self.assertIsInstance(t, Actor) + + def test_abstract(self): + t = create( + f_type = 'Object', + ) + self.assertIsNone(t) +