DigitalMockingbird DigitalMockingbird - 13 days ago 7
Python Question

I'm having trouble deciding between using a foreign key, many-to-many field, or choicefield

edit: Code has been updated with working solution

I'm building a video game library and I'm having some trouble structuring my models. I'm not sure if I should use a many-to-many, foreign key, or choices field to do the following:

There are game consoles (PC, Playstation 4, etc) and video games (Rocket League, Minecraft, etc). Each game must be on at least one console, but it might be available on more.

My idea is to create a game console table which would store the name, logo, and other useful information. The video game table would store the title, cover art, maps, game modes, and so on.

Ideally, in the admin dashboard, a user can edit console information or a game. The game section of the dashboard would allow editing of the game and the consoles it's available on.

Here's what I've done so far.

models.py

class GameConsole(models.Model):
name = models.CharField(
max_length=8,
default='PC',
)

# console_logo = models.ImageField()

def __str__(self):
return self.name

class Meta:
verbose_name = 'game console'
verbose_name_plural = 'game consoles'
db_table = 'console'
ordering = ['-name']


class VideoGame(models.Model):
title = models.CharField(
max_length=128,
default='???'
)
console = models.ManyToManyField(
GameConsole,
)

# game_cover = models.ImageField()
# company_website = models.URLField()
# date_published = models.DateField()

def __str__(self):
return self.title

class Meta:
verbose_name = 'video game'
verbose_name_plural = 'video games'
db_table = 'games'
ordering = ['-title']


admin.py

class ConsoleInline(admin.TabularInline):
model = VideoGame.console.through


class GameConsoleAdmin(admin.ModelAdmin):
list_display = ['name', ]


class VideoGameAdmin(admin.ModelAdmin):
list_display = ['title', 'game_platform']
inlines = [ConsoleInline]
exclude = ('console',)

def game_platform(self, obj):
return ', '.join([item.name for item in obj.console.all()])


admin.site.register(GameConsole, GameConsoleAdmin)
admin.site.register(VideoGame, VideoGameAdmin)


Thanks in advance for the help!

Answer

What you have actually implemented here is a Many To Many relationship between Consoles and Video Games without using Django's ManyToManyField. It does seem that a many to many relationship is what's required here.

The Game model corresponds to a 'through' model in django speak. But your Game model does not have any additional fields so it can be dropped completely to end up with just two models.

class GameConsole(models.Model):
    name = models.CharField(
        max_length=8,
        default='PC'
    )

    # console_logo = models.ImageField()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'game console'
        verbose_name_plural = 'game consoles'
        db_table = 'console'
        ordering = ['-name']


class VideoGame(models.Model):
    title = models.CharField(
        max_length=128,
        default='???'
    )
    consoles = models.ManyToManyField(Console)
    # game_cover = models.ImageField()
    # company_website = models.URLField()
    # date_published = models.DateField()

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = 'video game'
        verbose_name_plural = 'video games'
        db_table = 'games'
        ordering = ['-title']

Then it's possible for you to use Admin Inlines to Games from Console change forms and vice verce

If you want to display many-to-many relations using an inline, you can do so by defining an InlineModelAdmin object for the relationship:

class GameInline(admin.TabularInline):
     model = VideoGame
     exclude = ('consoles',)

class ConsoleInline(admin.TabularInline):
     model = Console

class GameConsoleAdmin(admin.ModelAdmin):
    list_display = ['name']
    inlines = [ GameInline]

class GameAdmin(admin.ModelAdmin):
    list_display = ['game', 'console']
    inlines = [ ConsoleInline, ]    

admin.site.register(GameConsole, GameConsoleAdmin)
admin.site.register(Game, GameAdmin)