3

I'm trying to design my first database and got a little bit stuck. The design is as follows: You have an author who can have many books. Each book can have many pages. Each page can have many pictures. Each page is unique (ForeignKey). Pictures may not be unique (you can use the same picture in different pages/books, so this should be ManyToMany). My problem is that I can't get the pictures to be a ManyToManyField when using Inlines.

If I change ForeignKey to ManyToMany, I get the exception "<class 'books.models.Picture'> has no ForeignKey to <class 'books.models.Page'>". I had a look here and here, but have no clue how to apply this in my case.

This is how my models.py looks like:

class Author(models.Model):
    name = models.CharField(max_length=30)
    email = models.EmailField()

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=50)

class Page(models.Model):
    book = models.ForeignKey(Book)
    contents = models.TextField(max_length=15999)

class Picture(models.Model):
    page = models.ForeignKey(Page) # ideally, this should be many-to-many
    picture_uuid = models.CharField(max_length=36)

In my admin interface, I'd like to have all the books an author wrote on the author page. Also, I'd like to have all pages of a book in the books page and so on. My admin.py therefore looks like this:

class PictureAdmin(admin.ModelAdmin):
    list_display = ('id', 'picture_uuid')

class PictureInline(admin.TabularInline):
    model = Picture

class PageAdmin(admin.ModelAdmin):
    list_display = ('id', 'page_uuid')

    inlines = [
        PictureInline,
        ]

class PageInline(admin.TabularInline):
    model = Page

class BookAdmin(admin.ModelAdmin):
    list_display = ('id', 'title')

    inlines = [
        PageInline,
        ]

class BookInline(admin.TabularInline):
    model = Book

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'email')

    inlines = [
        BookInline,
        ]
Community
  • 1
  • 1
n.evermind
  • 11,944
  • 19
  • 78
  • 122

2 Answers2

3

You can't use ManyToManyFields directly as inlines. Inlines must have a foreign key back to the model being edited, and of course the actual child does not. If you want to edit a M2M inline, the best you can do is use the through table, so you'd need to alter your InlineModelAdmin like so:

class PictureInline(admin.TabularInline):
    model = Page.pictures.through

Which requires that the ManyToManyField be on the Page model:

class Page(models.Model):
    ...
    pictures = models.ManyToManyField(Picture)
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Works very well, except that I have now two sections on a page where I can select pictures. One foreign key drop-down menu to select a picture and one normal many-to-many menu to select a picture. How can I get rid of the second? – n.evermind Jan 17 '12 at 15:20
  • 1
    Just add the actual field to your `ModelAdmin`'s `excludes` – Chris Pratt Jan 17 '12 at 15:33
  • Thanks, just done that and it disappeared. Next problem, however: if I try to put the same picture on a page again, I'll get the error (in the Django Admin interface): "Page-picture relationship with this Page and Picture already exists." Why is this so? I thought this is now a many-to-many relationship, i.e. I should be able to have the same picture on a page twice or three times or four times etc. – n.evermind Jan 17 '12 at 15:37
  • @ChrisPratt I didn't realise you could assign the through table directly as the model in an inlineadmin. Thanks for this. – Daniel Roseman Jan 17 '12 at 15:41
  • 1
    Generally, no, you don't add the same relation to the same model twice. I've never thought about even doing that before because I've never had a need. It's possible that the default intermediary table that Django creates for an M2M relationship enforces a unique together constraint on the two foreign keys. You might be able to get around that by creating your own `through` model. – Chris Pratt Jan 17 '12 at 15:45
  • @DanielRoseman: Neither did I for the longest time. Don't even remember now where I stumbled upon that info. It's still not as good as being able to inline the actual model directly, but it's something ;). – Chris Pratt Jan 17 '12 at 15:48
  • Actually, you are right. I don't have a need either. I can define in my layout field that picture such and such appears 3 or 4 times. Thanks so much for your help! One last question if I may: I assume that I may end up with 1000s of pictures and loading the drop-down would take some time (I guess). I tried to show pictures as raw id's but this didn't work (i.e. Django didn't really care when I put raw_id_fields = ("pictures",) in my PageAdmin class. It still gave me the same old drop down). – n.evermind Jan 17 '12 at 15:55
  • 1
    You have to put `raw_id_fields` on your *inline* class. `InlineModelAdmin`s (for the most part) have all the same functionally as regular `ModelAdmin`s. Things like fieldsets, excludes, etc. are defined on the `InlineModelAdmin` just like they would be on `ModelAdmin`. – Chris Pratt Jan 17 '12 at 15:57
  • Thanks, but if I put it in my inline class (PictureInline) I get the error message 'PictureInline.raw_id_fields' refers to field 'pictures' that is missing from model 'Page_pictures'. – n.evermind Jan 17 '12 at 16:45
  • i.e. right now I have `class PictureInline(admin.TabularInline): model = Page.pictures.through raw_id_fields = ("pictures",)` – n.evermind Jan 17 '12 at 16:46
  • 1
    That's because the field on the `through` model is not "pictures" it's `picture`. – Chris Pratt Jan 17 '12 at 18:54
  • Yes, yes, yes, it works!! Thanks so much, this was really really helpful. Can't thank you enough. – n.evermind Jan 17 '12 at 19:57
0

According to the documentation in https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models you have to define a model attribute in your inline

armonge
  • 3,108
  • 20
  • 36