Commit 9aa12db6 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

See #170: Funkwhale federation

parent fce4d875
......@@ -107,6 +107,4 @@ def generate_actor(username, **kwargs):
@receiver(post_delete, sender=Channel)
def delete_channel_related_objs(instance, **kwargs):
instance.library.delete()
if instance.actor != instance.attributed_to:
instance.actor.delete()
instance.artist.delete()
......@@ -13,10 +13,12 @@ from django.utils import timezone
from funkwhale_api.common import locales
from funkwhale_api.common import permissions
from funkwhale_api.common import preferences
from funkwhale_api.common import utils as common_utils
from funkwhale_api.common.mixins import MultipleLookupDetailMixin
from funkwhale_api.federation import actors
from funkwhale_api.federation import models as federation_models
from funkwhale_api.federation import routes
from funkwhale_api.federation import tasks as federation_tasks
from funkwhale_api.federation import utils as federation_utils
from funkwhale_api.music import models as music_models
from funkwhale_api.music import views as music_views
......@@ -128,6 +130,8 @@ class ChannelViewSet(
)
# prefetch stuff
subscription = SubscriptionsViewSet.queryset.get(pk=subscription.pk)
if not object.actor.is_local:
routes.outbox.dispatch({"type": "Follow"}, context={"follow": subscription})
data = serializers.SubscriptionSerializer(subscription).data
return response.Response(data, status=201)
......@@ -139,7 +143,15 @@ class ChannelViewSet(
)
def unsubscribe(self, request, *args, **kwargs):
object = self.get_object()
request.user.actor.emitted_follows.filter(target=object.actor).delete()
follow_qs = request.user.actor.emitted_follows.filter(target=object.actor)
follow = follow_qs.first()
if follow:
if not object.actor.is_local:
routes.outbox.dispatch(
{"type": "Undo", "object": {"type": "Follow"}},
context={"follow": follow},
)
follow_qs.delete()
return response.Response(status=204)
@decorators.action(
......@@ -248,11 +260,10 @@ class ChannelViewSet(
@transaction.atomic
def perform_destroy(self, instance):
routes.outbox.dispatch(
{"type": "Delete", "object": {"type": instance.actor.type}},
context={"actor": instance.actor},
)
instance.__class__.objects.filter(pk=instance.pk).delete()
common_utils.on_commit(
federation_tasks.remove_actor.delay, actor_id=instance.actor.pk
)
class SubscriptionsViewSet(
......
......@@ -7,6 +7,7 @@ from django.utils import timezone
from rest_framework import serializers
from funkwhale_api.audio import models as audio_models
from funkwhale_api.common import fields as common_fields
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.music import models as music_models
......@@ -171,6 +172,7 @@ FETCH_OBJECT_CONFIG = {
"library": {"queryset": music_models.Library.objects.all(), "id_attr": "uuid"},
"upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"},
"account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"},
"channel": {"queryset": audio_models.Channel.objects.all(), "id_attr": "uuid"},
}
FETCH_OBJECT_FIELD = common_fields.GenericRelation(FETCH_OBJECT_CONFIG)
......
from . import schema_org
CONTEXTS = [
{
"shortId": "LDP",
......@@ -218,6 +220,12 @@ CONTEXTS = [
}
},
},
{
"shortId": "SC",
"contextUrl": None,
"documentUrl": "http://schema.org",
"document": {"@context": schema_org.CONTEXT},
},
{
"shortId": "SEC",
"contextUrl": None,
......@@ -280,6 +288,7 @@ CONTEXTS = [
"type": "@type",
"as": "https://www.w3.org/ns/activitystreams#",
"fw": "https://funkwhale.audio/ns#",
"schema": "http://schema.org#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"Album": "fw:Album",
"Track": "fw:Track",
......@@ -298,6 +307,8 @@ CONTEXTS = [
"musicbrainzId": "fw:musicbrainzId",
"license": {"@id": "fw:license", "@type": "@id"},
"copyright": "fw:copyright",
"category": "schema:category",
"language": "schema:inLanguage",
}
},
},
......@@ -364,4 +375,5 @@ AS = NS(CONTEXTS_BY_ID["AS"])
LDP = NS(CONTEXTS_BY_ID["LDP"])
SEC = NS(CONTEXTS_BY_ID["SEC"])
FW = NS(CONTEXTS_BY_ID["FW"])
SC = NS(CONTEXTS_BY_ID["SC"])
LITEPUB = NS(CONTEXTS_BY_ID["LITEPUB"])
import logging
from django.db.models import Q
from funkwhale_api.music import models as music_models
from . import activity
......@@ -158,18 +160,26 @@ def outbox_create_audio(context):
@inbox.register({"type": "Create", "object.type": "Audio"})
def inbox_create_audio(payload, context):
serializer = serializers.UploadSerializer(
data=payload["object"],
context={"activity": context.get("activity"), "actor": context["actor"]},
)
is_channel = "library" not in payload["object"]
if is_channel:
channel = context["actor"].get_channel()
serializer = serializers.ChannelUploadSerializer(
data=payload["object"], context={"channel": channel},
)
else:
serializer = serializers.UploadSerializer(
data=payload["object"],
context={"activity": context.get("activity"), "actor": context["actor"]},
)
if not serializer.is_valid(raise_exception=context.get("raise_exception", False)):
logger.warn("Discarding invalid audio create")
return
upload = serializer.save()
return {"object": upload, "target": upload.library}
if is_channel:
return {"object": upload, "target": channel}
else:
return {"object": upload, "target": upload.library}
@inbox.register({"type": "Delete", "object.type": "Library"})
......@@ -252,9 +262,10 @@ def inbox_delete_audio(payload, context):
# we did not receive a list of Ids, so we can probably use the value directly
upload_fids = [payload["object"]["id"]]
candidates = music_models.Upload.objects.filter(
library__actor=actor, fid__in=upload_fids
query = Q(fid__in=upload_fids) & (
Q(library__actor=actor) | Q(track__artist__channel__actor=actor)
)
candidates = music_models.Upload.objects.filter(query)
total = candidates.count()
logger.info("Deleting %s uploads with ids %s", total, upload_fids)
......@@ -483,3 +494,44 @@ def outbox_flag(context):
to=[{"type": "actor_inbox", "actor": report.target_owner}],
),
}
@inbox.register({"type": "Delete", "object.type": "Album"})
def inbox_delete_album(payload, context):
actor = context["actor"]
album_id = payload["object"].get("id")
if not album_id:
logger.debug("Discarding deletion of empty library")
return
query = Q(fid=album_id) & (Q(attributed_to=actor) | Q(artist__channel__actor=actor))
try:
album = music_models.Album.objects.get(query)
except music_models.Album.DoesNotExist:
logger.debug("Discarding deletion of unkwnown album %s", album_id)
return
album.delete()
@outbox.register({"type": "Delete", "object.type": "Album"})
def outbox_delete_album(context):
album = context["album"]
actor = (
album.artist.channel.actor
if album.artist.get_channel()
else album.attributed_to
)
actor = actor or actors.get_service_actor()
serializer = serializers.ActivitySerializer(
{"type": "Delete", "object": {"type": "Album", "id": album.fid}}
)
yield {
"type": "Delete",
"actor": actor,
"payload": with_recipients(
serializer.data,
to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
),
}
CONTEXT = {
"type": "@type",
"id": "@id",
"HTML": {"@id": "rdf:HTML"},
"@vocab": "http://schema.org/",
"xml": "http://www.w3.org/XML/1998/namespace",
"foaf": "http://xmlns.com/foaf/0.1/",
"eli": "http://data.europa.eu/eli/ontology#",
"snomed": "http://purl.bioontology.org/ontology/SNOMEDCT/",
"bibo": "http://purl.org/ontology/bibo/",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"skos": "http://www.w3.org/2004/02/skos/core#",
"void": "http://rdfs.org/ns/void#",
"dc": "http://purl.org/dc/elements/1.1/",
"dctype": "http://purl.org/dc/dcmitype/",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"dcat": "http://www.w3.org/ns/dcat#",
"rdfa": "http://www.w3.org/ns/rdfa#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"schema": "http://schema.org/",
"dct": "http://purl.org/dc/terms/",
"dcterms": "http://purl.org/dc/terms/",
"owl": "http://www.w3.org/2002/07/owl#",
"3DModel": {"@id": "schema:3DModel"},
"AMRadioChannel": {"@id": "schema:AMRadioChannel"},
"APIReference": {"@id": "schema:APIReference"},
"Abdomen": {"@id": "schema:Abdomen"},
"AboutPage": {"@id": "schema:AboutPage"},
"AcceptAction": {"@id": "schema:AcceptAction"},
"Accommodation": {"@id": "schema:Accommodation"},
"AccountingService": {"@id": "schema:AccountingService"},
"AchieveAction": {"@id": "schema:AchieveAction"},
"Action": {"@id": "schema:Action"},
"ActionAccessSpecification": {"@id": "schema:ActionAccessSpecification"},
"ActionStatusType": {"@id": "schema:ActionStatusType"},
"ActivateAction": {"@id": "schema:ActivateAction"},
"ActiveActionStatus": {"@id": "schema:ActiveActionStatus"},
"ActiveNotRecruiting": {"@id": "schema:ActiveNotRecruiting"},
"AddAction": {"@id": "schema:AddAction"},
"AdministrativeArea": {"@id": "schema:AdministrativeArea"},
"AdultEntertainment": {"@id": "schema:AdultEntertainment"},
"AdvertiserContentArticle": {"@id": "schema:AdvertiserContentArticle"},
"AerobicActivity": {"@id": "schema:AerobicActivity"},
"AggregateOffer": {"@id": "schema:AggregateOffer"},
"AggregateRating": {"@id": "schema:AggregateRating"},
"AgreeAction": {"@id": "schema:AgreeAction"},
"Airline": {"@id": "schema:Airline"},
"Airport": {"@id": "schema:Airport"},
"AlbumRelease": {"@id": "schema:AlbumRelease"},
"AlignmentObject": {"@id": "schema:AlignmentObject"},
"AllWheelDriveConfiguration": {"@id": "schema:AllWheelDriveConfiguration"},
"AllocateAction": {"@id": "schema:AllocateAction"},
"AmusementPark": {"@id": "schema:AmusementPark"},
"AnaerobicActivity": {"@id": "schema:AnaerobicActivity"},
"AnalysisNewsArticle": {"@id": "schema:AnalysisNewsArticle"},
"AnatomicalStructure": {"@id": "schema:AnatomicalStructure"},
"AnatomicalSystem": {"@id": "schema:AnatomicalSystem"},
"Anesthesia": {"@id": "schema:Anesthesia"},
"AnimalShelter": {"@id": "schema:AnimalShelter"},
"Answer": {"@id": "schema:Answer"},
"Apartment": {"@id": "schema:Apartment"},
"ApartmentComplex": {"@id": "schema:ApartmentComplex"},
"Appearance": {"@id": "schema:Appearance"},
"AppendAction": {"@id": "schema:AppendAction"},
"ApplyAction": {"@id": "schema:ApplyAction"},
"ApprovedIndication": {"@id": "schema:ApprovedIndication"},
"Aquarium": {"@id": "schema:Aquarium"},
"ArchiveComponent": {"@id": "schema:ArchiveComponent"},
"ArchiveOrganization": {"@id": "schema:ArchiveOrganization"},
"ArriveAction": {"@id": "schema:ArriveAction"},
"ArtGallery": {"@id": "schema:ArtGallery"},
"Artery": {"@id": "schema:Artery"},
"Article": {"@id": "schema:Article"},
"AskAction": {"@id": "schema:AskAction"},
"AskPublicNewsArticle": {"@id": "schema:AskPublicNewsArticle"},
"AssessAction": {"@id": "schema:AssessAction"},
"AssignAction": {"@id": "schema:AssignAction"},
"Atlas": {"@id": "schema:Atlas"},
"Attorney": {"@id": "schema:Attorney"},
"Audience": {"@id": "schema:Audience"},
"AudioObject": {"@id": "schema:AudioObject"},
"Audiobook": {"@id": "schema:Audiobook"},
"AudiobookFormat": {"@id": "schema:AudiobookFormat"},
"AuthenticContent": {"@id": "schema:AuthenticContent"},
"AuthoritativeLegalValue": {"@id": "schema:AuthoritativeLegalValue"},
"AuthorizeAction": {"@id": "schema:AuthorizeAction"},
"AutoBodyShop": {"@id": "schema:AutoBodyShop"},
"AutoDealer": {"@id": "schema:AutoDealer"},
"AutoPartsStore": {"@id": "schema:AutoPartsStore"},
"AutoRental": {"@id": "schema:AutoRental"},
"AutoRepair": {"@id": "schema:AutoRepair"},
"AutoWash": {"@id": "schema:AutoWash"},
"AutomatedTeller": {"@id": "schema:AutomatedTeller"},
"AutomotiveBusiness": {"@id": "schema:AutomotiveBusiness"},
"Ayurvedic": {"@id": "schema:Ayurvedic"},
"BackgroundNewsArticle": {"@id": "schema:BackgroundNewsArticle"},
"Bacteria": {"@id": "schema:Bacteria"},
"Bakery": {"@id": "schema:Bakery"},
"Balance": {"@id": "schema:Balance"},
"BankAccount": {"@id": "schema:BankAccount"},
"BankOrCreditUnion": {"@id": "schema:BankOrCreditUnion"},
"BarOrPub": {"@id": "schema:BarOrPub"},
"Barcode": {"@id": "schema:Barcode"},
"Beach": {"@id": "schema:Beach"},
"BeautySalon": {"@id": "schema:BeautySalon"},
"BedAndBreakfast": {"@id": "schema:BedAndBreakfast"},
"BedDetails": {"@id": "schema:BedDetails"},
"BedType": {"@id": "schema:BedType"},
"BefriendAction": {"@id": "schema:BefriendAction"},
"BenefitsHealthAspect": {"@id": "schema:BenefitsHealthAspect"},
"BikeStore": {"@id": "schema:BikeStore"},
"Blog": {"@id": "schema:Blog"},
"BlogPosting": {"@id": "schema:BlogPosting"},
"BloodTest": {"@id": "schema:BloodTest"},
"BoardingPolicyType": {"@id": "schema:BoardingPolicyType"},
"BodyOfWater": {"@id": "schema:BodyOfWater"},
"Bone": {"@id": "schema:Bone"},
"Book": {"@id": "schema:Book"},
"BookFormatType": {"@id": "schema:BookFormatType"},
"BookSeries": {"@id": "schema:BookSeries"},
"BookStore": {"@id": "schema:BookStore"},
"BookmarkAction": {"@id": "schema:BookmarkAction"},
"Boolean": {"@id": "schema:Boolean"},
"BorrowAction": {"@id": "schema:BorrowAction"},
"BowlingAlley": {"@id": "schema:BowlingAlley"},
"BrainStructure": {"@id": "schema:BrainStructure"},
"Brand": {"@id": "schema:Brand"},
"BreadcrumbList": {"@id": "schema:BreadcrumbList"},
"Brewery": {"@id": "schema:Brewery"},
"Bridge": {"@id": "schema:Bridge"},
"BroadcastChannel": {"@id": "schema:BroadcastChannel"},
"BroadcastEvent": {"@id": "schema:BroadcastEvent"},
"BroadcastFrequencySpecification": {
"@id": "schema:BroadcastFrequencySpecification"
},
"BroadcastRelease": {"@id": "schema:BroadcastRelease"},
"BroadcastService": {"@id": "schema:BroadcastService"},
"BrokerageAccount": {"@id": "schema:BrokerageAccount"},
"BuddhistTemple": {"@id": "schema:BuddhistTemple"},
"BusOrCoach": {"@id": "schema:BusOrCoach"},
"BusReservation": {"@id": "schema:BusReservation"},
"BusStation": {"@id": "schema:BusStation"},
"BusStop": {"@id": "schema:BusStop"},
"BusTrip": {"@id": "schema:BusTrip"},
"BusinessAudience": {"@id": "schema:BusinessAudience"},
"BusinessEntityType": {"@id": "schema:BusinessEntityType"},
"BusinessEvent": {"@id": "schema:BusinessEvent"},
"BusinessFunction": {"@id": "schema:BusinessFunction"},
"BuyAction": {"@id": "schema:BuyAction"},
"CDFormat": {"@id": "schema:CDFormat"},
"CT": {"@id": "schema:CT"},
"CableOrSatelliteService": {"@id": "schema:CableOrSatelliteService"},
"CafeOrCoffeeShop": {"@id": "schema:CafeOrCoffeeShop"},
"Campground": {"@id": "schema:Campground"},
"CampingPitch": {"@id": "schema:CampingPitch"},
"Canal": {"@id": "schema:Canal"},
"CancelAction": {"@id": "schema:CancelAction"},
"Car": {"@id": "schema:Car"},
"CarUsageType": {"@id": "schema:CarUsageType"},
"Cardiovascular": {"@id": "schema:Cardiovascular"},
"CardiovascularExam": {"@id": "schema:CardiovascularExam"},
"CaseSeries": {"@id": "schema:CaseSeries"},
"Casino": {"@id": "schema:Casino"},
"CassetteFormat": {"@id": "schema:CassetteFormat"},
"CategoryCode": {"@id": "schema:CategoryCode"},
"CategoryCodeSet": {"@id": "schema:CategoryCodeSet"},
"CatholicChurch": {"@id": "schema:CatholicChurch"},
"CausesHealthAspect": {"@id": "schema:CausesHealthAspect"},
"Cemetery": {"@id": "schema:Cemetery"},
"Chapter": {"@id": "schema:Chapter"},
"CheckAction": {"@id": "schema:CheckAction"},
"CheckInAction": {"@id": "schema:CheckInAction"},
"CheckOutAction": {"@id": "schema:CheckOutAction"},
"CheckoutPage": {"@id": "schema:CheckoutPage"},
"ChildCare": {"@id": "schema:ChildCare"},
"ChildrensEvent": {"@id": "schema:ChildrensEvent"},
"Chiropractic": {"@id": "schema:Chiropractic"},
"ChooseAction": {"@id": "schema:ChooseAction"},
"Church": {"@id": "schema:Church"},
"City": {"@id": "schema:City"},
"CityHall": {"@id": "schema:CityHall"},
"CivicStructure": {"@id": "schema:CivicStructure"},
"Claim": {"@id": "schema:Claim"},
"ClaimReview": {"@id": "schema:ClaimReview"},
"Class": {"@id": "schema:Class"},
"Clinician": {"@id": "schema:Clinician"},
"Clip": {"@id": "schema:Clip"},
"ClothingStore": {"@id": "schema:ClothingStore"},
"CoOp": {"@id": "schema:CoOp"},
"Code": {"@id": "schema:Code"},
"CohortStudy": {"@id": "schema:CohortStudy"},
"Collection": {"@id": "schema:Collection"},
"CollectionPage": {"@id": "schema:CollectionPage"},
"CollegeOrUniversity": {"@id": "schema:CollegeOrUniversity"},
"ComedyClub": {"@id": "schema:ComedyClub"},
"ComedyEvent": {"@id": "schema:ComedyEvent"},
"ComicCoverArt": {"@id": "schema:ComicCoverArt"},
"ComicIssue": {"@id": "schema:ComicIssue"},
"ComicSeries": {"@id": "schema:ComicSeries"},
"ComicStory": {"@id": "schema:ComicStory"},
"Comment": {"@id": "schema:Comment"},
"CommentAction": {"@id": "schema:CommentAction"},
"CommentPermission": {"@id": "schema:CommentPermission"},
"CommunicateAction": {"@id": "schema:CommunicateAction"},
"CommunityHealth": {"@id": "schema:CommunityHealth"},
"CompilationAlbum": {"@id": "schema:CompilationAlbum"},
"CompleteDataFeed": {"@id": "schema:CompleteDataFeed"},
"Completed": {"@id": "schema:Completed"},
"CompletedActionStatus": {"@id": "schema:CompletedActionStatus"},
"CompoundPriceSpecification": {"@id": "schema:CompoundPriceSpecification"},
"ComputerLanguage": {"@id": "schema:ComputerLanguage"},
"ComputerStore": {"@id": "schema:ComputerStore"},
"ConfirmAction": {"@id": "schema:ConfirmAction"},
"Consortium": {"@id": "schema:Consortium"},
"ConsumeAction": {"@id": "schema:ConsumeAction"},
"ContactPage": {"@id": "schema:ContactPage"},
"ContactPoint": {"@id": "schema:ContactPoint"},
"ContactPointOption": {"@id": "schema:ContactPointOption"},
"ContagiousnessHealthAspect": {"@id": "schema:ContagiousnessHealthAspect"},
"Continent": {"@id": "schema:Continent"},
"ControlAction": {"@id": "schema:ControlAction"},
"ConvenienceStore": {"@id": "schema:ConvenienceStore"},
"Conversation": {"@id": "schema:Conversation"},
"CookAction": {"@id": "schema:CookAction"},
"Corporation": {"@id": "schema:Corporation"},
"CorrectionComment": {"@id": "schema:CorrectionComment"},
"Country": {"@id": "schema:Country"},
"Course": {"@id": "schema:Course"},
"CourseInstance": {"@id": "schema:CourseInstance"},
"Courthouse": {"@id": "schema:Courthouse"},
"CoverArt": {"@id": "schema:CoverArt"},
"CovidTestingFacility": {"@id": "schema:CovidTestingFacility"},
"CreateAction": {"@id": "schema:CreateAction"},
"CreativeWork": {"@id": "schema:CreativeWork"},
"CreativeWorkSeason": {"@id": "schema:CreativeWorkSeason"},
"CreativeWorkSeries": {"@id": "schema:CreativeWorkSeries"},
"CreditCard": {"@id": "schema:CreditCard"},
"Crematorium": {"@id": "schema:Crematorium"},
"CriticReview": {"@id": "schema:CriticReview"},
"CrossSectional": {"@id": "schema:CrossSectional"},
"CssSelectorType": {"@id": "schema:CssSelectorType"},
"CurrencyConversionService": {"@id": "schema:CurrencyConversionService"},
"DDxElement": {"@id": "schema:DDxElement"},
"DJMixAlbum": {"@id": "schema:DJMixAlbum"},
"DVDFormat": {"@id": "schema:DVDFormat"},
"DamagedCondition": {"@id": "schema:DamagedCondition"},
"DanceEvent": {"@id": "schema:DanceEvent"},
"DanceGroup": {"@id": "schema:DanceGroup"},
"DataCatalog": {"@id": "schema:DataCatalog"},
"DataDownload": {"@id": "schema:DataDownload"},
"DataFeed": {"@id": "schema:DataFeed"},
"DataFeedItem": {"@id": "schema:DataFeedItem"},
"DataType": {"@id": "schema:DataType"},
"Dataset": {"@id": "schema:Dataset"},
"Date": {"@id": "schema:Date"},
"DateTime": {"@id": "schema:DateTime"},
"DatedMoneySpecification": {"@id": "schema:DatedMoneySpecification"},
"DayOfWeek": {"@id": "schema:DayOfWeek"},
"DaySpa": {"@id": "schema:DaySpa"},
"DeactivateAction": {"@id": "schema:DeactivateAction"},
"DefenceEstablishment": {"@id": "schema:DefenceEstablishment"},
"DefinedTerm": {"@id": "schema:DefinedTerm"},
"DefinedTermSet": {"@id": "schema:DefinedTermSet"},
"DefinitiveLegalValue": {"@id": "schema:DefinitiveLegalValue"},
"DeleteAction": {"@id": "schema:DeleteAction"},
"DeliveryChargeSpecification": {"@id": "schema:DeliveryChargeSpecification"},
"DeliveryEvent": {"@id": "schema:DeliveryEvent"},
"DeliveryMethod": {"@id": "schema:DeliveryMethod"},
"Demand": {"@id": "schema:Demand"},
"DemoAlbum": {"@id": "schema:DemoAlbum"},
"Dentist": {"@id": "schema:Dentist"},
"Dentistry": {"@id": "schema:Dentistry"},
"DepartAction": {"@id": "schema:DepartAction"},
"DepartmentStore": {"@id": "schema:DepartmentStore"},
"DepositAccount": {"@id": "schema:DepositAccount"},
"Dermatologic": {"@id": "schema:Dermatologic"},
"Dermatology": {"@id": "schema:Dermatology"},
"DiabeticDiet": {"@id": "schema:DiabeticDiet"},
"Diagnostic": {"@id": "schema:Diagnostic"},
"DiagnosticLab": {"@id": "schema:DiagnosticLab"},
"DiagnosticProcedure": {"@id": "schema:DiagnosticProcedure"},
"Diet": {"@id": "schema:Diet"},
"DietNutrition": {"@id": "schema:DietNutrition"},
"DietarySupplement": {"@id": "schema:DietarySupplement"},
"DigitalAudioTapeFormat": {"@id": "schema:DigitalAudioTapeFormat"},
"DigitalDocument": {"@id": "schema:DigitalDocument"},
"DigitalDocumentPermission": {"@id": "schema:DigitalDocumentPermission"},
"DigitalDocumentPermissionType": {"@id": "schema:DigitalDocumentPermissionType"},
"DigitalFormat": {"@id": "schema:DigitalFormat"},
"DisagreeAction": {"@id": "schema:DisagreeAction"},
"Discontinued": {"@id": "schema:Discontinued"},
"DiscoverAction": {"@id": "schema:DiscoverAction"},
"DiscussionForumPosting": {"@id": "schema:DiscussionForumPosting"},
"DislikeAction": {"@id": "schema:DislikeAction"},
"Distance": {"@id": "schema:Distance"},
"Distillery": {"@id": "schema:Distillery"},
"DonateAction": {"@id": "schema:DonateAction"},
"DoseSchedule": {"@id": "schema:DoseSchedule"},
"DoubleBlindedTrial": {"@id": "schema:DoubleBlindedTrial"},
"DownloadAction": {"@id": "schema:DownloadAction"},
"DrawAction": {"@id": "schema:DrawAction"},
"Drawing": {"@id": "schema:Drawing"},
"DrinkAction": {"@id": "schema:DrinkAction"},
"DriveWheelConfigurationValue": {"@id": "schema:DriveWheelConfigurationValue"},
"DrivingSchoolVehicleUsage": {"@id": "schema:DrivingSchoolVehicleUsage"},
"Drug": {"@id": "schema:Drug"},
"DrugClass": {"@id": "schema:DrugClass"},
"DrugCost": {"@id": "schema:DrugCost"},
"DrugCostCategory": {"@id": "schema:DrugCostCategory"},
"DrugLegalStatus": {"@id": "schema:DrugLegalStatus"},
"DrugPregnancyCategory": {"@id": "schema:DrugPregnancyCategory"},
"DrugPrescriptionStatus": {"@id": "schema:DrugPrescriptionStatus"},
"DrugStrength": {"@id": "schema:DrugStrength"},
"DryCleaningOrLaundry": {"@id": "schema:DryCleaningOrLaundry"},
"Duration": {"@id": "schema:Duration"},
"EBook": {"@id": "schema:EBook"},
"EPRelease": {"@id": "schema:EPRelease"},
"Ear": {"@id": "schema:Ear"},
"EatAction": {"@id": "schema:EatAction"},
"EducationEvent": {"@id": "schema:EducationEvent"},
"EducationalAudience": {"@id": "schema:EducationalAudience"},
"EducationalOccupationalCredential": {
"@id": "schema:EducationalOccupationalCredential"
},
"EducationalOccupationalProgram": {"@id": "schema:EducationalOccupationalProgram"},
"EducationalOrganization": {"@id": "schema:EducationalOrganization"},
"Electrician": {"@id": "schema:Electrician"},
"ElectronicsStore": {"@id": "schema:ElectronicsStore"},
"ElementarySchool": {"@id": "schema:ElementarySchool"},
"EmailMessage": {"@id": "schema:EmailMessage"},
"Embassy": {"@id": "schema:Embassy"},
"Emergency": {"@id": "schema:Emergency"},
"EmergencyService": {"@id": "schema:EmergencyService"},
"EmployeeRole": {"@id": "schema:EmployeeRole"},
"EmployerAggregateRating": {"@id": "schema:EmployerAggregateRating"},
"EmployerReview": {"@id": "schema:EmployerReview"},
"EmploymentAgency": {"@id": "schema:EmploymentAgency"},
"Endocrine": {"@id": "schema:Endocrine"},
"EndorseAction": {"@id": "schema:EndorseAction"},
"EndorsementRating": {"@id": "schema:EndorsementRating"},
"Energy": {"@id": "schema:Energy"},
"EngineSpecification": {"@id": "schema:EngineSpecification"},
"EnrollingByInvitation": {"@id": "schema:EnrollingByInvitation"},
"EntertainmentBusiness": {"@id": "schema:EntertainmentBusiness"},
"EntryPoint": {"@id": "schema:EntryPoint"},
"Enumeration": {"@id": "schema:Enumeration"},
"Episode": {"@id": "schema:Episode"},
"Event": {"@id": "schema:Event"},
"EventAttendanceModeEnumeration": {"@id": "schema:EventAttendanceModeEnumeration"},
"EventCancelled": {"@id": "schema:EventCancelled"},
"EventMovedOnline": {"@id": "schema:EventMovedOnline"},
"EventPostponed": {"@id": "schema:EventPostponed"},
"EventRescheduled": {"@id": "schema:EventRescheduled"},
"EventReservation": {"@id": "schema:EventReservation"},
"EventScheduled": {"@id": "schema:EventScheduled"},
"EventSeries": {"@id": "schema:EventSeries"},
"EventStatusType": {"@id": "schema:EventStatusType"},
"EventVenue": {"@id": "schema:EventVenue"},
"EvidenceLevelA": {"@id": "schema:EvidenceLevelA"},
"EvidenceLevelB": {"@id": "schema:EvidenceLevelB"},
"EvidenceLevelC": {"@id": "schema:EvidenceLevelC"},
"ExchangeRateSpecification": {"@id": "schema:ExchangeRateSpecification"},