There is a model called Borrow
. Inside BorrowLine
model, a ForeignKey is defined with related_name lines
. I write BorrowAdmin
and BorrowLineInline
for Borrow
model. Inside BorrowAdmin
class I used the save_model
function as follows:
def save_model(self, request, obj, form, change):
obj.save()
print(obj.lines)
return super(BorrowAdmin, self).save_model(request, obj, form, change)
But when I add an instance of Borrow
model in Django administration, the following line is printed in the console:
borrow.BorrowLine.None
and I don't have access to any of the BorrowLine
fields. For example, one of its fields is book, but when I call it, it gives this error:
'RelatedManager' object has no attribute 'book'
How can I fix this problem and access the class fields?
my codes:
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.')
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)
I also used signals like post_save
and pre_save
instead of save_model
, but they also had the same error and returned None.
The manager always prints app_name.ModelName.None
, you need to work with the QuerySet
, so:
def save_model(self, request, obj, form, change):
obj.save()
print(obj.lines.all())
return super().save_model(request, obj, form, change)
That being said, it will not show the lines
, since it will first save the obj
and then its many-to-many relations. You thus inspect the relation after saving the entire model:
def save_model(self, request, obj, form, change):
return super().save_model(request, obj, form, change)
print(obj.lines.all())
If you want to process the .book
s for these lines
, you can work with:
def save_model(self, request, obj, form, change):
return super().save_model(request, obj, form, change)
for line in obj.lines.select_related('book'):
print(line.book)
Note: Since PEP-3135 [pep], you don't need to call
super(…)
with parameters if the first parameter is the class in which you define the method, and the second is the first parameter (usuallyself
) of the function.