DRF保存模型触发AttributeError:Ingredient无ingredient属性
DRF保存模型时AttributeError错误排查
错误信息
Got AttributeError when attempting to get a value for field
idon serializerIngredientSerializer. The serializer field might be named incorrectly and not match any attribute or key on theIngredientinstance. Original exception text was: 'Ingredient' object has no attribute 'ingredient'.
使用普通序列化器和ModelSerializer均触发该错误,当前Django版本为3.2.25。
相关代码
序列化器代码
from django.db import transaction from rest_framework import serializers from .models import Recipe, Ingredient, RecipeIngredient, Tag class IngredientSerializer(serializers.ModelSerializer): id = serializers.PrimaryKeyRelatedField(queryset=Ingredient.objects.all, source="ingredient") amount = serializers.IntegerField() class Meta: model = RecipeIngredient fields = ["id", "amount"] class CreateRecipeSerializer(serializers.ModelSerializer): ingredients = IngredientSerializer(many=True) tags=serializers.PrimaryKeyRelatedField(...) image = serializers.ImageField(required=False) name = serializers.CharField(source="recipe_name") class Meta: model = Recipe fields = ["name", "image", "text", "cooking_time", "ingredients", "tags"] def create(self, validated_data): ingredients_data = validated_data.pop("ingredients") tags_data = validated_data.pop("tags") with transaction.atomic(): recipe = Recipe.objects.create(**validated_data) recipe.tags.set(tags_data) recipe_ingredients = [ RecipeIngredient( recipe=recipe, ingredient=ingredient_data["ingredient"], amount=ingredient_data["amount"], ) for ingredient_data in ingredients_data] RecipeIngredient.objects.bulk_create(recipe_ingredients) return recipe
模型代码
class Ingredient(models.Model): ingredient_name = models.CharField(max_length=MAX_LENGTH_200) measurement_unit = models.CharField(max_length=MAX_LENGTH_200) class Recipe(models.Model): ingredients = models.ManyToManyField(Ingredient, through="RecipeIngredient", related_name="recipes") tags = models.ManyToManyField(Tag, related_name="recipes") author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="recipes") recipe_name = models.CharField(max_length=MAX_LENGTH_200) image = models.ImageField(upload_to="recipes/images/") text = models.TextField() cooking_time = models.PositiveIntegerField(validators=[MinValueValidator(1),MaxValueValidator(50000)]) class RecipeIngredient(models.Model): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name="recipe_ingredients") ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, related_name="ingredients") amount = models.PositiveIntegerField()
错误根源
CreateRecipeSerializer绑定的模型是Recipe,其ingredients字段是关联Ingredient的多对多字段,但你将该字段指定为IngredientSerializer(绑定RecipeIngredient模型),序列化逻辑冲突。- 当DRF序列化
Recipe实例返回时,会把Ingredient对象传入IngredientSerializer,而该序列化器中定义的id = serializers.PrimaryKeyRelatedField(..., source="ingredient")会尝试从Ingredient对象中获取ingredient属性——但Ingredient模型不存在该字段,因此抛出AttributeError。 - 另外,
queryset=Ingredient.objects.all缺少括号,应改为queryset=Ingredient.objects.all(),否则传递的是方法对象而非查询集。
修复方案
方案一:仅用嵌套序列化器处理创建输入(推荐)
将CreateRecipeSerializer中的ingredients字段设为write_only=True,仅用于接收创建时的配料数据,返回时使用Recipe模型默认的ingredients序列化逻辑:
class IngredientSerializer(serializers.ModelSerializer): id = serializers.PrimaryKeyRelatedField(queryset=Ingredient.objects.all(), source='ingredient') amount = serializers.IntegerField() class Meta: model = RecipeIngredient fields = ["id", "amount"] class CreateRecipeSerializer(serializers.ModelSerializer): # 仅用于创建时接收数据,序列化返回时忽略 ingredients = IngredientSerializer(many=True, write_only=True) tags = serializers.PrimaryKeyRelatedField(queryset=Tag.objects.all(), many=True) image = serializers.ImageField(required=False) name = serializers.CharField(source="recipe_name") class Meta: model = Recipe fields = ["name", "image", "text", "cooking_time", "ingredients", "tags"] def create(self, validated_data): ingredients_data = validated_data.pop("ingredients") tags_data = validated_data.pop("tags") with transaction.atomic(): recipe = Recipe.objects.create(**validated_data) recipe.tags.set(tags_data) recipe_ingredients = [ RecipeIngredient( recipe=recipe, ingredient=ingredient_data["ingredient"], amount=ingredient_data["amount"], ) for ingredient_data in ingredients_data ] RecipeIngredient.objects.bulk_create(recipe_ingredients) return recipe
方案二:返回时包含配料数量
如果需要在返回Recipe时同时带出配料的使用数量,可自定义序列化逻辑,通过关联的RecipeIngredient获取数据:
class RecipeIngredientSerializer(serializers.ModelSerializer): id = serializers.PrimaryKeyRelatedField(source='ingredient.id', read_only=True) name = serializers.CharField(source='ingredient.ingredient_name', read_only=True) amount = serializers.IntegerField() class Meta: model = RecipeIngredient fields = ["id", "name", "amount"] class CreateRecipeSerializer(serializers.ModelSerializer): # 仅用于创建时接收数据 ingredients = serializers.ListField( child=serializers.DictField( child=serializers.CharField(), ), write_only=True ) # 返回时展示带数量的配料信息 recipe_ingredients = RecipeIngredientSerializer(source='recipe_ingredients', many=True, read_only=True) tags = serializers.PrimaryKeyRelatedField(queryset=Tag.objects.all(), many=True) image = serializers.ImageField(required=False) name = serializers.CharField(source="recipe_name") class Meta: model = Recipe fields = ["name", "image", "text", "cooking_time", "ingredients", "tags", "recipe_ingredients"] def create(self, validated_data): ingredients_data = validated_data.pop("ingredients") tags_data = validated_data.pop("tags") with transaction.atomic(): recipe = Recipe.objects.create(**validated_data) recipe.tags.set(tags_data) recipe_ingredients = [] for item in ingredients_data: ingredient = Ingredient.objects.get(id=item['id']) recipe_ingredients.append( RecipeIngredient( recipe=recipe, ingredient=ingredient, amount=int(item['amount']), ) ) RecipeIngredient.objects.bulk_create(recipe_ingredients) return recipe
内容的提问来源于stack exchange,提问作者SerhiiL
相关产品推荐
相关产品推荐

