Django中save_model函数内related_name关联数据访问异常求助
我有一个名为Borrow的模型,在BorrowLine模型里定义了指向Borrow的ForeignKey,并设置related_name='lines'。我给Borrow模型写了BorrowAdmin和BorrowLineInline类,在BorrowAdmin的save_model函数里尝试访问关联的BorrowLine数据:
def save_model(self, request, obj, form, change): obj.save() print(obj.lines) return super(BorrowAdmin, self).save_model(request, obj, form, change)
但在Django后台添加Borrow实例时,控制台打印出borrow.BorrowLine.None,而且访问BorrowLine的字段(比如book)时会报错:'RelatedManager' object has no attribute 'book'。我试过用post_save/pre_save信号替代save_model,结果一样。
模型代码
from django.db import models from datetime import date, timedelta from django.core.exceptions import ValidationError def delivery_time_validator(value): # 自定义验证逻辑 pass class Borrow(models.Model): student = models.ForeignKey('student.Student', on_delete=models.CASCADE, related_name='borrow') created_time = models.DateField(auto_now_add=True) delivery_time = models.DateField(default=date.today() + timedelta(days=7), validators=[delivery_time_validator]) delivered = models.BooleanField(default=False) def __str__(self): return str(self.student) class BorrowLine(models.Model): borrow = models.ForeignKey('Borrow', on_delete=models.CASCADE, related_name='lines') book = models.ForeignKey('book.Book', on_delete=models.CASCADE, related_name='lines') def __str__(self): return f'{self.borrow} -> {self.book}' def clean(self): book = self.book if not book.is_available: raise ValidationError(f'The book {book.title} is not available.')
Admin代码
from django.contrib import admin from .models import Borrow, BorrowLine class BorrowLineInline(admin.TabularInline): model = BorrowLine extra = 1 max_num = 3 autocomplete_fields = ('book',) @admin.register(Borrow) class BorrowAdmin(admin.ModelAdmin): inlines = (BorrowLineInline,) list_display = ('student', 'created_time', 'delivery_time', 'delivered') search_fields = ('student__first_name', 'student__last_name', 'lines__book__title') list_filter = ('student__class_type', 'delivered', 'created_time', 'delivery_time') autocomplete_fields = ('student',) def add_view(self, request, form_url="", extra_context=None): self.fields = ('student', 'delivery_time') return super(BorrowAdmin, self).add_view(request, form_url=form_url, extra_context=extra_context) def change_view(self, request, object_id, form_url="", extra_context=None): self.fields = ('student', 'delivery_time', 'delivered') return super(BorrowAdmin, self).change_view(request, object_id, form_url=form_url, extra_context=extra_context) def save_model(self, request, obj, form, change): obj.save() print(obj.lines.book) # Do something return super(BorrowAdmin, self).save_model(request, obj, form, change)
1. 正确理解related_name的返回值
obj.lines是Django的RelatedManager对象,代表和当前Borrow关联的所有BorrowLine实例集合,不是单个对象。直接访问.book会报错,因为集合没有单个实例的属性。
要访问字段,需要遍历集合或取单个实例:
# 遍历所有关联的BorrowLine for line in obj.lines.all(): print(line.book) # 取第一个BorrowLine的book(需先判断是否存在) if obj.lines.exists(): print(obj.lines.first().book)
2. 解决Inline模型的保存时机问题
Django Admin中,主模型会先于Inline模型保存:点击保存时,Borrow先被存入数据库,关联的BorrowLine才会被保存。所以在save_model里调用obj.save()后,BorrowLine还未入库,obj.lines自然为空。
改用save_formset方法,它会在所有Inline实例保存完成后触发:
class BorrowAdmin(admin.ModelAdmin): # 其他代码保持不变 def save_formset(self, request, form, formset, change): # 先保存所有Inline和主模型 formset.save() obj = form.instance # 现在可以正常访问所有关联的BorrowLine for line in obj.lines.all(): print(line.book) super().save_formset(request, form, formset, change)
如果想用信号,需监听BorrowLine的post_save信号(而非Borrow的),但save_formset是Admin场景下最直接的解决方案。
3. 修复Admin字段的线程安全问题
不要直接修改self.fields,这会导致多用户访问时字段混乱。重写get_fields方法更安全:
def get_fields(self, request, obj=None): if obj is None: # 添加页面的字段 return ('student', 'delivery_time') else: # 修改页面的字段 return ('student', 'delivery_time', 'delivered')
内容的提问来源于stack exchange,提问作者ArianNa

