noirbizarre / flask-restplus

Fully featured framework for fast, easy and documented API development with Flask
http://flask-restplus.readthedocs.org
Other
2.74k stars 506 forks source link

Flatting nested field to array in Flask API #786

Open datawookie opened 4 years ago

datawookie commented 4 years ago

I asked this question on StackOverflow (here) but didn't get any help there. I'm sure that there is a simple answer to this.

I'm using flask_restplus to build an API.

I have two models:

model_barcode = api.model('Barcode', {
        'code': fields.String,
    })

and

model_product = api.model('Product', {
        'id': fields.Integer,
        'name': fields.String,
        'barcodes': fields.Nested(model_barcode, as_list=True),
    })

The relationship between Product and Barcode is many-to-many.

The API response looks like this:

[
    {
        "id": 15707,
        "name": "Jojo Leaf Eater - White",
        "barcodes": [
            {
                "code": "6009702666853"
            },
            {
                "code": "9317118010229"
            },
            {
                "code": "6009194082315"
            },
            {
                "code": "6009149569649"
            }
        ]
    }
]

However, since the contents of the barcode model are just a single field I want it to marshal like this instead:

[
    {
        "id": 15707,
        "name": "Jojo Leaf Eater - White",
        "barcodes": ["6009702666853", "9317118010229",
                     "6009194082315", "6009149569649"]
    }
]

How would I do this?

I've tried wrapping the call to fields.Nested() in fields.List() but that did not help.

If anybody has an idea of how to make this work, I'd really appreciate the help!

Thanks.

Background Information

Here are the relevant package versions:

Flask==1.1.1
flask-restplus==0.13.0
marshmallow==3.3.0
SQLAlchemy==1.3.11
simplejson==3.17.0

Database Classes

Here are the definitions of the SQLAlchemy classes:

class Product(Base):
    __tablename__ = 'product'

    id                  = Column(Integer, primary_key=True)
    name                = Column(String(256))
    barcodes            = relationship('Barcode',
                                       secondary='product_barcode',
                                       back_populates='products')

class Barcode(Base):
    __tablename__ = 'barcode'

    id                  = Column(Integer, primary_key=True)
    code                = Column(String(15))
    products            = relationship('Product',
                                       secondary='product_barcode',
                                       back_populates='barcodes')

Alternative Implementation

I have a working implementation using Marshmallow.

from marshmallow import Schema, fields

class BarcodeSchema(Schema):
    class Meta:
        fields = ('id', 'code',)

class ProductDetailSchema(Schema):
    barcodes = fields.Pluck(BarcodeSchema, "code", many=True)
    class Meta:
        fields = ('id', 'name', 'barcodes')
        ordered = False

This does precisely what I want. However I'd really prefer to use flask_restplus models because they make the code for the actual API a lot neater.

aqibgatoo commented 4 years ago

Though late to the party but you could have done it easliy like this 'barcodes': fields.List(fields.String, attribute=lambda p: [product.code for product in p.barcodes])