Django中列表对象转字典未生效却新增属性的原因咨询
作为Django新手,我希望在将Queryset/列表中的Job对象传入模板前为其添加键值对。查阅资料得知不能直接为模型对象追加字段,需创建新字典或添加属性。我尝试在循环中将对象转为__dict__并添加conflict/available字段,发现列表仍保留原Job/User对象而非字典,且对象能访问新增属性;但单对象执行相同操作会报错,想了解这一现象的原因。
相关代码片段
views.py(jobs视图,缩写)
def jobs(request): # 获取所有未分配的工作,筛选出用户技能匹配的工作 available = Job.objects.filter(assigned_to__isnull=True).order_by('start_time').all() available = [job for job in available if request.user.is_skilled(job)] # 遍历工作,若日程冲突则转为字典并添加"conflict":True for job in available: if not request.user.is_available(job): job = job.__dict__ job["conflict"] = True # 序列化后返回 return JsonResponse({'available': [job.serialize() for job in available]})
models.py(Job模型,缩写)
class Job(models.Model): title = models.CharField(max_length=50) description = models.CharField(max_length=500) start_time = models.DateTimeField() end_time = models.DateTimeField() skillsets = models.ManyToManyField(Skillset, related_name="jobs") def serialize(self): return { 'id': self.id, 'title': self.title, 'description': self.description, 'start_time': self.start_time.strftime("%b %d %Y, %I:%M %p"), 'end_time': self.end_time.strftime("%b %d %Y, %I:%M %p"), 'skillsets': [skill.title for skill in self.skillsets.all()], 'conflict': self.conflict if 'conflict' in self.__dict__ else False, }
views.py(job_details视图,缩写)
def job_details(request, job_id): # 获取指定工作及符合条件的用户 job = Job.objects.get(pk=job_id) users = User.objects.filter(user_type=User.SPECIALIST) users = list(filter(lambda usr: usr.is_skilled(job), users)) # 遍历用户,添加available字段 for usr in users: if usr.is_available(job): usr = usr.__dict__ usr["available"] = True else: usr = usr.__dict__ usr["available"] = False # 返回渲染数据 return render(request, "jobs/job_details.html", { 'job': job.serialize(), 'assignable': users, 'conflict': True if request.user.user_type == User.SPECIALIST and not request.user.is_available(job) else False})
原因解析
1. 循环中赋值不修改原列表的本质
循环里的job或usr只是列表元素的临时引用变量。当你执行job = job.__dict__时,只是把这个临时变量的指向从模型对象改成了字典,并没有修改列表里存储的原始元素。所以遍历结束后,列表里仍然是原来的Job/User模型对象,不会变成字典。
2. 原对象能访问新增属性的原因
Python是动态语言,Django模型实例本质也是Python对象,你可以直接给它添加实例属性(比如job.conflict = True),这些属性会被存在对象的__dict__字典中。你看到的新增属性能被访问,大概率是你在代码中无意中直接给对象添加了属性(而非转字典的操作生效),所以serialize方法能通过self.__dict__检测到conflict字段。
3. 单对象操作报错的原因
如果单独对一个模型对象执行obj = obj.__dict__,变量obj就变成了字典类型,而字典没有serialize()方法,之后调用obj.serialize()自然会报错。但循环中因为列表里还是原模型对象,所以调用job.serialize()不会出错。
正确的实现方式
方式一:直接给模型对象添加属性(简单高效)
既然Django模型实例支持动态添加属性,直接赋值即可,无需转字典:
for job in available: if not request.user.is_available(job): job.conflict = True # 直接给对象添加属性,列表中的原始对象会被修改
方式二:创建新的字典列表(适合不需要保留原模型对象的场景)
如果确实需要字典形式,应该创建新列表存储转换后的字典,避免修改原对象:
available_jobs = [] for job in available: # 复制__dict__避免修改原对象的内部字典 job_dict = job.__dict__.copy() if not request.user.is_available(job): job_dict["conflict"] = True # 注意:__dict__包含Django模型的内部属性,可按需过滤或改用serialize方法生成基础字典 available_jobs.append(job_dict) # 返回时使用新列表 return JsonResponse({'available': available_jobs})
内容的提问来源于stack exchange,提问作者Kinome79

