diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 4c574b4c76c43eb1b796b9bb341edddc86e7e07f..bff43b233481b45a91c1e2ff53d1e235c6743838 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -83,6 +83,7 @@ if RAVEN_ENABLED: # Apps specific for this project go here. LOCAL_APPS = ( 'funkwhale_api.common', + 'funkwhale_api.activity.apps.ActivityConfig', 'funkwhale_api.users', # custom users app # Your stuff: custom apps go here 'funkwhale_api.instance', diff --git a/api/funkwhale_api/activity/apps.py b/api/funkwhale_api/activity/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..0c66cbf50cf2542499ccce1fbdb262a13600324d --- /dev/null +++ b/api/funkwhale_api/activity/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig, apps + +from . import record + +class ActivityConfig(AppConfig): + name = 'funkwhale_api.activity' + + def ready(self): + super(ActivityConfig, self).ready() + + app_names = [app.name for app in apps.app_configs.values()] + record.registry.autodiscover(app_names) diff --git a/api/funkwhale_api/activity/record.py b/api/funkwhale_api/activity/record.py new file mode 100644 index 0000000000000000000000000000000000000000..fa55c0e85288318acd2f3d41ab02de9805eb8632 --- /dev/null +++ b/api/funkwhale_api/activity/record.py @@ -0,0 +1,38 @@ +import persisting_theory + + +class ActivityRegistry(persisting_theory.Registry): + look_into = 'activities' + + def _register_for_model(self, model, attr, value): + key = model._meta.label + d = self.setdefault(key, {'consumers': []}) + d[attr] = value + + def register_serializer(self, serializer_class): + model = serializer_class.Meta.model + self._register_for_model(model, 'serializer', serializer_class) + return serializer_class + + def register_consumer(self, label): + def decorator(func): + consumers = self[label]['consumers'] + if func not in consumers: + consumers.append(func) + return func + return decorator + + +registry = ActivityRegistry() + + + + +def send(obj): + conf = registry[obj.__class__._meta.label] + consumers = conf['consumers'] + if not consumers: + return + serializer = conf['serializer'](obj) + for consumer in consumers: + consumer(data=serializer.data, obj=obj) diff --git a/api/funkwhale_api/common/channels.py b/api/funkwhale_api/common/channels.py new file mode 100644 index 0000000000000000000000000000000000000000..a2f85ee4e1df0a8d33cf67d80b56ed7886fdf1e8 --- /dev/null +++ b/api/funkwhale_api/common/channels.py @@ -0,0 +1,5 @@ +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer + +channel_layer = get_channel_layer() +group_send = async_to_sync(channel_layer.group_send) diff --git a/api/tests/activity/test_record.py b/api/tests/activity/test_record.py new file mode 100644 index 0000000000000000000000000000000000000000..41846ba6f109cdc94b24c4e1ab01fb9812065310 --- /dev/null +++ b/api/tests/activity/test_record.py @@ -0,0 +1,45 @@ +import pytest + +from django.db import models +from rest_framework import serializers + +from funkwhale_api.activity import record + + +class FakeModel(models.Model): + class Meta: + app_label = 'tests' + + +class FakeSerializer(serializers.ModelSerializer): + class Meta: + model = FakeModel + fields = ['id'] + + + + +def test_can_bind_serializer_to_model(activity_registry): + activity_registry.register_serializer(FakeSerializer) + + assert activity_registry['tests.FakeModel']['serializer'] == FakeSerializer + + +def test_can_bind_consumer_to_model(activity_registry): + activity_registry.register_serializer(FakeSerializer) + @activity_registry.register_consumer('tests.FakeModel') + def propagate(data, obj): + return True + + assert activity_registry['tests.FakeModel']['consumers'] == [propagate] + + +def test_record_object_calls_consumer(activity_registry, mocker): + activity_registry.register_serializer(FakeSerializer) + stub = mocker.stub() + activity_registry.register_consumer('tests.FakeModel')(stub) + o = FakeModel(id=1) + data = FakeSerializer(o).data + record.send(o) + + stub.assert_called_once_with(data=data, obj=o) diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 10d7c323512c684ff495bda2a2a7a7ae581213f8..2d655f23f28dd2ea1ad56d4073df1147451c603c 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -5,6 +5,7 @@ from django.core.cache import cache as django_cache from dynamic_preferences.registries import global_preferences_registry from rest_framework.test import APIClient +from funkwhale_api.activity import record from funkwhale_api.taskapp import celery @@ -81,3 +82,28 @@ def superuser_client(db, factories, client): setattr(client, 'user', user) yield client delattr(client, 'user') + + +@pytest.fixture +def activity_registry(): + r = record.registry + state = list(record.registry.items()) + yield record.registry + record.registry.clear() + for key, value in state: + record.registry[key] = value + + +@pytest.fixture +def activity_registry(): + r = record.registry + state = list(record.registry.items()) + yield record.registry + record.registry.clear() + for key, value in state: + record.registry[key] = value + + +@pytest.fixture +def activity_muted(activity_registry, mocker): + yield mocker.patch.object(record, 'send')