'Django: following relationships "backward" with multiple models

I have a set of models similar to this, where both Novel and Magazine have a ForeignKey to Bookshelf:

class Bookshelf(models.Model):
    name = models.TextField()

class Novel(models.Model):
    bookshelf = models.ForeignKey(Bookshelf)

class Magazine(models.Model):
    bookshelf = models.ForeignKey(Bookshelf)

Following relationships backward according to the docs, I know I can use two Managers: bookshelf.novel_set and bookshelf.magazine_set.

But what is the best way to get a single set of Novels AND Magazines on a Bookshelf? My actual setup has a growing list of models like this. Running the same operations on many "_sets" seems unpythonic and way too much boilerplate.

I've looked at GenericRelation and writing a custom Manager, but neither seems to do what I need cleanly (I may be wrong). Using Django 3.2.



Solution 1:[1]

Each Model maps to one DB table. Which is restrictive.

THe buzzword is "polymorphic models". You want a table that can represent both books and magazines. So far as they are similar in most other respects, you can accomplish this with

class BookshelfItem( models.Model)
    NOVEL = 'Novel'
    MAGAZINE = 'Magazine'
    BOOKSHELF_ITEM_TYPES = ( (NOVEL, 'Novel'), (MAGAZINE, 'Magazine'), )

    item_type = models.CharField( choices = BOOKSHELF_ITEM_TYPES, ...)
    bookshelf = models.ForeignKey(Bookshelf, related_name='items')
    ...

Now you can get a list of both with bookshelf.items and a list of a chosen one with bookshelf.items.filter( item_type = BookShelfItem.MAGAZINE ). You can even define more managers on the Bookshelf model to make it possible to do Bookshelf.magazines.filter(...) etc.

Where the items start being grossly dissimilar and cause you to have large numbers of often null or blank fields, you realize why there are other approaches to paper over this fundamental limitation with relational databases.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 nigel222