Search code examples
djangodjango-modelsdjango-adminsignals

Connection problem to related_name in save_model function in Django


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.


Solution

  • 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 .books 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 (usually self) of the function.