Validate a condition that relies on foreign keys in Django admin

Question

I have two models, Book and Page, where Page has a foreign key relation to Book. In admin.py, the webpage to modify a book has inline elements for Page objects.

models.py:

class Book(models.Model):
    # ...

class Page(models.Model):
    book = models.ForeignKey(Book, editable=False)
    number = models.IntegerField('Page number')
    # ...

admin.py:

class PageAdminInline(admin.TabularInline):
    model = Page
    extra = 1

class BookAdmin(admin.ModelAdmin):
    inlines = [PageAdminInline]

admin.site.register(Book, BookAdmin)

In the inline forms, a field allows to set the page number for each page. With this configuration, how can I validate the global condition that all pages numbers of a book should be different and numbered from one to the number of Page objects associated to the book?

I suppose I have to override a clean() method related to a book model or form somewhere, but I don't know how to access from there the data related to the pages that the user inputs.

Edit

Based on Emett's suggestion, I have tried to override the clean() method of the Page model:

class Page(models.Model):
    book = models.ForeignKey(Book, editable=False)
    number = models.IntegerField('Page number')

    def clean():
        book_pages = Page.objects.filter(book=self.book)
        # ... [apply condition on the book_pages QuerySet]
        super(Page, self).clean()

This does not work: if I modify the page number of all pages in the admin site for a book, book_pages will contain objects that have the old page numbers.

In addition, having the condition checked in Page also means that it will be tested n times if I have n pages, while just checking it once should be sufficient.


Show source
| django   | django-admin   | django-validation   2017-01-04 01:01 1 Answers

Answers to Validate a condition that relies on foreign keys in Django admin ( 1 )

  1. 2017-01-04 09:01

    An easier solution would be putting unique_together in django model. IE

    class Page(models.Model):
        book = models.ForeignKey(Book, editable=False)
        number = models.IntegerField('Page number')
    
        class Meta:
            app_label = 'page'
            db_table = 'pages'
            verbose_name = 'Page'
            verbose_name_plural = 'Pages'
            unique_together = (('book', 'number'),)
    

    Another workaround if you don't want to use unique_together, then create a form, use it in inline, ie:

    class PageForm(forms.ModelForm):
       class Meta:
         model = Page
         fields ='__all__'
    
       def clean():
           cleaned_data = self.cleaned_data
           book = cleaned_data.get('book')
           number = cleaned_data.get('number')
           page_qset = Page.objects.filter(book=book, number=number)
    
           if len(page_qset) > 0:  # inefficient solution, using it for forcibly executing query
                 raise forms.ValidationError('Already exists')
           return super().clean()
    
    
    
    class PageAdminInline(admin.TabularInline):
        form = PageForm
    

Leave a reply to - Validate a condition that relies on foreign keys in Django admin

◀ Go back