3

Is there something equivalent to has_one :through in Django?

I have User, Company, and CompanyMember models. CompanyMember has foreign keys to User and Company. A User can belong to a Company through the CompanyMember model. Assume User is unique in CompanyMember

In Rails, this relationship would look something like

class User < ActiveRecord::Base
  has_one :company_member
  has_one :company, :through => :company_member
end

class Company < ActiveRecord::Base
  has_many :company_members
end

class CompanyMember < ActiveRecord::Base
  belongs_to :company
  belongs_to :user
end

And I would be able to use user.company to get the user's company.

In Django, I have my models like this

from django.contrib.auth.models import User
from django.db import models


class Company(models.Model):
    name = models.CharField()


class CompanyMember(models.Model):
    company = models.ForeignKey(Company)
    user = models.ForeignKey(User, unique=True)
    is_company_admin = models.BooleanField(default=False)

Is it possible to get user.company to work like in Rails? I also like to be able to use it in queryset filters, like user__company=company.

kentor
  • 1,104
  • 2
  • 9
  • 19

1 Answers1

2

This should do what you want:

from django.contrib.auth.models import User
from django.db import models


class Company(models.Model):
    name = models.CharField()
    users = models.ManyToManyField(User, through='CompanyMember')

class CompanyMember(models.Model):
    company = models.ForeignKey(Company)
    user = models.ForeignKey(User, unique=True)

More info: https://docs.djangoproject.com/en/1.7/topics/db/models/#extra-fields-on-many-to-many-relationships

Update:

To access the company for a given user, you can now do this:

myuser.company_set.first()

You probably don't want to change the built-in User model. But you can add your own convenience methods/properties fairly easily by using a proxy model, ex:

class MyUser(User):
    @property
    def company(self):
        return self.company_set.first()
    class Meta:
        proxy=True

Then, any time you would normally use a User object, use MyUser instead and it will have that convenience property for you. Ex:

MyUser.objects.get(id=123)

will be the same record as

User.objects.get(id=123)

but you'll have the extra convenience methods/properties on the object. Ex:

myuser = MyUser.objects.get(id=123)
myuser.company
dylrei
  • 1,728
  • 10
  • 13
  • 2
    FWIW, it is not necessary to provide a model for the "middle" table of an M2M, this is only needed if you plan to store data *about* the relationship. – dylrei Jan 30 '15 at 01:01
  • 1
    Thank you, I've read through the documentation. Indeed I am storing extra data. So to access the company from the user I would have to write `user.company_set.first()`? I guess that would work but really it would be nicer if it were just `user.company` since in my case a user can only belong to one company, and this is enforced with the unique key on `user` in `CompanyMember`. – kentor Jan 30 '15 at 01:16
  • you can use user.company, but you have to use OneToOneField in django. You are using ForeignKey, which is equivalent to many to one – Arpit Singh Aug 26 '19 at 10:19