You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何构建动态关联字段序列化器,实现UserLocation的创建与JSON返回?

问题描述

现有以下Django模型:

class Country(models.Model):
    """
    Country model
    """
    # With name and isoCode (charfield) 
    ...
class State(models.Model):
    """
    State model
    """
    # With name and isoCode (charfield) 
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    ...
class City(models.Model):
    """
    City model
    """
    name = models.CharField(max_length=32)
    state = models.ForeignKey(State, on_delete=models.CASCADE)
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    ...

关联用户的Location模型:

class Location(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="location")
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    state = models.ForeignKey(State, on_delete=models.CASCADE)
    city = models.ForeignKey(City, on_delete=models.CASCADE)

需要构建一个序列化器,同时支持创建UserLocation和以JSON格式返回相关信息。尝试编写的序列化器如下:

class LocationSerializer(serializers.ModelSerializer):
    country = serializers.SlugRelatedField(slug_field="isoCode", queryset=Country.objects.all())
    state = serializers.SlugRelatedField(slug_field="isoCode", queryset=State.objects.filter(country__isoCode=country))
    city = serializers.SlugRelatedField(slug_field="name", queryset=City.objects.all())
    class Meta:
        model = Location
        fields = ["user", "country", "state", "city"]

运行时报错:

{"state":["Object with isoCode=BC does not exist."],...}

求解决动态关联序列化器的可行方案。


解决方案

原序列化器的核心问题是:初始化state的queryset时,country是序列化器字段对象,并非请求传入的实际isoCode值,导致过滤逻辑完全失效。以下是几种可行的解决方式:

方法一:自定义动态SlugRelatedField

通过重写get_queryset方法,根据请求传入的前置数据动态过滤关联查询集,同时支持多级关联校验:

class DynamicSlugRelatedField(serializers.SlugRelatedField):
    def __init__(self, filter_field, slug_lookup, *args, **kwargs):
        self.filter_field = filter_field
        self.slug_lookup = slug_lookup
        super().__init__(*args, **kwargs)

    def get_queryset(self):
        queryset = super().get_queryset()
        data = self.context["request"].data
        filter_value = data.get(self.filter_field)
        
        if filter_value:
            filter_kwargs = {f"{self.slug_lookup}": filter_value}
            queryset = queryset.filter(**filter_kwargs)
        return queryset

class LocationSerializer(serializers.ModelSerializer):
    country = serializers.SlugRelatedField(slug_field="isoCode", queryset=Country.objects.all())
    # 根据传入的country isoCode过滤state
    state = DynamicSlugRelatedField(
        slug_field="isoCode",
        queryset=State.objects.all(),
        filter_field="country",
        slug_lookup="country__isoCode"
    )
    # 根据传入的state isoCode过滤city
    city = DynamicSlugRelatedField(
        slug_field="name",
        queryset=City.objects.all(),
        filter_field="state",
        slug_lookup="state__isoCode"
    )

    class Meta:
        model = Location
        fields = ["user", "country", "state", "city"]

方法二:在validate方法中手动校验关联关系

保留基础SlugRelatedField,通过序列化器的validate方法手动校验层级关联的合法性:

class LocationSerializer(serializers.ModelSerializer):
    country = serializers.SlugRelatedField(slug_field="isoCode", queryset=Country.objects.all())
    state = serializers.SlugRelatedField(slug_field="isoCode", queryset=State.objects.all())
    city = serializers.SlugRelatedField(slug_field="name", queryset=City.objects.all())

    class Meta:
        model = Location
        fields = ["user", "country", "state", "city"]

    def validate(self, attrs):
        country = attrs.get("country")
        state = attrs.get("state")
        city = attrs.get("city")

        # 校验state属于选中的country
        if state.country != country:
            raise serializers.ValidationError({"state": "该州/省不属于选中的国家"})
        
        # 校验city同时属于选中的state和country
        if city.state != state or city.country != country:
            raise serializers.ValidationError({"city": "该城市不属于选中的州/省或国家"})
        
        return attrs

方法三:结合嵌套序列化器优化返回格式(可选)

如果需要返回更详细的关联数据(如国家名称、州名称),可以在序列化返回时替换为嵌套序列化器,创建时仍用SlugRelatedField接收参数:

# 基础嵌套序列化器
class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ["name", "isoCode"]

class StateSerializer(serializers.ModelSerializer):
    class Meta:
        model = State
        fields = ["name", "isoCode"]

class CitySerializer(serializers.ModelSerializer):
    class Meta:
        model = City
        fields = ["name"]

class LocationSerializer(serializers.ModelSerializer):
    country = serializers.SlugRelatedField(slug_field="isoCode", queryset=Country.objects.all())
    state = serializers.SlugRelatedField(slug_field="isoCode", queryset=State.objects.all())
    city = serializers.SlugRelatedField(slug_field="name", queryset=City.objects.all())

    class Meta:
        model = Location
        fields = ["user", "country", "state", "city"]

    def to_representation(self, instance):
        # 序列化返回时替换为嵌套结构,展示详细信息
        representation = super().to_representation(instance)
        representation["country"] = CountrySerializer(instance.country).data
        representation["state"] = StateSerializer(instance.state).data
        representation["city"] = CitySerializer(instance.city).data
        return representation

    def validate(self, attrs):
        # 复用方法二中的校验逻辑
        country = attrs.get("country")
        state = attrs.get("state")
        city = attrs.get("city")

        if state.country != country:
            raise serializers.ValidationError({"state": "该州/省不属于选中的国家"})
        
        if city.state != state or city.country != country:
            raise serializers.ValidationError({"city": "该城市不属于选中的州/省或国家"})
        
        return attrs

内容的提问来源于stack exchange,提问作者Jjkivai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 18:36:26