plone / plone.restapi

RESTful API for Plone.
http://plonerestapi.readthedocs.org/
84 stars 73 forks source link

Plone 6.0.4 Endpoint @querystring-search GET vs POST - GET Method didn’t return Metadata Fields #1628

Closed 1letter closed 1 year ago

1letter commented 1 year ago

https://community.plone.org/t/plone-6-0-4-endpoint-querystring-search-get-vs-post-get-method-didnt-return-metadata-fields/17407

In Plone 6.0.4 a GET Request to REST-API Endpoint @querystring-search is possible. I see two different results if i change from POST to GET Method. My Question: Why? It's a Bug or a Feature? The requested metadata_fields are ignored in the GET Request, but in the POST Request all is fine.

Endpoint @querystring-search with POST Method

const url = "http://myplone.local/@querystring-search"
const data = {
    "query": [{
        "i": "portal_type",
        "o": "plone.app.querystring.operation.selection.is",
        "v": ["News Item"]
        }],
    "metadata_fields": ["effective", "CreatorFullname", "targetgroup", "getIcon", "image_scales"],
    "sort_on": "effective",
    "sort_order": "descending",
    "fullobjects": false,
    "limit": 30,
    "b_start": 0,
    "b_size": 30
};

const options = {
    headers: {
        "Content-Type": "application/json", Accept: "application/json",
    },
    credentials: "same-origin",
    method : "POST",
    body : JSON.stringify(data)
};

fetch(url, options)

This is one result item in the response of the POST request:

{
      "@id": "http://myplone.local/Aktuell/News/copy3_of_bild-4-3", 
      "@type": "News Item", 
      "CreatorFullname": "Doe, John", 
      "description": "Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Donec id justo. Fusce ac felis sit amet ligula pharetra condimentum. Nulla sit amet est. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.", 
      "effective": "2023-04-21T05:29:00+00:00", 
      "getIcon": true, 
      "image_scales": {
        "image": [
          {
            "content-type": "image/jpeg", 
            "download": "@@images/image-4136-04a944fe06aa9f1a42c0899e300b3677.jpeg", 
            "filename": "1-4136x4136.jpg", 
            "height": 4136, 
            "scales": {
              .... some scales ....
              "thumb": {
                "download": "@@images/image-128-773c62682359d8a47d7c288293c279f1.jpeg", 
                "height": 128, 
                "width": 128
              }, 
              "tile": {
                "download": "@@images/image-64-778fd62fc2e4a7fe9c1e17884084310b.jpeg", 
                "height": 64, 
                "width": 64
              }
            }, 
            "size": 1324745, 
            "width": 4136
          }
        ]
      }, 
      "review_state": "external", 
      "targetgroup": "Clinic", 
      "title": "Bild 2:2"
    },

Endpoint @querystring-search with GET Method

const url = "http://myplone.local/@querystring-search"
const data = {
    "query": [{
        "i": "portal_type",
        "o": "plone.app.querystring.operation.selection.is",
        "v": ["News Item"]
        }],
    "metadata_fields": ["effective", "CreatorFullname", "targetgroup", "getIcon", "image_scales"],
    "sort_on": "effective",
    "sort_order": "descending",
    "fullobjects": false,
    "limit": 30,
    "b_start": 0,
    "b_size": 30
};

const options = {
    headers: {
        "Content-Type": "application/json", Accept: "application/json",
    },
    credentials: "same-origin",
    method : "GET",
};

url = url + "?query=" + encodeURIComponent(JSON.stringify(data))

fetch(url, options)

This is one result item in the response of the GET request:

{
  "@id": "http://myplone.local/Aktuell/News/copy_of_bild-4-3", 
  "@type": "News Item", 
  "description": "Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Donec id justo. Fusce ac felis sit amet ligula pharetra condimentum. Nulla sit amet est. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.", 
  "review_state": "external", 
  "title": "Bild 3:2"
}, 
tisto commented 1 year ago

@1letter thanks for reporting this!

@davisagli @robgietema I guess this is something we want to investigate...

1letter commented 1 year ago

@tisto @davisagli @robgietema

The Problem is the DefaultJSONSummarySerializer

In the QuerystringSearchGet Service the Get Paramter are copied to self.request["BODY"] but the Serializer use self.request.form to calculate the metadata fields.

def metadata_fields(self):
        query = self.request.form

        if not query:
            # maybe its a POST request
            query = json_body(self.request)

        print(query)
        """
        GET
        {'query': '%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22News%20Item%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22image_scales%22%2C%20%22getIcon%22%5D%7D'}
        {'query': '%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22News%20Item%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22image_scales%22%2C%20%22getIcon%22%5D%7D'}
        {'query': '%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22News%20Item%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22image_scales%22%2C%20%22getIcon%22%5D%7D'}

        POST
        {'query': [{'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.is', 'v': ['News Item']}], 'metadata_fields': ['image_scales', 'getIcon']}
        {'query': [{'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.is', 'v': ['News Item']}], 'metadata_fields': ['image_scales', 'getIcon']}
        {'query': [{'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.is', 'v': ['News Item']}], 'metadata_fields': ['image_scales', 'getIcon']}
        """

        additional_metadata_fields = query.get("metadata_fields", [])
        if not isinstance(additional_metadata_fields, list):
            additional_metadata_fields = [additional_metadata_fields]
        additional_metadata_fields = set(additional_metadata_fields)

        if "_all" in additional_metadata_fields:
            fields_cache = self.request.get("_summary_fields_cache", None)
            if fields_cache is None:
                catalog = getToolByName(self.context, "portal_catalog")
                fields_cache = set(catalog.schema()) | self.non_metadata_attributes
                self.request.set("_summary_fields_cache", fields_cache)
            additional_metadata_fields = fields_cache

        return self.default_metadata_fields | additional_metadata_fields
1letter commented 1 year ago

Sidenote: self.request["BODY"] is an encoded string b'xxxx' not 'xxxxx'

Maybe that's the cleaner solution

class QuerystringSearchGet(Service):
    """Returns the querystring search results given a p.a.querystring data."""

    def reply(self):
        # We need to copy the JSON query parameters from the querystring
        # into the request body, because that's where other code expects to find them
        self.request["BODY"] = parse.unquote(
            self.request.form.get("query", "{}")
        ).encode(self.request.charset)
        querystring_search = QuerystringSearch(self.context, self.request)
        return querystring_search()