marshmallow-jsonapi icon indicating copy to clipboard operation
marshmallow-jsonapi copied to clipboard

Absolute links in included relationship data

Open pgpbpadilla opened this issue 7 years ago • 1 comments

Hi, I've been struggling to make the following work:

class CommentSchema(Schema):
    def __init__(self, prefix=None, *args, **kwargs):
        super(CommentSchema, self).__init__(*args, **kwargs)
        self.prefix = prefix
    
    id = fields.Str()
    content = fields.Str()

    def get_resource_links(self, item):
        return {
            'self': '{} - some link'.format(self.prefix)
        }
    
    class Meta:
        type_ = 'comments'
        strict = True
        self_url='/comments/{id}'
        self_url_kwargs={'id': '<id>'}

class PostSchema(Schema):
    def __init__(self, prefix, *args, **kwargs):
        super(PostSchema, self).__init__(*args, **kwargs)
        self.prefix = prefix

    id = fields.Str(dump_only=True)
    title = fields.Str()
    comments = fields.Relationship(
        include_resource_linkage=True,
        type_='comments',
        schema=CommentSchema # How to pass the prefix here?
    )
    class Meta:
        type_ = 'posts'
        strict = True
        self_url = '/posts/{id}'
        self_url_kwargs = { 'id': '<id>'}

Serializing a comment

This works as expected since I can pass the prefix to CommentSchema directly.

comment = comments=Model(id=123, content='blah')
data, errors = CommentSchema('something').dump(comment) # Passing `prefix=something`
print json.dumps(data, indent=4)

The resulting dictionary contains a links attribute with my custom links:

{
    "data": {
        "attributes": {
            "content": "blah"
        },
        "type": "comments",
        "id": "123",
        "links": {
            "self": "something - some link"
        }
    },
    "links": {
        "self": "something - some link"
    }
}

Serializing a compound document

However when serializing a compound document:

comment = comments=Model(id=123, content='blah')
post = Model(id=123, title='something', comments=comment)
data, errors = PostSchema('prefix').dump(post)
print json.dumps(data, indent=4)

the resulting included shows the links prefixed with None:

{
    "included": [
        {
            "attributes": {
                "content": "blah"
            },
            "type": "comments",
            "id": "123",
            "links": {
                "self": "None - some link"
            }
        }
    ],
    "data": {
        "relationships": {
            "comments": {
                "data": {
                    "type": "comments",
                    "id": "123"
                }
            }
        },
        "attributes": {
            "title": "something"
        },
        "type": "posts",
        "id": "123",
        "links": {
            "self": "/posts/123"
        }
    },
    "links": {
        "self": "/posts/123"
    }
}

How can I pass extra args (prefix) to the relationships schema=CommaSchema option?

 comments = fields.Relationship(
        include_resource_linkage=True,
        type_='comments',
        # Invalid since `self.prefix` does not exist yet
        schema=CommentSchema(self.prefix) 
    )

Maybe there's a better way to generate absolute links for relationship data, could you point me to an example if that's the case?

pgpbpadilla avatar Mar 23 '17 06:03 pgpbpadilla

You can pass the prefix in the schema's context attribute. The relationship field inherits the context from the parent schema which was fixed in marshmallow-jsonapi==0.19.0.

class CommentSchema(Schema):
    id = fields.Str()
    content = fields.Str()

    def get_resource_links(self, item):
        prefix = self.context['prefix']
        return {
            'self': '{} - comment link'.format(prefix)
        }

    class Meta:
        type_ = 'comments'
        strict = True
        self_url = '/comments/{id}'
        self_url_kwargs = {'id': '<id>'}


class PostSchema(Schema):
    id = fields.Str(dump_only=True)
    title = fields.Str()
    comments = fields.Relationship(
        include_resource_linkage=True,
        type_='comments',
        schema=CommentSchema  # How to pass the prefix here?
    )

    def get_resource_links(self, item):
        prefix = self.context['prefix']
        return {
            'self': '{} - post link'.format(prefix)
        }

    class Meta:
        type_ = 'posts'
        strict = True
        self_url = '/posts/{id}'
        self_url_kwargs = {'id': '<id>'}


comments = {'id': 123, 'content': 'blah'}
post = {'id': 123, 'title': 'something', 'comments': comments}
data = PostSchema(context={'prefix': 'prefix'}, include_data=('comments',)).dump(post)
print(json.dumps(data, indent=4))
{
    "data": {
        "type": "posts",
        "relationships": {
            "comments": {
                "data": {
                    "type": "comments",
                    "id": "123"
                }
            }
        },
        "id": "123",
        "attributes": {
            "title": "something"
        },
        "links": {
            "self": "prefix - post link"
        }
    },
    "links": {
        "self": "prefix - post link"
    },
    "included": [
        {
            "type": "comments",
            "attributes": {
                "content": "blah"
            },
            "id": "123",
            "links": {
                "self": "prefix - comment link"
            }
        }
    ]
}

scottwernervt avatar May 30 '18 20:05 scottwernervt