简体   繁体   English

在 Marshallow / SQLAlchemy Schema 上展平数据

[英]Flatten data on Marshallow / SQLAlchemy Schema

I'm having some issues with how I'm getting my data back from one of my endpoints - specifically, with Marshmallow and SQLAlchemy.我在如何从我的端点之一取回数据方面遇到了一些问题 - 特别是使用 Marshmallow 和 SQLAlchemy。

I have a many-to-many relationship between cocktails and ingredients, but I also have more data than just foreign keys on the relational table, ings_in_cocktail, such as ounces.我有鸡尾酒和配料之间的多对多关系,但我还有更多的数据,而不仅仅是关系表上的外键,ings_in_cocktail,例如ounces. When I GET /cocktails/ , it returns something like this:当我 GET /cocktails/ ,它返回如下内容:

{
  "cocktails": [
    {
      "glass": "rocks",
      "ingredients": [
        {
          "ingredient": {
            "ing_type": "liquor",
            "id": 1,
            "name": "gin"
          },
          "ounces": 20
        }
      ],
      "finish": "stirred",
      "id": 1,
      "name": "gin and tonic"
    }
  ]
}

What I'd like to do is combine the spread the ounces property with the ingredient dict.我想做的是将ounces属性与ingredient dict 结合起来。

I want the data to look like the following:我希望数据如下所示:

{
  "cocktails": [
    {
      "glass": "rocks",
      "ingredients": [
        {
          "ing_type": "liquor",
          "id": 1,
          "name": "gin",
          "ounces": 20
        }
      ],
      "finish": "stirred",
      "id": 1,
      "name": "gin and tonic"
    }
  ]
}

After searching the web for hours, I can't find a way to do this easily with Marshmallow.在网上搜索了几个小时后,我找不到使用 Marshmallow 轻松做到这一点的方法。 Is there some easy way I'm missing?有什么简单的方法我想念吗?

Code代码

ingredients.py成分.py

from flask import Flask
from settings import db, ma

class Ingredient(db.Model):
    __tablename__ = 'ingredients'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    ing_type = db.Column(db.String(20), nullable=False)

class IngredientSchema(ma.ModelSchema):
    class Meta:
        model = Ingredient

ings_in_cocktail.py ings_in_cocktail.py

from flask import Flask
from settings import db, ma

from models.ingredients import Ingredient, IngredientSchema

class CocktailIngredient(db.Model):
    __tablename__ = 'ings_in_cocktail'
    ing_id = db.Column(db.Integer, db.ForeignKey('ingredients.id'), primary_key=True)
    cocktail_id = db.Column(db.Integer, db.ForeignKey('cocktails.id'), primary_key=True)
    ounces = db.Column(db.Integer, nullable=False)

    ingredient = db.relationship('Ingredient')

# Necessary for transforming sqlalchemy data into serialized JSON


class CocktailIngredientSchema(ma.ModelSchema):
    ingredient = ma.Nested(IngredientSchema, strict=True)

    class Meta:
    model = CocktailIngredient

cocktails.py鸡尾酒.py

from flask import Flask
from settings import db, ma

from models.ing_in_cocktails import CocktailIngredient, CocktailIngredientSchema
from models.ingredients import Ingredient, IngredientSchema

class Cocktail(db.Model):
    __tablename__ = 'cocktails'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    glass = db.Column(db.String(20), nullable=False)
    finish = db.Column(db.String(20), nullable=True)
    ingredients = db.relationship(
        'CocktailIngredient',
        # secondary='ings_in_cocktail',
        backref=db.backref('cocktails'),
        # primaryjoin=id == CocktailIngredient.cocktail_id
    )

# Necessary for transforming sqlalchemy data into serialized JSON
class CocktailSchema(ma.ModelSchema):
    # this is responsible for returning all the ingredient data on the cocktail
    ingredients = ma.Nested(CocktailIngredientSchema, many=True, strict=True)
    class Meta:
    model = Cocktail

Update: I put a better solution at the bottom.更新:我在底部放了一个更好的解决方案。

You can achieve this by using Method or Function fields to allow a parent to get data from its child.您可以通过使用MethodFunction字段来允许父级从其子级获取数据来实现此目的。 Here is what you need to do:以下是您需要做的:

  1. Leave CocktailSchema alone.不理会CocktailSchema
  2. Get rid of IngredientSchema entirely (unless you also need it for something else).完全摆脱IngredientSchema (除非您还需要它来做其他事情)。
  3. Inside CocktailIngredientSchema , replace the Nested field with several Function fields that pull data right out of the inner "ingredient" object.CocktailIngredientSchema ,用几个Function字段替换Nested字段,这些Function字段直接从内部“成分”对象中提取数据。

Schemas Before之前的架构

class IngredientSchema(ma.ModelSchema):
    class Meta:
        model = Ingredient


class CocktailIngredientSchema(ma.ModelSchema):
    ingredient = ma.Nested(IngredientSchema)

    class Meta:
        model = CocktailIngredient


class CocktailSchema(ma.ModelSchema):
    ingredients = ma.Nested(CocktailIngredientSchema, many=True)

    class Meta:
        model = Cocktail

Schemas After之后的架构

class CocktailIngredientSchema(ma.ModelSchema):
    ing_type = ma.Function(lambda obj: obj.ingredient.ing_type)
    id = ma.Function(lambda obj: obj.ingredient.id)
    name = ma.Function(lambda obj: obj.ingredient.name)

    class Meta:
        model = CocktailIngredient


class CocktailSchema(ma.ModelSchema):
    ingredients = ma.Nested(CocktailIngredientSchema, many=True)

    class Meta:
        model = Cocktail

A cleaner alternative for marshmallow version 3+ (requires Python 3)棉花糖版本 3+ 的更清洁替代品(需要 Python 3)

  1. Leave CocktailSchema alone.不理会CocktailSchema
  2. Leave IngredientSchema alone (instead of getting rid of it as shown above).单独留下IngredientSchema (而不是如上所示摆脱它)。
  3. Inside CocktailIngredientSchema , replace the Nested field with several Pluck fields that pull data right out of the inner schema.CocktailIngredientSchema ,用几个Pluck字段替换Nested字段,这些Pluck字段将数据直接从内部架构中提取出来。

Schemas After之后的架构

class IngredientSchema(ma.ModelSchema):
    class Meta:
        model = Ingredient


class CocktailIngredientSchema(ma.ModelSchema):
    ing_type = ma.Pluck(IngredientSchema, 'ing_type')
    id = ma.Pluck(IngredientSchema, 'id')
    name = ma.Pluck(IngredientSchema, 'name')

    class Meta:
        model = CocktailIngredient


class CocktailSchema(ma.ModelSchema):
    ingredients = ma.Nested(CocktailIngredientSchema, many=True)

    class Meta:
        model = Cocktail

You can using a method field in IngredientSchema您可以在IngredientSchema使用方法字段

https://marshmallow.readthedocs.io/en/stable/custom_fields.html#method-fields https://marshmallow.readthedocs.io/en/stable/custom_fields.html#method-fields

Please check this to see how to use this field in documents.请检查此项以了解如何在文档中使用此字段。

I ended up solving it like this:我最终是这样解决的:

class CocktailSchema(ma.ModelSchema):
    # this is responsible for returning all the ingredient data on the cocktail
    ingredients = ma.Nested(CocktailIngredientSchema, many=True, strict=True)
    ingredients = fields.Method('concat_ingredients_dicts')

    """
    at this point the ingredients field on the cocktail object looks something like this

    ingredients: [{
        ingredient: {
            name: 'white russian',
            glass: 'rocks',
            finish: 'stirred'
        },
        ounces: 2,
        action: 'muddle',
        step: 1
    }]

    what we want is to concat this data so "ingredients" just turns
    into an list of dicts
    """
    def concat_ingredients_dicts(self, obj):
        result_ingredients_list = []
        i = 0
        while i < len(list(obj.ingredients)):
            # create a dict from the fields that live in the relational table
            relational_fields_dict = {
                'ounces': obj.ingredients[i].ounces,
                'action': obj.ingredients[i].action,
                'step': obj.ingredients[i].step
            }

            # create a dict from the fields on each ingredient in the cocktail
            ingredients_dict = obj.ingredients[i].ingredient.__dict__
            ingredients_dict_extracted_values = {
                'name': ingredients_dict.get('name'),
                'type': ingredients_dict.get('ing_type'),
                'id': ingredients_dict.get('id')
            }

            # merge the two dicts together
            merged = dict()
            merged.update(ingredients_dict_extracted_values)
            merged.update(relational_fields_dict)

            # append this merged dict a result array
            result_ingredients_list.append(merged)
            i += 1
        # return the array of ingredients
        return result_ingredients_list

    class Meta:
      model = Cocktail

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM