ManyToMany field with two models

Hello everyone,

I’m a newbie in Django, it’s my first post here and I’m sorry if this question has been already asked.

I try to understand how I have to approach a ‘probably basic’ problem.

I have tow models

Person
name = …
address =…

Company
name = …
legal_form= …
VAT= …

And a third one RentContract
contractor =
contractant =

I would like for the contractor AND the contractant to be able to add multiple objects from the Person AND Company models.

I there a way to make a MANY TO MANY relationship to two models ? Should I have an intermediate model ? Should I have a parent model for PERSON and COMPANY and make a MANY TO MANY relationship to this parent model ?

Before posting I tried to search on stackoverflow, Carefully read the documentation… But still I do not understand the approach I should use…

Thanks in advance for your time.

Axel

This sounds to me to be more of a data model question than anything directly Django specific. I think I’d need to understand the relationships among your entities here a little better before making any specific suggestions.

What I’m understanding from what you’ve provided:

People - two types, contractor and contractant (customer? client?)? Is there any difference between the two?

Company - Is there a direct and required relationship between people and companies? (In other words, must either or both of the contractor / contractant be associated with a company?) Is there a difference between a “Contractor Company” and a “Contractant Company”?

RentContract - Is the contract a relationship between:

  • contractor and contractant?
  • contractor’s company and contractant?
  • contractor and contractant’s company?
  • contractor’s company and contractant’s company?
  • all of the above?

Hello Ken,

Thanks for your quick answer.

I’ll try to be more specific (model classes in the bottom)

For the RentContract

  • the contractor can be one or multiple Person object AND / OR one or multiple Company object
  • idem for the contractant

there is no difference between the contractor and the contractant. Both can be one or multiple Person OR /AND one or mulptiple Company.
I used these terms (contractant and contractor) just to specify who are the two parts of the contract, like a lessor and a lessee for a flat renting contract. If I continue using the real estate example here, in The RentContract the Lessee could bet composed of two Persons and one Company and the lessor just one Company. So it could be like having a ‘normal’ Many to Many relationship but with the sum of the objects from Person.objects.all() + Company.objects.all().

there is no required association between a Person and a Company (even if a Person could be associated with a Company)

I hope I did clarify…

class Company(models.Model):
name = models.CharField(max_length=100)
legal_form = models.CharField(max_length=35, default=None, blank=True, null=True)
address = models.ForeignKey(Address, on_delete=models.PROTECT, default=None, null=True, blank=True)
vat_number = models.CharField(max_length=100, default=None, null=True, blank=True)
registration_number = models.CharField(max_length=100, default=None, null=True, blank=True)
phone = models.CharField(max_length=50, default=None, blank=True, null=True)
email = models.EmailField(default=None, blank=True, null=True)
internal_uuid = models.CharField(max_length=35, default=None, null=True, blank=True)

timestamp_updated = models.DateTimeField(auto_now=True)
timestamp_created = models.DateField(auto_now_add=True)
comment = models.TextField(default=None, blank=True, null=True)
note = models.TextField(default=None, blank=True, null=True)

def __str__(self):
    return f"{self.name} {self.legal_form}"

class Meta:
    verbose_name_plural = 'Companies'
    ordering = ('name',)

class Person(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
address = models.ForeignKey(Address, on_delete=models.PROTECT, default=None, null=True, blank=True)
company = models.ManyToManyField(Company, through=‘Position’, default=None, blank=True)
phone = models.CharField(max_length=50, default=None, blank=True, null=True)
email = models.EmailField(default=None, blank=True, null=True)

timestamp_updated = models.DateTimeField(auto_now=True)
timestamp_created = models.DateField(auto_now_add=True)
comment = models.TextField(default=None, blank=True, null=True)
note = models.TextField(default=None, blank=True, null=True)

def __str__(self):
    return f"{self.lastname} {self.firstname}"

class Meta:
    ordering = ('lastname',)

Cool, Yes, that helps a lot! I’ll have to think about this a little… It’s doable, but I’ve got at least two ideas in mind and I want to noodle the situation a little before responding.

Thank you very much for your help and your time…

Ok, based upon the information I have here, and not really having an idea of what’s going to be done with this data in terms of presentation, reporting, or maintenance beyond fundamental data entry, my current idea would be to go with the most basic, pragmatic solution. Something like:

class RentContract(Model):
    contractor_people = ManyToMany(Person, ...)
    contractor_companies = ManyToMany(Company, ...)
    contractant_people = ManyToMany(Person, ...)
    contractant_companies = ManyToMany(Company, ...)

Then I’d add some constraints and guards to ensure that (contractor_people not null or contractor_companies not null) and (contractant_people not null or contractant_companies not null) along with ensuring the intersection of contractor_people and contractant_people is null. (Whether or not the intersection of contractor_companies and contractant_companies must be null is highly context sensitive.)

As a side note, I had considered two other alternatives, either multi-table inheritance for “contract_entities” having Person and Company inherit from the base entity, allowing the RentContract table to have a single ManyToMany column for each role, or using GenericForeignKeys in the ManyToMany join table. However, in general I believe that either of those solutions add unnecessary complexity in the absence of a compelling reason to require them, and so I lean toward the more basic and straight-forward solution.

I wouldn’t go so far to say that any one of these is “right” vs “wrong” - data modelling of business entities is as much art as it is science. You can make a good case for picking any of these. In this case, I’ve chosen this pattern to minimize the number of edge-cases or boundary conditions that might be created that aren’t obvious to detect or prevent.

Ken, Thank you very much for your time. It’s highly appreciated…

I must say tat it’s a very good first contact with the Django Community.

Thanks again

Thank you for your kind comments, and Welcome! I think you’ll generally find us to be a friendly bunch.