2đź‘Ť
Bullet for bullet:
-
A contract is unique
Unique how? You can make it unique for the number or unique for the name or even unique for a particular client (although that wouldn’t make much sense, because you probably will want clients to have multiple contracts. Repeat business FTW!). To make a particular field unique, you just add
unique=True
. -
Any contract can have (and share) any number of projects
Contracts have many projects and projects have many contracts, so that’s an obvious M2M, which you’ve got covered.
-
Unique information needs to be stored about each specific project for a specific contract
So this is where you went sideways. If unique information needs to be stored on the relationship, i.e. it applies to specifically to a combination of contract and project, then you need a “through” model. You basically created that with the
Info
model, but you need to tell Django to actually use this model and not its default implicit model for the relationship, e.g.:projects = models.ManyToManyField(Project, through='Info')
(I would suggest renaming Info
to something more descriptive though, like ContractProject
, to indicate that it’s the join table for these two models.)
With that in place, your template would look roughly like (based on your current code, and not my suggested name change):
{% for contract in contracts %}
{{ contract.name }}
{% for info in contract.info_set.all %}
{{ info.project.name }}
{{ info.title }}
{{ info.info_text }}
{% endfor %}
{% enfor %}
So the idea here is that instead of getting projects, directly from the contract, you need to get the join table instances (so you can access that info), and then pull the project through that. Bear in mind though that this is relatively expensive, you’ve got one query for every contract to get the Info
instances and then for each of those queries another query for the project. So if you had 3 contracts and 3 projects for every contract, you’re already talking 1+3*3 queries or 10, total. That number will of course scale exponentially the more contracts/projects you have.
In Django 1.4, you can use the new prefetch_related
to fetch all the info instances for all the contracts in one query, which cuts down the queries dramatically, but you’ll still have a query for each project.
contracts = Contract.objects.prefetch_related('info')
Using the previous example, your query count would be 1+1+3, or 5 total, so you would have cut it in half. Thankfully, though, Django even lets you take this farther by support join syntax with prefetch_related
.
contracts = Contract.objects.prefetch_related('info_project')
That will fetch all the contracts, then fetch all the info instances for those contracts and finally fetch all the projects for those info instances. Again, using the previous example, that brings your query count down to 1+1+1 for a total of 3.
If you’re not running Django 1.4, you can get much the same functionality from a package called django-batch-select.