kopia lustrzana https://gitlab.com/marnanel/chapeau
Huge checkin: central project "kepi" to use the "django_kepi" library.
This has shown up some bugs. Checking in the fixes for them too.2019-08-17
rodzic
c59228c58f
commit
18565c6afe
|
@ -4,5 +4,6 @@ __pycache__
|
|||
.eggs
|
||||
.tox
|
||||
*.egg-info
|
||||
*.sqlite3
|
||||
build/
|
||||
dist/
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
from django.contrib import admin
|
||||
from polymorphic.admin import *
|
||||
from django_kepi.models import *
|
||||
|
||||
class ThingChildAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = Thing
|
||||
|
||||
@admin.register(Actor)
|
||||
class ActorChildAdmin(ThingChildAdmin):
|
||||
base_model = Actor
|
||||
|
||||
@admin.register(Item)
|
||||
class ItemChildAdmin(ThingChildAdmin):
|
||||
base_model = Item
|
||||
|
||||
@admin.register(Activity)
|
||||
class ActivityChildAdmin(ThingChildAdmin):
|
||||
base_model = Activity
|
||||
|
||||
@admin.register(Thing)
|
||||
class ThingParentAdmin(PolymorphicParentModelAdmin):
|
||||
base_model = Thing
|
||||
child_models = (
|
||||
Actor,
|
||||
Item,
|
||||
Activity,
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
# 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),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,37 @@
|
|||
# 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),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# 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),
|
||||
),
|
||||
]
|
|
@ -18,9 +18,13 @@ class Actor(thing.Thing):
|
|||
"""
|
||||
|
||||
privateKey = models.TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
publicKey = models.TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
auto_follow = models.BooleanField(
|
||||
|
|
|
@ -8,35 +8,18 @@ import random
|
|||
import json
|
||||
import datetime
|
||||
import warnings
|
||||
from django_kepi.types import ACTIVITYPUB_TYPES
|
||||
|
||||
logger = logging.getLogger(name='django_kepi')
|
||||
|
||||
######################
|
||||
|
||||
ACTIVITY_TYPES = set([
|
||||
'Create',
|
||||
'Update',
|
||||
'Delete',
|
||||
'Follow',
|
||||
'Add',
|
||||
'Remove',
|
||||
'Like',
|
||||
'Undo',
|
||||
'Accept',
|
||||
'Reject',
|
||||
])
|
||||
ACTIVITY_TYPES = sorted(ACTIVITYPUB_TYPES.keys())
|
||||
|
||||
ACTIVITY_TYPE_CHOICES = [(x,x) for x in sorted(ACTIVITY_TYPES)]
|
||||
ACTIVITY_TYPE_CHOICES = [(x,x) for x in ACTIVITY_TYPES]
|
||||
|
||||
OTHER_OBJECT_TYPES = set([
|
||||
# https://www.w3.org/TR/activitystreams-vocabulary/
|
||||
|
||||
'Actor', 'Application', 'Group', 'Organization', 'Person', 'Service',
|
||||
|
||||
'Article', 'Audio', 'Document', 'Event', 'Image', 'Note', 'Page',
|
||||
'Place', 'Profile', 'Relationship', 'Video',
|
||||
|
||||
])
|
||||
def _new_number():
|
||||
return '%08x' % (random.randint(0, 0xffffffff),)
|
||||
|
||||
######################
|
||||
|
||||
|
@ -46,7 +29,7 @@ class Thing(PolymorphicModel):
|
|||
max_length=8,
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
default='',
|
||||
default=_new_number,
|
||||
)
|
||||
|
||||
f_type = models.CharField(
|
||||
|
@ -58,24 +41,16 @@ class Thing(PolymorphicModel):
|
|||
max_length=255,
|
||||
unique=True,
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
f_actor = models.URLField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
f_name = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
active = models.BooleanField(
|
||||
default=True,
|
||||
)
|
||||
|
||||
other_fields = models.TextField(
|
||||
blank=True,
|
||||
default='',
|
||||
)
|
||||
|
||||
|
@ -97,10 +72,7 @@ class Thing(PolymorphicModel):
|
|||
else:
|
||||
inactive_warning = ' INACTIVE'
|
||||
|
||||
if self.f_name:
|
||||
details = self.f_name
|
||||
else:
|
||||
details = self.url
|
||||
details = self.url
|
||||
|
||||
result = '[%s %s %s%s]' % (
|
||||
self.number,
|
||||
|
@ -131,7 +103,6 @@ class Thing(PolymorphicModel):
|
|||
|
||||
items.extend( [
|
||||
('actor', self.f_actor),
|
||||
('_name', self.f_name),
|
||||
('_number', self.number),
|
||||
('_remote_url', self.remote_url),
|
||||
] )
|
||||
|
@ -177,8 +148,6 @@ class Thing(PolymorphicModel):
|
|||
|
||||
if value=='':
|
||||
value = None
|
||||
else:
|
||||
value = json.loads(value)
|
||||
|
||||
result[name[2:]] = value
|
||||
|
||||
|
@ -372,33 +341,6 @@ class Thing(PolymorphicModel):
|
|||
self['object'] = creation
|
||||
self.save()
|
||||
|
||||
elif f_type in OTHER_OBJECT_TYPES:
|
||||
# XXX only if this came in via a local inbox
|
||||
logger.debug('New Thing is not an activity: %s',
|
||||
str(self.activity_form))
|
||||
logger.debug('We must create a Create wrapper for it.')
|
||||
|
||||
from django_kepi.create import create
|
||||
wrapper = create(
|
||||
f_type = 'Create',
|
||||
f_actor = self.f_actor,
|
||||
to = self['to'],
|
||||
cc = self['cc'],
|
||||
f_object = self.url,
|
||||
run_side_effects = False,
|
||||
)
|
||||
|
||||
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 is_local(self):
|
||||
|
||||
|
@ -438,13 +380,10 @@ class Thing(PolymorphicModel):
|
|||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if not self.number:
|
||||
self.number = '%08x' % (random.randint(0, 0xffffffff),)
|
||||
|
||||
try:
|
||||
super().save(*args, **kwargs)
|
||||
except IntegrityError:
|
||||
self.number = None
|
||||
self.number = _new_number()
|
||||
return self.save(*args, **kwargs)
|
||||
|
||||
######################################
|
||||
|
@ -469,3 +408,5 @@ def _normalise_type_for_thing(v):
|
|||
return v.activity_form
|
||||
except AttributeError:
|
||||
return str(v)
|
||||
|
||||
|
||||
|
|
|
@ -191,10 +191,10 @@ class ThingView(KepiView):
|
|||
)
|
||||
|
||||
elif 'name' in kwargs:
|
||||
logger.debug('Looking up Thing by name==%s',
|
||||
logger.debug('Looking up Actor by name==%s',
|
||||
kwargs['name'])
|
||||
activity_object = Thing.objects.get(
|
||||
f_name=kwargs['name'],
|
||||
activity_object = Actor.objects.get(
|
||||
f_preferredUsername=kwargs['name'],
|
||||
)
|
||||
else:
|
||||
raise ValueError("Need an id or a name")
|
||||
|
@ -217,9 +217,8 @@ class FollowingView(KepiView):
|
|||
|
||||
logger.debug('Finding following of %s:', kwargs['name'])
|
||||
|
||||
person = Thing.objects.get(
|
||||
f_type='Person',
|
||||
f_name = kwargs['name'],
|
||||
person = Actor.objects.get(
|
||||
f_preferredUsername=kwargs['name'],
|
||||
)
|
||||
|
||||
logging.debug('Finding followers of %s: %s',
|
||||
|
@ -237,9 +236,8 @@ class FollowersView(KepiView):
|
|||
|
||||
logger.debug('Finding followers of %s:', kwargs['name'])
|
||||
|
||||
person = Thing.objects.get(
|
||||
f_type='Person',
|
||||
f_name=kwargs['name'],
|
||||
person = Actor.objects.get(
|
||||
f_preferredUsername=kwargs['name'],
|
||||
)
|
||||
|
||||
logging.debug('Finding followers of %s: %s',
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import os
|
||||
import djcelery
|
||||
djcelery.setup_loader()
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
ROOT_URLCONF = 'kepi.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'kepi.wsgi.application'
|
||||
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'cmfy8%_q^u#bix$_4bq!p^8eq@=46bb*a7ztmg4i)l8jo(kl%^'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
KEPI = {
|
||||
'ACTIVITY_URL_FORMAT': 'https://altair.example.com/%s',
|
||||
'LOCAL_OBJECT_HOSTNAME': 'example.com',
|
||||
'FOLLOWERS_PATH': '/user/%(username)s/followers',
|
||||
'FOLLOWING_PATH': '/user/%(username)s/followers',
|
||||
'INBOX_PATH': '/user/%(username)s/inbox',
|
||||
'OUTBOX_PATH': '/user/%(username)s/outbox',
|
||||
}
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
INSTALLED_APPS = (
|
||||
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'djcelery',
|
||||
'django_celery_results',
|
||||
|
||||
'django_kepi',
|
||||
'polymorphic',
|
||||
|
||||
)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'kepi.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'altair.example.com',
|
||||
'sirius.example.com',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'kepi.urls'
|
||||
|
||||
CELERY = {
|
||||
'task_ignore_result': True,
|
||||
}
|
||||
|
||||
LOGGING = {
|
||||
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_true': {
|
||||
'()': 'django.utils.log.RequireDebugTrue',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'filters': ['require_debug_true'],
|
||||
'class': 'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django_kepi': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-gb'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
|
@ -0,0 +1,25 @@
|
|||
"""kepi URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/2.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path, include
|
||||
import django_kepi.urls
|
||||
|
||||
urlpatterns = [
|
||||
path(r'admin/', admin.site.urls),
|
||||
# path('', kepi.views.FrontPageView.as_view()), # or something
|
||||
path(r'', include(django_kepi.urls)),
|
||||
]
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for kepi project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kepi.settings')
|
||||
|
||||
application = get_wsgi_application()
|
24
manage.py
24
manage.py
|
@ -1,13 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# this is a manage.py for reusable libraries
|
||||
# it is dedicated to the public domain
|
||||
# feel free to copy it into your project
|
||||
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_settings")
|
||||
|
||||
def main():
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kepi.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
from django.test import TestCase
|
||||
from tests import create_local_note, create_local_person
|
||||
from django_kepi.create import create
|
||||
from django_kepi.models import *
|
||||
import logging
|
||||
|
||||
SENDER_ID = 'https://example.com/actor'
|
||||
SENDER_DOMAIN = 'example.com'
|
||||
SENDER_FOLLOWERS = 'https://example.com/followers'
|
||||
|
||||
logger = logging.getLogger(name='tests')
|
||||
|
||||
# XXX Why does this only test updating of profiles?
|
||||
# XXX I thought we should update items as well.
|
||||
|
||||
class TestUpdate(TestCase):
|
||||
|
||||
def test_update_profile(self):
|
||||
|
||||
sender = create_local_person()
|
||||
|
||||
object_json = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': 'foo',
|
||||
'type': 'Update',
|
||||
'actor': sender,
|
||||
'object': actor_json,
|
||||
}
|
||||
self.assertEqual(
|
||||
sender.display_name,
|
||||
"Totally modified now",
|
||||
)
|
||||
|
||||
|
||||
hidden = """
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Update do
|
||||
let!(:sender) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
stub_request(:get, actor_json[:outbox]).to_return(status: 404)
|
||||
stub_request(:get, actor_json[:followers]).to_return(status: 404)
|
||||
stub_request(:get, actor_json[:following]).to_return(status: 404)
|
||||
stub_request(:get, actor_json[:featured]).to_return(status: 404)
|
||||
|
||||
sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender))
|
||||
end
|
||||
|
||||
let(:modified_sender) do
|
||||
sender.dup.tap do |modified_sender|
|
||||
modified_sender.display_name = 'Totally modified now'
|
||||
end
|
||||
end
|
||||
|
||||
let(:actor_json) do
|
||||
ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, key_transform: :camel_lower).as_json
|
||||
end
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Update',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: actor_json,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'updates profile' do
|
||||
expect(sender.reload.display_name).to eq 'Totally modified now'
|
||||
end
|
||||
end
|
||||
end
|
||||
"""
|
Ładowanie…
Reference in New Issue