assign a property to a many to many relationship - django

I'm making a couple of models for cooking, in django, Recipes and Ingredients.
I use a many to many field to relate both. Now, I'd like to assign a number to each relationship, so going from
recipe.ingredients = [sugar,egg]
to
recipe.ingredients = {sugar:200,egg:2}
How can I do that? It is 100% necessary to explicitly build a third model ingredients_recipes? The table should exist already, but I want to know if is possible to use the many to many field directly.

Yes, you need to create the intermediate model with your additional fields. You can then specify the intermediate in a through argument to the ManyToManyField, for example:
class Recipe(models.Model):
#...
ingredients = models.ManyToManyField(Ingredients, through="RecipeIngredients")
class RecipeIngredients(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
amount = models.IntegerField()
class Meta:
unique_together = ('recipe', 'ingredient')
See also the official documentation: Extra fields on many-to-many relationships

Related

Model Inheritance of ManyToMany Through

Suppose I have following models:
class Author(Model):
name = CharField()
class Publication(Model):
name = CharField()
authors = ManyToManyField(Author)
class Meta:
abstract = True
class Book(Publication):
pass
class Article(Publication):
pass
class Journal(Publication):
pass
How to change code so that I can add through table to authors? If I write authors = ManyToManyField(Author, through='Relationship'), it will not work.
Django thankfully takes care of intermediate tables without any coding. You can even access them using .through on the M2M relationship manager, e.g. one_publication.authors.through.
You only need to specify the through table if you want to manage it yourself, e.g. because you want to add more fields than just the foreign keys of the two related entities. Is that the case here?
If yes, you have to create a Relationship model (consider giving it a more helpful name) that contains foreign keys to Publication and Author.
Update: If you want to add a default order to object lists from many-to-many relationships, an intermediate model would indeed be one way to achieve this:
class Relationship(models.Model):
author = models.ForeignKey(Author)
publication = models.ForeignKey(Publication)
# Any further fields that you need
class Meta:
ordering = ['author__last_name', 'author__first_name']
However, you can just as easily order your m2m relationships on querying them, without any intermediate model or through manager:
book = Book.objects.first()
for author in book.authors.order_by('last_name', 'first_name'):
# Will print in alphabetical order
print(f'Author: {author.first_name} {author.last_name}')
One caveat is that if you use prefetching, you need to specify the ordering in a Prefetch object to avoid executing the query twice, first for prefetching without ordering, than on access with ordering.
# 2 queries plus one for every book
books = Book.objects.prefetch_related('authors')
for book in books:
for author in book.authors.order_by('last_name', 'first_name'):
print(f'Author: {author.first_name} {author.last_name}')
# 2 queries regardless of number of books
books = Book.objects.prefetch_related(Prefetch('authors',
queryset=Author.objects.order_by('last_name', 'first_name')))
for book in books:
for author in book.authors.all():
print(f'Author: {author.first_name} {author.last_name}')

Django 1.8 - Intermediary Many-to-Many-Through Relationship - What is the consequence of where 'ManytoManyField' is used?

An example Many-to-Many through relationship in Django:
class First(models.Model):
seconds = models.ManyToManyField(Second, through='Middle')
class Middle(models.Model):
first = models.ForeignKey(First)
second = models.ForeignKey(Second)
class Second(models.Model):
Following the documentation on intermediary models, only one model of the pair to be related contains the ManytoManyField, model First in the example above. Is this correct?
If so, which model should contain the ManytoManyField field? Are there any differences in using the relationship from either end depending on where the ManytoManyField is?
Thanks
EDIT (I should have been clearer):
I'm interested in an Intermediary table because I will have additional data to store on the relationship.
When I say usage, I don't mean defining the models, I mean using the relationship (otherwise I'd let Django do it's thing).
If I want all Seconds related to a First, would it be exactly the same as getting all Firsts related to a Second, or would the ManytoManyField make one direction easier to do than the other by introducing any extra functionality?
There shouldn't be a difference from an operational perspective, so the only difference would be in the definition of the model and things that affect it (for instance, Manager classes).
You also don't always need to define a "through" class. Django does that automatically for you, and all that class really does is maintain a third table to track the respective IDs for each related record in the two other tables. You have to decide whether you want to add anything to that third table that is important.
For instance, say you are designing a web app for a conference. They might want to store information about the attendees (both individuals and companies), as well as the speakers and sponsors (also individuals and companies). Part of your models for companies might look like this:
class Company(models.Model):
name = models.CharField(max_length=100)
sponsored_segment = models.ForeignKey(ConferenceSegment, null=True)
class ConferenceSegment(models.Model):
title = models.CharField(max_length=100)
But that gets cumbersome quickly, and you'll have lots of attending companies that have nothing to do with sponsoring. Also, you might want to track their rank/package on the website (after all, bigger sponsors get bigger placement):
class Company(models.Model):
name = models.CharField(max_length=100)
class ConferenceSegment(models.Model):
title = models.CharField(max_length=100)
sponsors = models.ManyToManyField(Company, through=u'Sponsor', related_name=u'sponsored_segments')
class Sponsor(models.Model):
company = models.ForeignKey(Company)
segment = models.ForeignKey(ConferenceSegment)
rank = models.PositiveIntegerField()
Notice also the "related_name" attribute in the ManyToManyField. This means that we can access the ConferenceSegment object via a Company instance by using that name:
c = Company.objects.get(...)
segments = c.sponsored_segments.all()
Hope this helps.
When you add a many to many field to a model a separate table is created in the database that stores the links between two models. If you don't need to store any extra information in this third table then you don't have to define a model for it.
class First(models.Model):
seconds = models.ManyToManyField(Second, related_name='firsts')
class Second(models.Model):
pass
I can't think of any difference between defining the many to many field in the First or Second models:
class First(models.Model):
pass
class Second(models.Model):
firsts = models.ManyToManyField(First, related_name='seconds')
In both cases usage is the same:
firsts = my_second.firsts
seconds = my_first.seconds

Many to many association

I don't understand why we have to use ManyToManyField to declare a many to many association. To do so, I would create another table with two foreign keys, period!
Here is an example from the doc https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
extraField = models.DateField()
I would just write:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
extraField = models.DateField()
Is it correct? What is the difference?
There is nothing wrong with defining an intermediate model for the relationship. That way you can store extra information on the intermediate model (Membership), like when the person joined the group, or if the membership is still valid. However, most of the time you don't need the extra information and only want to store which person is member of which group. In those cases, you could just use a simple ManyToManyField. Django makes it very easy to work with those fields, so you can do group.members.add(user) and group.members.delete(user), compare that to:
Membership.objects.create(user=user, group=group)
Membership.objects.get(user=user, group=group).delete()
Disclaimer: pseudo-code, might not actually work
You can still use ManyToManyField with an intermediate model, this allows for most of the Django ManyToMany conveniences, but with some restrictions:
Unlike normal many-to-many fields, you can’t use add, create, or assignment (i.e., beatles.members = [...]) to create relationships.
Why? You can’t just create a relationship between a Person and a Group - you need to specify all the detail for the relationship required by the Membership model. The simple add, create and assignment calls don’t provide a way to specify this extra detail. As a result, they are disabled for many-to-many relationships that use an intermediate model. The only way to create this type of relationship is to create instances of the intermediate model.
The remove() method is disabled for similar reasons. However, the clear() method can be used to remove all many-to-many relationships for an instance.
Once you have established the many-to-many relationships by creating instances of your intermediate model, you can issue queries. Just as with normal many-to-many relationships, you can query using the attributes of the many-to-many-related model.
Source: docs.djangoproject.com

Django forms with odd model relationship

I am working with an existing database that I can not modify and having some trouble trying to deal with presenting forms for modifying the database in Django. The structure in question is as follows and all models are unmanaged.
class Persons(models.Model):
personid = models.BigIntegerField(primary_key=True, db_column='PersonID')
....
class Phones(models.Model):
phoneid = models.BigIntegerField(primary_key=True, db_column='PhoneID')
number = models.CharField(max_length=60, db_column='Number', blank=True)
type = models.CharField(max_length=15, db_column='Type', blank=True)
...
class Personsphones(models.Model):
personphoneid = models.BigIntegerField(primary_key=True, db_column='PersonPhoneID')
personid = models.ForeignKey(Persons, db_column='PersonID')
phoneid = models.ForeignKey(Phones, db_column='PhoneID')
...
I want to create a form to display all of the 'Phones' associated with a particular 'Persons' and in addition be able to modify/add/remove 'Phones' belonging to a 'Persons'. Right now the only thing I can think of is to display the 'Phones' in a modelformset and then if one is added or removed manually set the 'Personsphones' relation. Any ideas on how to best deal with this model setup?
For making changes to your models you may want to use django-south http://south.aeracode.org/docs/
As far as displaying your 'Phone' under your forms.py you may want to set up class meta like so. With this any changes made to models will reflect on change
class Meta:
model = Persons
exclude = ('user')
In models you may want to use Foreignkey fore relationships between phones and Persons. Better seen in action here https://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey

Should I use a seperate table instead of many to many field in Django

I needed to assign one or more categories to a list of submissions, I initially used a table with two foreign keys to accomplish this until I realized Django has a many-to-many field, however following the documentation I haven't been able to duplicate what I did with original table.
My question is : Is there a benefit to using many-to-many field instead of manually creating a relationship table? If better, are there any example on submitting and retrieving many-to-many fields with Django?
From the Django docs on Many-to-Many relationships:
When you're only dealing with simple many-to-many relationships such
as mixing and matching pizzas and toppings, a standard ManyToManyField
is all you need. However, sometimes you may need to associate data
with the relationship between two models.
In short: If you have a simple relationship a Many-To_Many field is better (creates and manages the extra table for you). If you need multiple extra details then create your own model with foreign keys. So it really depends on the situation.
Update :- Examples as requested:
From the docs:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
You can see through this example that membership details (date_joined and invite_reason) are kept in addition to the many-to-many relationship.
However on a simplified example from the docs:
class Topping(models.Model):
ingredient = models.CharField(max_length=128)
class Pizza(models.Model):
name = models.CharField(max_length=128)
toppings = models.ManyToManyField(Topping)
There seems no need for any extra data and hence no extra model.
Update 2 :-
An example of how to remove the relationship.
In the first example i gave you have this extra model Membership you just delete the relationship and its details like a normal model.
for membership in Membership.objects.filter(person__pk=1)
membership.delete()
Viola! easy as pie.
For the second example you need to use .remove() (or .clear() to remove all):
apple = Toppings.objects.get(pk=4)
super_pizza = Pizza.objects.get(pk=12)
super_pizza.toppings.remove(apple)
super_pizza.save()
And that one is done too!