Reputation: 708
I have a class that looks like this. In the pack items I want to have the number of that instance of Item and how many of that as a number.
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.DecimalField(decimal_places=2, max_digits=10)
is_pack = models.BooleanField(default=False)
pack_items = models.ManyToManyField(PackItem, blank=True)
class PackItem(models.Model):
packitem = models.ForeignKey('Item', on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
This seems to work but every time I create a new pack in the admin, I have to create first, one by one, each PackItem and then put them in the packitems field of Item. The goal is: I have bottles of drinks as Items and I want to have pre-made Packs of these drinks. Would my way of doing it cause problems later on? Is there a better practice? Or is this already wrong?
Upvotes: 2
Views: 2754
Reputation: 476557
If I understand this correctly, what you need is a many-to-many field with a through=…
model [Django-doc]. So we can implement a
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.DecimalField(decimal_places=2, max_digits=10)
class Pack(models.Model):
items = models.ManyToManyField(Item, through='PackItem')
class PackItem(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
pack = models.ForeignKey(Pack, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
Here we thus specify that a Pack
consists out of a collection of items, but the junction table [wiki] will contain an extra parameter named quantity
that specifies how many times we add the Item
to the Pack
object.
Upvotes: 4
Reputation: 15
This example is an excellent one that helps answer what a through model is.
To further clear things as I needed an extra hour or so of tutorials to understand this:
1- The Item model should be easily understood
class Item(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(decimal_places=2, max_digits=10)
# Display below in admin
def __str__(self):
return f"{self.name}"
2- The Pack model is the model you create instances in.
class Pack(models.Model):
pack = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="packname")
items = models.ManyToManyField(Item, through='PackItem')
# Display below in admin
def __str__(self):
return f"{self.id}"
3- The "PackInfo" model is the intermediary model. You don't manually create any instances here as it does so automatically. What's important is that you add 2 foreignkey fields; one for each model you wish to connect and then whatever additional fields you need (like 'quantity'). The purpose of this example is to add the 'quantity' field.
class PackInfo(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
pack = models.ForeignKey(Pack, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
# Display below in admin
def __str__(self):
return f"{self.id}"
The Pack model in the admin should now look like the below (I just added an extra foreignkey field in the Pack model that allows you to select an item):
Notice how although the Pack model only has 2 fields ('pack' and 'items'), there is an additional field called 'quantity'. This in my humble opinion is the main advantage of using a through model.
Finally, I was able to display the admin as shown in the image using "admin.TabularInline". You set the intermediary model (PackInfo) as a TabularInline admin view. You can then display it as such in the model with the M2M field (Pack). The extra = 0
is so that no extra empty instances are added in the admin until you press "Add another Pack Info".
@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price')
class PackInfoInline(admin.TabularInline):
model = PackInfo
extra = 0 # Default extra instances to add is 3
@admin.register(Pack)
class PackAdmin(admin.ModelAdmin):
inlines = [PackInfoInline]
class Meta:
model = Pack
Upvotes: 0