简体   繁体   中英

Django Multiple Foreign Key Model

Here is my code, is there a more efficient way of writing it? I'm not cool with it.

Basically, both Company and Supplier models should be abble to have several contacts with several phone numbers.

class Contact(models.Model):
    company = models.ForeignKey(Company, related_name='contact',
        blank=True, null=True)
    supplier = models.ForeignKey(Supplier, related_name='contact',
        blank=True, null=True)
    name = models.CharFields(max_length=50, blank=True, null=True)
class Phone(models.Model):
    contact = models.ForeignKey(Contato, related_name='phone')
    number = models.CharFields(max_length=50, blank=True, null=True)

There are at least four approaches to the "both companies of type X and companies of type Y have contacts" problem:

  1. Two tables: Companies and Contacts. There is an enumerated property on Companies which has values X and Y, and every contact has one foreign key to a company.
  2. Three tables: one table X for X-companies, another table Y for Y-companies, and one table for contacts C, where C has foreign keys to both X and Y. The foreign keys can be nullable.
  3. Four tables: X, Y, Cx, and Cy, tracking the two different contacts for the two different sorts of companies separately. (So Cx has a foreign key to X, and Cy has a foreign key to Y).
  4. Five tables: you start with these three tables X, Y, and C, but instead of adding nullable pointers to C you add two many-to-many joining tables XC and YC.

These have different demands of the underlying data. You are right now using the three-table solution (X, Y, C) = (Company, Supplier, Contact) . This is great if some contacts will be shared between companies and suppliers, so that you sometimes need to ask "who's the contact who sits between this company and that supplier?". I maintain a database where the two-table solution is used, and when it was initially adopted it was a good solution (why duplicate all of that logic about addresses and contacts when we don't have to?) but today it seems clunky (because the "Company" table contains fields which only make sense for X's and Y's separately).

Probably the easiest to deal with in my case, if we migrated, would be the four-table solution: keep contacts for X-type companies totally separate from contacts for Y-type companies. If you start with your current approach then the five-table solution would be the obvious generalization if you face similar growing pains in your application.

As for tracking phone numbers, you have some limited options:

  1. Store a bunch of columns in the Contacts table, one for each separate phone number. This gets ugly real fast but it's the quick-and-easy way to do it. This is called "denormalized" data.
  2. Store JSON in a text field in the Contacts list. Phone numbers are unlikely to be searched over much; it's just not very common to say "I have this number, who does it belong to?", so you can easily denormalize. This also lets you do things like {"mon thru thurs": 12025551234, "fri, sat": 12025554321} , storing simple custom annotations for the numbers.
  3. Create a phone table, like you've done now. This is the most general way to do this, and if you need those sorts of annotations you can add another text field to that table.

If you mix option 3 here with option 3 above (four tables plus an explicit phone table) then you'll probably want to have separate phone tables as well as separate contact tables; Px and Py each with a foreign key to Cx and Cy.

To me Company and Supplier models could be the same. Since most suppliers are companies right? If they are basically the same than merge Company and Supplier models like this:

class Company(models.Model):
    name = models.CharFields(max_length=50)
    is_supplier = models.BooleanField(default=False)  
    suppliers = models.ManyToManyField("self", 
        limit_choices_to={'is_supplier': True})


class Contact(models.Model):
    name = models.CharFields(max_length=50)    
    company = models.ForeignKey(Company)


class Phone(models.Model):
    number = models.CharFields(max_length=50)    
    contact = models.ForeignKey(Contact)

This is Chris Drosts '2 table' solution. If you need supplier specific fields than add a Supplier model and link it to Company with a OneToOne:

class Supplier(models.Model):
    ... # Some supplier specific fields.
    company = models.OneToOneField(Company)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM