See #170: expose/store actor URL over federation

This commit is contained in:
Eliot Berriot 2020-02-07 10:48:17 +01:00
commit b351ea67e2
10 changed files with 160 additions and 14 deletions

View file

@ -25,6 +25,7 @@ class ChannelFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
music_factories.ArtistFactory,
attributed_to=factory.SelfAttribute("..attributed_to"),
)
rss_url = factory.Faker("url")
metadata = factory.LazyAttribute(lambda o: {})
class Meta:

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.10 on 2020-02-06 15:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audio', '0002_channel_metadata'),
]
operations = [
migrations.AddField(
model_name='channel',
name='rss_url',
field=models.URLField(blank=True, max_length=500, null=True),
),
]

View file

@ -36,6 +36,7 @@ class Channel(models.Model):
"music.Library", on_delete=models.CASCADE, related_name="channel"
)
creation_date = models.DateTimeField(default=timezone.now)
rss_url = models.URLField(max_length=500, null=True, blank=True)
# metadata to enhance rss feed
metadata = JSONField(
@ -46,6 +47,9 @@ class Channel(models.Model):
return federation_utils.full_url("/channels/{}".format(self.uuid))
def get_rss_url(self):
if not self.artist.is_local:
return self.rss_url
return federation_utils.full_url(
reverse("api:v1:channels-rss", kwargs={"uuid": self.uuid})
)

View file

@ -191,6 +191,7 @@ class ChannelSerializer(serializers.ModelSerializer):
artist = serializers.SerializerMethodField()
actor = federation_serializers.APIActorSerializer()
attributed_to = federation_serializers.APIActorSerializer()
rss_url = serializers.CharField(source="get_rss_url")
class Meta:
model = models.Channel
@ -201,6 +202,7 @@ class ChannelSerializer(serializers.ModelSerializer):
"actor",
"creation_date",
"metadata",
"rss_url",
]
def get_artist(self, obj):

View file

@ -167,7 +167,7 @@ def prepare_for_serializer(payload, config, fallbacks={}):
attr=field_config.get("attr"),
)
except (IndexError, KeyError):
aliases = field_config.get("aliases", [])
aliases = field_config.get("aliases", {})
noop = object()
value = noop
if not aliases:
@ -176,9 +176,7 @@ def prepare_for_serializer(payload, config, fallbacks={}):
for a in aliases:
try:
value = get_value(
payload[a],
keep=field_config.get("keep"),
attr=field_config.get("attr"),
payload[a["property"]], keep=a.get("keep"), attr=a.get("attr"),
)
except (IndexError, KeyError):
continue
@ -231,14 +229,20 @@ def get_default_context_fw():
class JsonLdSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
self.jsonld_expand = kwargs.pop("jsonld_expand", True)
super().__init__(*args, **kwargs)
def run_validation(self, data=empty):
if data and data is not empty and self.context.get("expand", True):
try:
data = expand(data)
except ValueError:
raise serializers.ValidationError(
"{} is not a valid jsonld document".format(data)
)
if data and data is not empty:
if self.context.get("expand", self.jsonld_expand):
try:
data = expand(data)
except ValueError:
raise serializers.ValidationError(
"{} is not a valid jsonld document".format(data)
)
try:
config = self.Meta.jsonld_mapping
except AttributeError:
@ -247,6 +251,7 @@ class JsonLdSerializer(serializers.Serializer):
fallbacks = self.Meta.jsonld_fallbacks
except AttributeError:
fallbacks = {}
data = prepare_for_serializer(data, config, fallbacks=fallbacks)
dereferenced_fields = [
k

View file

@ -254,6 +254,17 @@ class Actor(models.Model):
except ObjectDoesNotExist:
return None
def get_channel(self):
try:
return self.channel
except ObjectDoesNotExist:
return None
def get_absolute_url(self):
if self.is_local:
return federation_utils.full_url("/@{}".format(self.preferred_username))
return self.url or self.fid
def get_current_usage(self):
actor = self.__class__.objects.filter(pk=self.pk).with_current_usage().get()
data = {}

View file

@ -91,6 +91,17 @@ class ImageSerializer(MediaSerializer):
return validated_data
class URLSerializer(jsonld.JsonLdSerializer):
href = serializers.URLField(max_length=500)
mediaType = serializers.CharField(required=False)
class Meta:
jsonld_mapping = {
"href": jsonld.first_id(contexts.AS.href, aliases=[jsonld.raw("@id")]),
"mediaType": jsonld.first_val(contexts.AS.mediaType),
}
class EndpointsSerializer(jsonld.JsonLdSerializer):
sharedInbox = serializers.URLField(max_length=500, required=False)
@ -105,10 +116,19 @@ class PublicKeySerializer(jsonld.JsonLdSerializer):
jsonld_mapping = {"publicKeyPem": jsonld.first_val(contexts.SEC.publicKeyPem)}
def get_by_media_type(urls, media_type):
for url in urls:
if url.get("mediaType", "text/html") == media_type:
return url
class ActorSerializer(jsonld.JsonLdSerializer):
id = serializers.URLField(max_length=500)
outbox = serializers.URLField(max_length=500, required=False)
inbox = serializers.URLField(max_length=500, required=False)
url = serializers.ListField(
child=URLSerializer(jsonld_expand=False), required=False, min_length=0
)
type = serializers.ChoiceField(
choices=[getattr(contexts.AS, c[0]) for c in models.TYPE_CHOICES]
)
@ -144,6 +164,7 @@ class ActorSerializer(jsonld.JsonLdSerializer):
"mediaType": jsonld.first_val(contexts.AS.mediaType),
"endpoints": jsonld.first_obj(contexts.AS.endpoints),
"icon": jsonld.first_obj(contexts.AS.icon),
"url": jsonld.raw(contexts.AS.url),
}
def to_representation(self, instance):
@ -165,6 +186,36 @@ class ActorSerializer(jsonld.JsonLdSerializer):
if instance.summary_obj_id:
ret["summary"] = instance.summary_obj.rendered
urls = []
if instance.url:
urls.append(
{"type": "Link", "href": instance.url, "mediaType": "text/html"}
)
channel = instance.get_channel()
if channel:
ret["url"] = [
{
"type": "Link",
"href": instance.channel.get_absolute_url()
if instance.channel.artist.is_local
else instance.get_absolute_url(),
"mediaType": "text/html",
},
{
"type": "Link",
"href": instance.channel.get_rss_url(),
"mediaType": "application/rss+xml",
},
]
else:
ret["url"] = [
{
"type": "Link",
"href": instance.get_absolute_url(),
"mediaType": "text/html",
}
]
ret["@context"] = jsonld.get_default_context()
if instance.public_key:
@ -192,6 +243,10 @@ class ActorSerializer(jsonld.JsonLdSerializer):
"name": self.validated_data.get("name"),
"preferred_username": self.validated_data["preferredUsername"],
}
url = get_by_media_type(self.validated_data.get("url", []), "text/html")
if url:
kwargs["url"] = url["href"]
maf = self.validated_data.get("manuallyApprovesFollowers")
if maf is not None:
kwargs["manually_approves_followers"] = maf