Django Admin One-To-Many-To-One (Site->Product->Price) Relationship

Viewed 57

I'm developing a web app for fun and the goal is to use Django Admin and only Django Admin, without any custom templates (yet). I'm unable to figure out how I should structure my models.py and admin.py files, where what I'm trying to do is:

There are many items and many departments: Each item can only belong to one department and each department can have several items. This currently works.

Now, what I can't seem to figure out is:

There are many sites. Each site can have many items, but the PRICE of each item at each site can be different. For example:

Site #123 can have a can of coke for $1.00 Site #124 can also have a can of coke, but at a price of $0.95 Site #123 can have a bag of chips for $1.50 Site #124 can also have a bag of chips, but at a price of $1.95 etc...

How do I establish this relationship in Django models.py/admin.py? Also, how could I edit the price using the inline (screenshot below)? In other words, how could the price be shown to the right of the description?

Thanks in advance

Current Code:

admin.py:

from django.contrib import admin
from .models import Site, Department, Item

class ItemInline(admin.TabularInline):
    model = Site.items.through
    can_delete = False
    verbose_name = 'Item'
    verbose_name_plural = 'Items'
    extra = 0


@admin.register(Site)
class SiteAdmin(admin.ModelAdmin):
    fields = ("site", "address")
    list_display = ("site", "address")
    inlines = (ItemInline, )
    exclude = ("items", )
    
    
@admin.register(Department)
class DepartmentAdmin(admin.ModelAdmin):
    fields = ("number", "description")
    list_display = ("number", "description")


@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
    fields = ("upc", "description", "department")
    list_display = ("upc", "description", "department")
    save_as = True
    
    def formfield_for_dbfield(self, *args, **kwargs):
        formfield = super().formfield_for_dbfield(*args, **kwargs)
        formfield.widget.can_delete_related = False
        formfield.widget.can_change_related = False
        formfield.widget.can_add_related = False 
        # formfield.widget.can_view_related = False

        return formfield

models.py:

from django.db import models


class Department(models.Model):
    number = models.IntegerField(unique=True)
    description = models.CharField(max_length=30)

    def __str__(self):
        return str(self.number) + ": " + self.description

    class Meta:
        ordering = ["number", ]


class Item(models.Model):
    upc = models.CharField(max_length=30, unique=True, verbose_name="UPC")
    description = models.CharField(max_length=30)
    department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return self.upc + ": " + self.description

    class Meta:
        ordering = ["upc", ]


class Site(models.Model):
    site = models.IntegerField(unique=True)
    address = models.CharField(max_length=50)
    items = models.ManyToManyField(Item)

    def __str__(self):
        return str(self.site)

    class Meta:
        ordering = ["site", ]

inline

1 Answers

I figured this out about 20 minutes after posting. The key was to create a new model (named ItemPrice) with site, upc, and price -- with site and upc FK'd to Site and Item. I then created a new Inline pointing to ItemPrice:


class ItemPrice(models.Model):
    site = models.ForeignKey(Site, on_delete=models.CASCADE, null=True)
    upc = models.ForeignKey(Item, on_delete=models.CASCADE, null=True, verbose_name="UPC")
    price = models.DecimalField(max_digits=6, decimal_places=2, default=0)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['site', 'upc'], name='unique_site_upc')
        ]

and

class ItemPriceInline(admin.TabularInline):
    model = ItemPrice
    # can_delete = False
    verbose_name = 'Item Price'
    verbose_name_plural = 'Item Prices'
    extra = 0
Related