Django从外键模型获取信息的写法是否正确?如何优化?
现有代码的实现逻辑
你这段代码本质是给模型加了一个名为author的只读计算属性,@property装饰器的作用是让这个方法可以像普通实例属性一样被访问,不需要加()调用,大部分DRF序列化、模型转字典的逻辑都会自动识别这个属性,把它当成普通字段返回给前端,这也是为什么功能可以正常运行的原因。
逐行的执行逻辑:
- 从当前模型实例上取
book_id字段的值 - 发起一次数据库查询,取出id匹配的
Book记录 - 从拿到的
Book实例上取author_id字段的值 - 再发起一次数据库查询,取出id匹配的
Author记录 - 从
Author实例上取first_name字段的值返回
这个写法目前能跑,但有几个很明显的问题:
- 存在严重的N+1查询风险:只要你查询多条当前模型的记录,每访问一次
author属性就会发起2次独立的数据库查询,查100条数据就会多打200次SQL,数据量上来之后接口响应速度会非常慢。 - 没有异常处理:如果关联的
Book记录被删了、或者Book关联的Author记录不存在,objects.get()会直接抛出DoesNotExist异常,导致接口500报错。 - 冗余代码太多:你所有用
getattr取字段的地方,字段都是模型上确定存在的,直接用self.book_id、book.author_id这种点属性的写法就行,没必要绕一层getattr。
更优的实现方案
根据你的模型关联情况选对应的方案即可:
优先方案:利用ORM的关联关系,避免手动查库
如果你的当前模型本身定义了指向Book的外键,比如:
class Record(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) # 其他业务字段
完全不需要手动写两次数据库查询,直接顺着关联关系取字段即可:
@property def author(self): try: return self.book.author.first_name except (Book.DoesNotExist, Author.DoesNotExist): return ""
注意查询数据集的时候一定要用select_related提前把两层关联数据一次性查出来,从根源上避免N+1问题:
# 查列表时提前JOIN关联的book和author表,后续访问author属性不会再触发额外查询 record_list = Record.objects.select_related("book__author").all()
如果你是用Django REST Framework做序列化,连模型层的@property都不用写,直接在序列化器里指定字段来源即可:
class RecordSerializer(serializers.ModelSerializer): author = serializers.CharField(source="book.author.first_name", read_only=True) class Meta: model = Record fields = "__all__"
特殊情况处理
如果你的模型没有定义外键,只是单独存了book_id字段,建议优先补全外键定义,Django的外键会自动帮你维护关联关系、处理级联逻辑,比手动存id查库靠谱很多。
如果暂时没法修改表结构加外键,至少要补上异常捕获,不要让关联数据缺失直接打挂接口,同时尽量在查询阶段就批量把关联数据查出来缓存,不要在属性里逐条查库。
内容的提问来源于stack exchange,提问作者Galoperidol
相关产品推荐
相关产品推荐

