## Questions: * How should registration of SCRUD resource work in Django * For example, where should the registration happen, and what should that imply? ### Ideal Answer Start with a `resource_types` table and autoload that from `conf.py` based on static resources in the application. This would require metaclasses that know how to read that table and generate the models, serializers, and Django REST Framework views dynamically based on the configured SCRUD resources. #### Stepping Stones Toward Ideal for django 1. Define a model for `Resource` a. It has the fields `schema` and `context` that are both `Resource` links. Nullable. b. It has a field `content` that is the JSON for the content 2. Define a serializer for `Resource`. 3. Define a DRF view for `Resource`. 4. Define a way to "register" `Resource` instances based on static files in the app. ```python #Django, phase 1 class Resource(Model): # The actual JSON content for this resource content = JsonField() # JSON-Schema for this resource schema = ForeignKey(Resource, nullable=True) # JSON-LD for this resource context = ForeignKey(Resource, nullable=True) modified_at = DateTime() etag = StringField() ``` ```python #Django, phase 2, we could start here... # this will support something like GET /resource_type/schema?uri=http://schema.org/person # to get the json-schema for Person # or GET /resource_type/context?uri=http://schema.org/person # to get the json-ld for Person class ResourceType(Model): type_uri: StringField() # Necessary IFF there is a collection endpoint # associated with this ResourceType slug: StringField(nullable=true) # require either schema or schema_uri schema = ForeignKey(Resource, nullable=True) schema_uri = StringField(nullable=True) # require either context or context_uri context = ForeignKey(Resource, nullable=True) context_uri = StringField(nullable=True) class Resource(Model): # The actual JSON content for this resource content = JsonField() resource_type = ForeignKey(ResourceType) modified_at = DateTime() etag = StringField() ``` #### FASTAPI approach, start with ABC typing 1. Generate the schema and context from the types! ```python class Resource(Model): content: Content schema: Schema context: Context modified_at: DateTime etag: String ``` ```python from scrud_django import register_resource_type ... # This results in the creation of the model, serializer, view, etc # for the resource specified by the json schema and json-ld context register_resource_type( 'my_resources', resource_type_uri, json_schema, json_ld_context) register_resource_type( collection_slug='mystuff', schema=json_schema, context=json_ld_context, mixins=[my_query_mixin, my_search_mixin] ) ``` ```json [{ "url": {} , "last_modified": "" , "etag": "" , "content": { "prop1": "foo" , "prop2": "bar" , "really_big_property": "url": "/path/to/big/image.png" } } , {} ] ``` ```javascript contextRdf.lookup("foo.bar[2].baz") -> "http://api.openteams.com/json-ld/baz" contextRdf.supertypes("http://api.openteams.com/json-ld/baz") -> ["http://schema.org/Person"] ``` ```json { "next": "" , "prev": "" , "results": [{ "url": "" , "last_modified": "" , "etag": "" , "content": { # what is the json-ld and json schema of this? ... } }] } ``` ```json { "context": "jsonld" , "@id": "https://api.openteams.com/json-ld/PeopleQueryResult" , "next": {} , "prev": {} "results": { "@id": "https://api.openteams.com/json-ld/PeopleQueryMatch" "isa": "https://api.openteams.com/json-ld/EnvelopeArray" "contains": "https://api.openteams.com/json-ld/Person" "schema": "https://api.openteams.com/schema/Person" } } ``` # Envelopes We're going to need a json-ld approach to indicating that a property is an envelope. What's more, we'll need to indicate the type of the resource contained within the envelope! ## *Key Thing to Remember* The envelopes have caching headers! So, when processing a resource, if the json-ld context specifies `Envelope` or `EnvelopeArray` properties - cache their contents as resources by grabbing the caching headers in the containing envelope. # Resource with an envelope Let's imagine a resource that contains a "status" resource in an envelope. In this example, this composite resource contains a `status` that is another resource. ```json { ... "status": { "url": "/thing/42/status" , "last_modified": "" , "etag": "" , "content": { "state": "in-progress" } } ... } ``` So, what would the json-ld look like? ```json { "openteams": "https://api.openteams.com/json-ld/" ... "status": { "@type": "https://api.openteams.com/json-ld/Envelope" , "content": { "@type": "https://api/openteams.com/json-ld/Status" } } } ``` So... if I have this right, the `status.content` property is a `https://api.openteams.com/json-ld/Status` resource. ## What about collections? Let's say I had a collection of Partner Profile resources ```json { ... "profiles": [ { "url": "" , "last_modified": "" , "etag": "" , "content": { ... } } ] } ``` How do we indicate that the elements of the `profiles` array are of resource type `https://api.openteams.com/json-ld/PartnerProfile`? ```json { ... "profiles": { "@type": "https://api.openteams.com/json-ld/EnvelopeArray" , "content": "https://api.openteams.com/json-ld/PartnerProfile" } } ``` # Service Catalog For the Django app, we're going to need a way to advertise endpoints available from the app to bootstrap the client. GET this from `https://backend.openteams.com/services`: ```json { "https://api.openteams.com/json-ld/PartnerProfile-Listing": "https://backend.openteams.com/partner-profiles" , ... } ``` The above indicates that there is a "Partner Profile Listing" resource (`https://api.openteams.com/json-ld/PartnerProfile-Listing`) located at `https://backend.openteams.com/partner-profiles`. ## What about just an array? ```json ["https://backend.openteams.com/partner-profiles" , "https://backend.openteams.com/client-profiles" , "https://backend.openteams.com/user" , "https://backend.openteams.com/initiatives" , "https://backend.openteams.com/projects" ] ``` The problem is in bootstrapping the client - you would have to perform OPTIONS on every element in the list to get the mapping needed. Let's assume a "configured" client - that is, it's set up to start from somewhere, like, listing several of the items. It's poor form to perform a bunch of calls to build the RDF Resource Type -> URL map just to get the client started.