inveniosoftware / training

Invenio v3 Training Material
https://training.readthedocs.io
MIT License
39 stars 45 forks source link

tutorial 11: no such command 'records'. #74

Open diegodelemos opened 4 years ago

diegodelemos commented 4 years ago

Problem Tutorial 11 relies on creating records with references using the invenio records CLI. However, Invenio-Records CLI has been remove in version 1.3.0.

Solution Modify tutorial to enable the creation of linked records through the REST API.

ppanero commented 4 years ago

To use the REST API, we would have to change the loaders, since the author field is not defined

ZedThree commented 2 years ago

How exactly do the loaders need to be changed? Is it it just the author field needs adding to records.marshmallow.json.MetadataSchemaV1? What kind of field should it be?

Presumably there's also something that needs doing to the deposit form to find and link the authors?

ZedThree commented 2 years ago

A minimal change to create the record using the REST API appears to be:

modified   my_site/records/marshmallow/json.py
@@ -8,7 +8,7 @@
 """JSON Schemas."""

 from invenio_jsonschemas import current_jsonschemas
-from invenio_records_rest.schemas import Nested, StrictKeysMixin
+from invenio_records_rest.schemas import Nested, StrictKeysMixin, RecordMetadataSchemaJSONV1
 from invenio_records_rest.schemas.fields import DateString, GenFunction, \
     PersistentIdentifier, SanitizedUnicode
 from marshmallow import fields, missing, validate
@@ -54,7 +54,7 @@ class ContributorSchemaV1(StrictKeysMixin):
     email = fields.Email()

-class MetadataSchemaV1(StrictKeysMixin):
+class MetadataSchemaV1(RecordMetadataSchemaJSONV1):
     """Schema for the record metadata."""

     id = PersistentIdentifier()

Which allows creation with:

curl -k --header Content-Type: application/json \
  --request POST \
  --data {"author": { "$ref": "https://my-site.com/api/resolver/author/1" }, \
     "title": "Invenio is awesome", "contributors": [{"name": "Kent, Clark"}], \
    "owner": 1}
  https://127.0.0.1:5000/api/records/?prettyprint=1

If I'm understanding how this works correctly, StrictKeysMixin will fail because author is not defined in MetadataSchemaV1, while RecordMetadataSchemaJSONV1 just passes along any keys it doesn't recognise.

However, retrieving the record doesn't include the author in the REST API:

$ curl -ks https://localhost:5000/api/records/2?prettyprint=1
{
  "id": "2", 
  "links": {
    "files": "https://localhost:5000/api/records/2/files", 
    "self": "https://localhost:5000/api/records/2"
  }, 
  "metadata": {
    "contributors": [
      {
        "name": "Kent, Clark"
      }
    ], 
    "id": "2", 
    "owner": 1, 
    "pid": "2", 
    "title": "Invenio is awesome"
  }, 
  "revision": 0, 
  "updated": "2022-07-11T09:42:17.536711+00:00"

and in the web view, author is there but it doesn't get resolved: image

So, has this just moved the problem onto the mapping? Sticking a print(record) into record_jsonresolver, I can see that it is correctly resolving the author when the record is retrieved via the REST API, though not through the web interface.

ZedThree commented 2 years ago

Further investigation reveals that searching for author.name in /api/records/ works with the above change, it's just the serialisation where it disappears.

I looked into that further, and the author reference does get resolved when access via REST, but marshmallow removes it, I guess because it's not in MetadataSchemaV1. The web interface doesn't call the resolver at all.

I've been looking at invenio-app-ils as an example that has links between data models, and I can't see how that handles anything differently, except for the change I suggest above.

Is this the expected behaviour?

ZedThree commented 2 years ago

I think I have something working, I'm just not sure if it's sensible:

modified   my_site/records/config.py
@@ -66,6 +66,7 @@ RECORDS_UI_ENDPOINTS = dict(
         route='/records/<pid_value>',
         template='records/record.html',
         record_class='invenio_records_files.api:Record',
+        view_imp="my_site.records.views:record_view",
     ),
     recid_previewer=dict(
         pid_type='recid',
modified   my_site/records/marshmallow/json.py
@@ -63,6 +63,7 @@ class MetadataSchemaV1(StrictKeysMixin):
     publication_date = DateString()
     contributors = Nested(ContributorSchemaV1, many=True, required=True)
     owner = fields.Integer()
+    author = fields.Dict()
     _schema = GenFunction(
         attribute="$schema",
         data_key="$schema",
modified   my_site/records/views.py
@@ -12,6 +12,7 @@ from os.path import splitext

 from flask import Blueprint
 from invenio_previewer.proxies import current_previewer
+from invenio_records_ui.views import default_view_method

 blueprint = Blueprint(
     'my_site_records',
@@ -46,3 +47,6 @@ def select_preview_file(files):
     except KeyError:
         pass
     return selected
+
+def record_view(pid, record, template=None, **kwargs):
+    return default_view_method(pid, record.replace_refs(), template, **kwargs)

Instead of using RecordMetadataSchemaJSONV1, we still use StrictKeysMixin, but add a field for the author that's just fields.Dict(), so we don't need to define its schema. This allows us to both create a record with an author reference, and also allows us to view the resolved author in the record REST API.

The other half is to add a custom view that just calls the default view but passes record.replace_refs() which resolves the references.

If this seems like a sensible solution, I'd be happy to make a PR adding this to the tutorial. If not, I'd really appreciate some pointers on how to fix this properly!