Django中QuerySet无items属性报错,求实现指定输出格式
问题描述
我需要API输出如下格式的数据:
{ "E": [ { "value": 50, "label": "company1" }, { "value": 0, "label": "company2" } ], "P": [ { "value": 50, "label": "company1" }, { "value": 0, "label": "company2" } ], "C": [ { "value": 50, "label": "company1" }, { "value": 0, "label": "company2" } ] }
尝试用serializers.DictField实现时,触发错误:AttributeError: 'QuerySet' object has no attribute 'items'。推测是模型方法返回的QuerySet不具备items属性,但这些方法在Jupyter单独运行正常。
以下是代码详情:
Model
class TblCorporation(models.Model): contractcorporationid = models.AutoField(db_column='ContractCorporationID', primary_key=True) contractid = models.ForeignKey(TblContract, related_name="Contract_Corporation", on_delete=models.CASCADE, db_column='ContractID') companyid = models.ForeignKey(TblCompany, related_name="Company_Corporation", on_delete=models.CASCADE, db_column='CompanyID') e_percent = models.FloatField(db_column='E_Percent') p_percent = models.FloatField(db_column='P_Percent') c_percent = models.FloatField(db_column='C_Percent') def __str__(self) -> str: return '%s: e=%s, p=%s, c=%s' % (self.companyid.company, self.e_percent, self.p_percent, self.c_percent) def E(self): e = TblCorporation.objects.filter(contractid__exact=self.contractid).values( 'companyid__company', 'e_percent').annotate( value = F('e_percent'), label = F('companyid__company')).values( 'value', 'label') return (e) def P(self): p = TblCorporation.objects.filter(contractid__exact=self.contractid).values( 'companyid__company', 'p_percent').annotate( value = F('p_percent'), label = F('companyid__company')).values( 'value', 'label') return (p) def C(self): c = TblCorporation.objects.filter(contractid__exact=self.contractid).values( 'companyid__company', 'c_percent').annotate( value = F('c_percent'), label = F('companyid__company')).values( 'value', 'label') return (c) def company(self): return self.companyid.company class Meta: db_table = 'tbl_Corporation' verbose_name = 'Contract_Corporation' verbose_name_plural = 'Contract_Corporations'
Serializer
class EPCDictField(serializers.DictField): def to_internal_value(self, data): if set(data.keys()) != {"value", "label"}: raise serializers.ValidationError("Invalid keys for dictionary.") return super().to_internal_value(data) def to_representation(self, value): return super().to_representation(value) class ContractCorporationSerializer(serializers.ModelSerializer): E = EPCDictField() P = EPCDictField() C = EPCDictField() class Meta: model = TblCorporation fields = ('E', 'P', 'C')
API
def get_ContractCorporation(request, id): try: contractCorporations = TblCorporation.objects.filter(contractid__exact=id) serializer = ContractCorporationSerializer(contractCorporations, many=True) return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK) except Exception as e: return Response({"status": "error", "data": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
错误原因
- 模型中的
E()/P()/C()方法返回的是QuerySet对象,但DictField要求输入是字典类型,QuerySet是可迭代集合,没有字典的items()方法,因此触发报错。 - 视图中用
many=True序列化整个QuerySet,会返回数组结构,但目标输出是单个对象包含E/P/C三个数组,结构不匹配。
解决方案
步骤1:优化模型方法(移除冗余逻辑)
模型中的E/P/C方法存在重复查询,每个实例都会执行相同的筛选操作,建议直接移除这些方法,在序列化器中统一处理数据:
class TblCorporation(models.Model): contractcorporationid = models.AutoField(db_column='ContractCorporationID', primary_key=True) contractid = models.ForeignKey(TblContract, related_name="Contract_Corporation", on_delete=models.CASCADE, db_column='ContractID') companyid = models.ForeignKey(TblCompany, related_name="Company_Corporation", on_delete=models.CASCADE, db_column='CompanyID') e_percent = models.FloatField(db_column='E_Percent') p_percent = models.FloatField(db_column='P_Percent') c_percent = models.FloatField(db_column='C_Percent') def __str__(self) -> str: return '%s: e=%s, p=%s, c=%s' % (self.companyid.company, self.e_percent, self.p_percent, self.c_percent) def company(self): return self.companyid.company class Meta: db_table = 'tbl_Corporation' verbose_name = 'Contract_Corporation' verbose_name_plural = 'Contract_Corporations'
步骤2:自定义序列化器适配目标格式
改用ListField配合子序列化器来处理数组结构,直接在序列化器中查询指定contract下的所有数据并构造输出:
from rest_framework import serializers from .models import TblCorporation class EPCItemSerializer(serializers.Serializer): value = serializers.FloatField() label = serializers.CharField() class ContractCorporationSerializer(serializers.Serializer): E = serializers.ListField(child=EPCItemSerializer()) P = serializers.ListField(child=EPCItemSerializer()) C = serializers.ListField(child=EPCItemSerializer()) def to_representation(self, contract_id): # 查询指定contract下的所有企业数据 queryset = TblCorporation.objects.filter(contractid=contract_id) # 构造E/P/C对应的数组数据 e_data = [{'value': item.e_percent, 'label': item.companyid.company} for item in queryset] p_data = [{'value': item.p_percent, 'label': item.companyid.company} for item in queryset] c_data = [{'value': item.c_percent, 'label': item.companyid.company} for item in queryset] return { 'E': e_data, 'P': p_data, 'C': c_data }
步骤3:修改视图逻辑
视图不再序列化整个QuerySet,而是直接传入contract的id,让序列化器处理数据组装:
from rest_framework.response import Response from rest_framework import status from .serializers import ContractCorporationSerializer def get_ContractCorporation(request, id): try: # 传入contract id给序列化器 serializer = ContractCorporationSerializer(instance=id) return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK) except Exception as e: return Response({"status": "error", "data": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
验证输出
修改后调用API,将返回目标格式的数据:
{ "status": "success", "data": { "E": [ {"value": 50, "label": "company1"}, {"value": 0, "label": "company2"} ], "P": [ {"value": 50, "label": "company1"}, {"value": 0, "label": "company2"} ], "C": [ {"value": 50, "label": "company1"}, {"value": 0, "label": "company2"} ] } }
内容的提问来源于stack exchange,提问作者Ali Sajadian
相关产品推荐
相关产品推荐

