How do you bind a model to two other, already related models, and display the information?

风格不统一 提交于 2020-01-05 05:56:19

问题


Apologies if the title is not up to scratch, I wasn't sure how to word my problem. Please feel free to edit in a better one. Here is what I am trying to do:

I am trying to create a relational database using a few different models. The idea is:

  • A contract is unique
  • Any contract can have (and share) any number of projects
  • Unique information needs to be stored about each specific project for a specific contract

So far, the first two bullet points I have working as expected. The third point seems to be working, at least in the admin panel. I can assign a piece of info to a specific project of a specific contract.

First of all, am I going about this the right way?

Second, as part of a view, how do I list all of the information above at once? Like so:

-ContractName
    -ProjectName
    -ProjectName
        -SpecificInfo/Comments
    -ProjectName

Here is the model code I currently have:

from django.db import models

class Project(models.Model):
    identifier = models.CharField(max_length=30)
    description = models.CharField(max_length=100)

    def __unicode__(self):
        return self.identifier

class Contract(models.Model):
    number = models.CharField(max_length=30)
    name = models.CharField(max_length=75)
    projects = models.ManyToManyField(Project)

    def __unicode__(self):
        return self.number

class Info(models.Model):
    contract = models.ForeignKey(Contract)
    project = models.ForeignKey(Project)
    title = models.CharField(max_length=75)
    info_text = models.TextField()

    def __unicode__(self):
        return self.title

Here is the view, which currently only displays contracts and any associated projects:

<ul>
{% for contract in contracts %}
    <b><li>{{ contract.number }} - {{ contract.name }}</li></b>
        {% for project in contract.projects.all %}
            <ul>
                <li>{{ project.identifier }} - {{ project.description }}</li>
            </ul>
        {% endfor %}
{% endfor %}
</ul>

回答1:


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.



来源:https://stackoverflow.com/questions/11084596/how-do-you-bind-a-model-to-two-other-already-related-models-and-display-the-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!