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.
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
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
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
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
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.
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?
> 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
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.
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
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
@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.
Giovanni.
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.
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: