音楽で楽しみましょう!-Let's have fun with music!-
Signed-off-by: Shin'ya Minazuki <shinyoukai@laidback.moe>
This commit is contained in:
parent
7c3206bf83
commit
54c6d22102
517 changed files with 637 additions and 639 deletions
0
api/funquail_api/activity/__init__.py
Normal file
0
api/funquail_api/activity/__init__.py
Normal file
13
api/funquail_api/activity/apps.py
Normal file
13
api/funquail_api/activity/apps.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from django.apps import AppConfig, apps
|
||||
|
||||
from . import record
|
||||
|
||||
|
||||
class ActivityConfig(AppConfig):
|
||||
name = "funkwhale_api.activity"
|
||||
|
||||
def ready(self):
|
||||
super().ready()
|
||||
|
||||
app_names = [app.name for app in apps.app_configs.values()]
|
||||
record.registry.autodiscover(app_names)
|
||||
37
api/funquail_api/activity/record.py
Normal file
37
api/funquail_api/activity/record.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
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)
|
||||
23
api/funquail_api/activity/serializers.py
Normal file
23
api/funquail_api/activity/serializers.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from funkwhale_api.activity import record
|
||||
|
||||
|
||||
class ModelSerializer(serializers.ModelSerializer):
|
||||
id = serializers.CharField(source="get_activity_url")
|
||||
local_id = serializers.IntegerField(source="id")
|
||||
# url = serializers.SerializerMethodField()
|
||||
|
||||
def get_url(self, obj):
|
||||
return self.get_id(obj)
|
||||
|
||||
|
||||
class AutoSerializer(serializers.Serializer):
|
||||
"""
|
||||
A serializer that will automatically use registered activity serializers
|
||||
to serialize an henerogeneous list of objects (favorites, listenings, etc.)
|
||||
"""
|
||||
|
||||
def to_representation(self, instance):
|
||||
serializer = record.registry[instance._meta.label]["serializer"](instance)
|
||||
return serializer.data
|
||||
51
api/funquail_api/activity/utils.py
Normal file
51
api/funquail_api/activity/utils.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from django.db import models
|
||||
|
||||
from funkwhale_api.common import fields
|
||||
from funkwhale_api.favorites.models import TrackFavorite
|
||||
from funkwhale_api.history.models import Listening
|
||||
|
||||
|
||||
def combined_recent(limit, **kwargs):
|
||||
datetime_field = kwargs.pop("datetime_field", "creation_date")
|
||||
source_querysets = {qs.model._meta.label: qs for qs in kwargs.pop("querysets")}
|
||||
querysets = {
|
||||
k: qs.annotate(
|
||||
__type=models.Value(qs.model._meta.label, output_field=models.CharField())
|
||||
).values("pk", datetime_field, "__type")
|
||||
for k, qs in source_querysets.items()
|
||||
}
|
||||
_qs_list = list(querysets.values())
|
||||
union_qs = _qs_list[0].union(*_qs_list[1:])
|
||||
records = []
|
||||
for row in union_qs.order_by(f"-{datetime_field}")[:limit]:
|
||||
records.append(
|
||||
{"type": row["__type"], "when": row[datetime_field], "pk": row["pk"]}
|
||||
)
|
||||
# Now we bulk-load each object type in turn
|
||||
to_load = {}
|
||||
for record in records:
|
||||
to_load.setdefault(record["type"], []).append(record["pk"])
|
||||
fetched = {}
|
||||
|
||||
for key, pks in to_load.items():
|
||||
for item in source_querysets[key].filter(pk__in=pks):
|
||||
fetched[(key, item.pk)] = item
|
||||
|
||||
# Annotate 'records' with loaded objects
|
||||
for record in records:
|
||||
record["object"] = fetched[(record["type"], record["pk"])]
|
||||
return records
|
||||
|
||||
|
||||
def get_activity(user, limit=20):
|
||||
query = fields.privacy_level_query(user, lookup_field="user__privacy_level")
|
||||
querysets = [
|
||||
Listening.objects.filter(query).select_related(
|
||||
"track", "user", "track__artist", "track__album__artist"
|
||||
),
|
||||
TrackFavorite.objects.filter(query).select_related(
|
||||
"track", "user", "track__artist", "track__album__artist"
|
||||
),
|
||||
]
|
||||
records = combined_recent(limit=limit, querysets=querysets)
|
||||
return [r["object"] for r in records]
|
||||
20
api/funquail_api/activity/views.py
Normal file
20
api/funquail_api/activity/views.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
|
||||
from funkwhale_api.common.permissions import ConditionalAuthentication
|
||||
from funkwhale_api.favorites.models import TrackFavorite
|
||||
|
||||
from . import serializers, utils
|
||||
|
||||
|
||||
class ActivityViewSet(viewsets.GenericViewSet):
|
||||
serializer_class = serializers.AutoSerializer
|
||||
permission_classes = [ConditionalAuthentication]
|
||||
queryset = TrackFavorite.objects.none()
|
||||
|
||||
@extend_schema(operation_id="get_activity")
|
||||
def list(self, request, *args, **kwargs):
|
||||
activity = utils.get_activity(user=request.user)
|
||||
serializer = self.serializer_class(activity, many=True)
|
||||
return Response({"results": serializer.data}, status=200)
|
||||
Loading…
Add table
Add a link
Reference in a new issue