Many-to-Many with quantities

110 views
Skip to first unread message

Gio

unread,
May 31, 2007, 11:48:19 AM5/31/07
to Django users
Hi,
I searched about this subject and found only a few very old posts, so
maybe there is a better solution now.

As you may guess the model I'd like to code is similar to the Pizza -
Toppings you know from the "Creating Models" documentation:

class Topping(models.Model):
# ...

class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)


And what if I need to say that I can have two or three times the same
topping on my pizza? Something like twice mozzarella cheese and 3
times green olives topping?

I though about an intermediary class and indeed this is the same
solution found in those old posts mentioned before:

class Topping(models.Model):
# ...

class ToppingAndQuantity(models.Model):
amount = models.IntegerField()
topping = models.ForeignKey(Topping)

class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(ToppingAndQuantity)


I think it's ugly.
Can you suggest me a better solution?
It's that intermediary class really needed?

Thank you,
Giovanni.

Michael Newman

unread,
May 31, 2007, 12:02:32 PM5/31/07
to Django users
You could need an extra class for this but it should work:

class Topping(models.Model):
# ...

class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)

class PizzaToppings(model.Model):
pizza = models.ForeignKey(Pizza, help_text='Toppings to go on
Pizza: num in admin is how many will show up in Pizza',
edit_inline=models.TABULAR, num_in_admin=3)
topping = models.ManyToOne(Topping)
#...

some related material: http://www.djangoproject.com/documentation/model-api/#many-to-one-relationships

Hope that helps,
Mn

Nis Jorgensen

unread,
May 31, 2007, 12:21:09 PM5/31/07
to django...@googlegroups.com
I believe you should have

class Topping(models.Model):
# ...

class Pizza(models.Model):
# ...

class PizzaTopping(models.Model):


amount = models.IntegerField()
topping = models.ForeignKey(Topping)

pizza = models.ForeignKey(Pizza)


The "PizzaTopping" class is really a more elaborate specification of the
many-to-many relationship, so you don't need the ManyToManyField
anymore. Your model has the problem that two pizzas can share
(Mozarella, 2) - leading to problems if you modify one of them.

You will want to do the invocations for making (topping, pizza) unique
as well, and possibly do some magic so that trying to add another
Mozarella will increment the quantity instead.

Disclaimers: I am a novice Django user. The code is untested.

Update: Between posting and reposting from the correct mail address, I
saw the response from Michael. He is using "ManyToOneField" which I
don't see in my documentation, and keeps the ManyToManyField(Topping)
which seems strange to me. I believe you will be able to put together a
good solution from the two posts.

Nis


Tim Chase

unread,
May 31, 2007, 12:17:28 PM5/31/07
to django...@googlegroups.com
> And what if I need to say that I can have two or three times the same
> topping on my pizza? Something like twice mozzarella cheese and 3
> times green olives topping?
>
> I though about an intermediary class and indeed this is the same
> solution found in those old posts mentioned before:
>
> class Topping(models.Model):
> # ...
>
> class ToppingAndQuantity(models.Model):
> amount = models.IntegerField()
> topping = models.ForeignKey(Topping)
>
> class Pizza(models.Model):
> # ...
> toppings = models.ManyToManyField(ToppingAndQuantity)
>
> I think it's ugly.
> Can you suggest me a better solution?
> It's that intermediary class really needed?

I believe this is the best way to do it. However, this generally
gets composed as

class Pizza(Model):
#don't reference toppings here

class ToppingAndQuantity(Model):
pizza = ForeignKey(Pizza)
topping = ForeignKey(Topping)
amount = PositiveIntegerField()

The information you want (the quantity) is associated with the
join between a pizza and its toppings. The ManyToMany would make
a situation where you could have one pizza that has both 2x
Cheese and 4x Cheese. I suspect that you want just one topping
per pizza, but a quantity associated with that one topping.

The ManyToMany is a nice shortcut for basically creating this
intermediate join model with only two FK fields in it (pizza and
topping) with a little syntactic sugar around them.

With the above, you should be able to do things somewhat like

p = Pizza.objects.get(id=1)
for topping in p.toppingandquantity_set:
print "%s x%i" % (p.topping, p.amount)

-tim


Michael Newman

unread,
May 31, 2007, 12:27:21 PM5/31/07
to Django users
Nis;
Thanks for catching that I left the manytomany field there. There is
no need for that.
revised code:

class Topping(models.Model):
# ...

class Pizza(models.Model):
# ...

class PizzaToppings(model.Model):


pizza = models.ForeignKey(Pizza, help_text='Toppings to go on
Pizza: num in admin is how many will show up in Pizza',
edit_inline=models.TABULAR, num_in_admin=3)
topping = models.ManyToOne(Topping)

amount = models.IntegerField()
#...

This will appear in the Pizza part of the admin as a stacked model and
allow you to use or add as many as you would like. So you can remove
the amount if you just want people to be able to add double cheese by
adding a second cheese topping.

Thanks Nis and good luck Giovanni.

Mn

Gio

unread,
May 31, 2007, 1:03:40 PM5/31/07
to Django users
Thanks to you all, I took something from all the replies and I wrote
this modifying Michael code:

class Topping(models.Model):
name = models.CharField(maxlength=255, unique=True)

def __str__(self):
return self.name

class Admin:
pass


class Pizza(models.Model):
name = models.CharField(maxlength=255, unique=True)

def __str__(self):
return self.name

class Admin:
pass


class PizzaTopping(models.Model):
pizza = models.ForeignKey(Pizza,
help_text = 'Toppings to go on Pizza:


num in admin is how many will show up in Pizza',
edit_inline = models.TABULAR,

num_in_admin = 3,
core=True)
topping = models.ForeignKey(Topping)
amount = models.IntegerField()


I got problems in admin: I inserted a few toppings, now when I insert
a new pizza with his toppings the toppings and quantities aren't
saved.
No error messages, but inspecting database reveals no entries for the
auxiliary table pizza_pizzatopping

Any hint?
I'd like to solve this problem, leaving a solution for the archives.

Thanks,
Giovanni.

Michael Newman

unread,
May 31, 2007, 1:08:46 PM5/31/07
to Django users
Because of the dependants you might need to either manually add the
tables to the db or if you don't have any values in that table delete
all the rows in pizza and run syncdb again. Sometimes when working
within the same class the syncdb doesn't change the database all the
way (and rightly so). Give that a try and if it still doesn't work
we'll try a different solution.

Gio

unread,
Jun 1, 2007, 2:56:16 AM6/1/07
to Django users
I deleted the db and resync done, but still got the same problem.
I add a few toppings and it works.
I add one pizza with some toppings and their quantities: admin says
everything is right, but really it isn't: pizza_pizzatopping is an
empty table, while pizza_pizza got the right entry. The pizza is
saved, his toppings aren't.

I switched to an easier version:

class Topping(models.Model):
name = models.CharField(maxlength=255, unique=True)

def __str__(self):
return self.name

class Admin:
pass


class Pizza(models.Model):
name = models.CharField(maxlength=255, unique=True)

def __str__(self):
return self.name

class Admin:
pass


class PizzaTopping(models.Model):
pizza = models.ForeignKey(Pizza, core=True)


topping = models.ForeignKey(Topping)
amount = models.IntegerField()

class Admin:
pass

def __str__(self):
return '%s, %i %s' % (self.pizza, self.amount, self.topping)

And this is working, of course.

I'm using the latest Django code available through subversion, I'm
suspecting a bug.
Should I file an entry in the bug tracker?

Anyone having better luck with other Django versions?

Malcolm Tredinnick

unread,
Jun 1, 2007, 3:15:34 AM6/1/07
to django...@googlegroups.com
On Fri, 2007-06-01 at 06:56 +0000, Gio wrote:
> I deleted the db and resync done, but still got the same problem.
> I add a few toppings and it works.
> I add one pizza with some toppings and their quantities: admin says
> everything is right, but really it isn't: pizza_pizzatopping is an
> empty table, while pizza_pizza got the right entry. The pizza is
> saved, his toppings aren't.
>
> I switched to an easier version:
[...]

> And this is working, of course.

The usual process when you have one version of code that is working and
a more complicated version that is not working is to add features to the
working version, one at a time, until it stops working. In that way you
will be able to narrow down exactly which is the problematic step and
then work out, for example, if you do that step first, does it still
fail. Tracking down precisely where the problem lies is going to be
helpful to anybody trying to fix/understand this problem.

Regards,
Malcolm


Gio

unread,
Jun 1, 2007, 3:46:42 AM6/1/07
to Django users

> The usual process when you have one version of code that is working and
> a more complicated version that is not working is to add features to the
> working version, one at a time, until it stops working. In that way you
> will be able to narrow down exactly which is the problematic step and
> then work out, for example, if you do that step first, does it still
> fail. Tracking down precisely where the problem lies is going to be
> helpful to anybody trying to fix/understand this problem.
>
> Regards,
> Malcolm

Yes, but the problem here is already at the level you point to.
Take my code version and replace

class PizzaTopping(models.Model):
pizza = models.ForeignKey(Pizza, core=True)

with

class PizzaTopping(models.Model):
pizza = models.ForeignKey(Pizza, edit_inline = models.TABULAR,
core=True)

and you will switch from a working version to a crappy one.
edit_inline = models.STACKED is no better.
I miss something?

I would like to add that I think that this "generalized ManyToMany" is
very useful, shouldn't this be mentioned in the official
documentation?
As 42th Model Example?
I think this is excellent during first phases of Django learning as it
exposes you directly to the inner functionality of the ManyToMany
shortcut.

Let me go even further: what about a new option for ManyToManyField
letting you declare the amount as seen in our Pizza-Topping example?
It could be really relevant when using Django for developping web-
based ERP and similar things.
Bizarre?

Giovanni.

Malcolm Tredinnick

unread,
Jun 1, 2007, 4:12:28 AM6/1/07
to django...@googlegroups.com
On Fri, 2007-06-01 at 07:46 +0000, Gio wrote:
>
> > The usual process when you have one version of code that is working and
> > a more complicated version that is not working is to add features to the
> > working version, one at a time, until it stops working. In that way you
> > will be able to narrow down exactly which is the problematic step and
> > then work out, for example, if you do that step first, does it still
> > fail. Tracking down precisely where the problem lies is going to be
> > helpful to anybody trying to fix/understand this problem.
> >
> > Regards,
> > Malcolm
>
> Yes, but the problem here is already at the level you point to.
> Take my code version and replace
>
> class PizzaTopping(models.Model):
> pizza = models.ForeignKey(Pizza, core=True)
>
> with
>
> class PizzaTopping(models.Model):
> pizza = models.ForeignKey(Pizza, edit_inline = models.TABULAR,
> core=True)
>
> and you will switch from a working version to a crappy one.
> edit_inline = models.STACKED is no better.
> I miss something?

OK. I read your last reply and the bit you quoted from an earlier part
of the thread and there were three differences in the ForeignKey
(help_text, edit_inline and num_in_admin). I hadn't realised that wasn't
the most accurate summary of current events.

>
> I would like to add that I think that this "generalized ManyToMany" is
> very useful, shouldn't this be mentioned in the official
> documentation?
> As 42th Model Example?

It is already documented. See model example number 9.

> I think this is excellent during first phases of Django learning as it
> exposes you directly to the inner functionality of the ManyToMany
> shortcut.

Many people would argue that being exposed to the internals of how
something works is not something they want during the first phase. Just
goes to prove (again) that different people learn in different ways and
what is easier for some people is harder for others.

>
> Let me go even further: what about a new option for ManyToManyField
> letting you declare the amount as seen in our Pizza-Topping example?
> It could be really relevant when using Django for developping web-
> based ERP and similar things.
> Bizarre?

There's discussion on the developer's list at the moment about making it
possible to add fields to the intermediate many-to-many table that would
allow people to do things if they wanted. However, it's definitely not
something we would want to add to the ManyToManyField. It's no longer
many-to-many, then -- it's been restricted.

Regards,
Malcolm


Michael Newman

unread,
Jun 1, 2007, 4:17:10 AM6/1/07
to Django users
I use this code in quite a few of my programs and never have issues. I
don't know why you are having this problem and it is not throwing any
errors. Are you using the Web interface at all? If so when saving are
there any errors. If the objects aren;t saving to the db generally
Python throughs some erros that can point the to the problem.
The next guess I would tell you is to use the related_name variable to
rename the foreign key so the code doesn't confuse the variables.

Malcolm is right on this one; you need to figure out exactly what is
going on before you submit a bug, because otherwise you are just
wasting the programmers' time trying to reproduce the problem. Let's
exhaust all the other possibilities first and figure out what is going
on.

Hope that helps,
Mn

Gio

unread,
Jun 1, 2007, 4:44:11 AM6/1/07
to Django users
@Malcom
Thanks. I forgot about that example!

@Michael
This is what I do:
1. I create a new project with the model file made by a copy-and-paste
of the code I posted above. I add my new app to configuration files.
2. I create a new db and I create tables it with syncdb
3. manage.py runserver and I go to admin web interface, accessing it
with username and password
4. I see all the right things there, so I go on and I create a few
toppings
5. I create a pizza, giving to it the right toppings, in the right
amount
6. I hit the "save" button and I got a "success" answer

But: while the pizza is there (name got the right value) there aren't
toppings associated with it. Indeed inspecting the db the generalized
ManyToMany table we are using is still empty. Toppings table got the
right entries of course.
And no errors in my terminal where the server is running.

Doing same things with no "edit_inline" require a few more steps
(defining a pizza before associating toppings) but everything goes the
right way.
I'm always speaking about the admin web interface. Never touched the
shell during the whole process.

Where should I look further?

Regards,
Giovanni.

Gio

unread,
Jun 1, 2007, 4:46:22 AM6/1/07
to Django users
Let me add that it isn't a Python version problem, it's the same with
python 2.4 and 2.5.

Giovanni.

chrominance

unread,
Jun 1, 2007, 5:24:10 AM6/1/07
to Django users
class PizzaTopping(models.Model):
pizza = models.ForeignKey(Pizza,
help_text = 'Toppings to go on Pizza:
num in admin is how many will show up in Pizza',
edit_inline = models.TABULAR,
num_in_admin = 3,
core=True)
topping = models.ForeignKey(Topping)
amount = models.IntegerField()

You have core=True set on the same field that has edit_inline, which
doesn't make any sense if you read the model documentation. core=True
should only be set on fields other than the ForeignKey field with
edit_inline set. Setting core=True on a field means you're making that
field a required field; it's so the admin knows when a related object
should be deleted, simply by checking if all the core=True fields have
been filled out.

In this particular instance, you've set edit_inline on the pizza
field, which means PizzaTopping shows up as part of the Pizza admin.
Setting core=True on topping and amount would mean both topping and
amount would have to be filled out before the admin site will save a
PizzaTopping object. I don't know what happens if you set core=True
and edit_inline=models.X on the same field, but it probably isn't
pretty.

Gio

unread,
Jun 1, 2007, 5:38:13 AM6/1/07
to Django users
Yes, you are right.
Removing core=True solved the issue.

However I think an error message from admin web interface is needed
when things go wrong.
Or should the default model validator to point to the error?
As during all my experiments in the terminal where the server was
running I always got:
---------------------------
Validating models...
0 errors found.
---------------------------

Notice that if you don't put any core=True in this case you got a
validation issue:
---------------------------
Validating models...
pizza.pizzatopping: At least one field in PizzaTopping should have
core=True, because it's being edited inline by pizza.Pizza.
1 error found.
---------------------------

Thank you,
Giovanni.

chrominance ha scritto:

Reply all
Reply to author
Forward
0 new messages