如何构建动态关联字段序列化器,实现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
相关产品推荐
相关产品推荐

