I was building a project management app in Django where users could own projects and collaborate with others. Simple enough, right? But when I tried to link Account and Project with a many-to-many relationship (so contributors could join projects), Django hit me with cryptic errors like “not a foreign key” and “missing a foreign key”.
Turns out, I misunderstood how through models and through_fields work. Here’s how I fixed it—and how you can avoid my mistakes.
The Original Problem Owners vs. Contributors
My goal was straightforward:
- A
Projectshould have one owner (anAccount). - Multiple
Accountusers could be contributors to aProject.
But my initial code looked like this:
# Broken Model Setup
class Project(models.Model):
project_name = models.CharField(max_length=50)
class Account(models.Model):
username = models.CharField(max_length=50)
projects = models.ManyToManyField(
Project,
through='AccountProjects',
through_fields=('owner', 'project'), # Wait, what’s this??
related_name='contributors'
)
class AccountProjects(models.Model):
owner = models.ForeignKey(Account, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
The Errors I Faced
'AccountProjects.owner' is not a foreign key to 'Project'
I’d incorrectly assumedthrough_fields=('owner', 'project')meant “useownerfromAccountandprojectfromProject“. Nope. Django was yelling: “Yourownerfield points toAccount, notProject!”Missing foreign key to 'Account' or 'Project'
MyAccountProjectsmodel was missing a foreign key to one of the related models. Django requires the intermediate (“through”) model to have FKs to both sides of the M2M relationship.
Simplify and Clarify Relationships
Here’s the corrected code:
# Fixed Models
class Project(models.Model):
project_name = models.CharField(max_length=50)
# Owner is a direct FK (one owner per project)
owner = models.ForeignKey('Account', on_delete=models.CASCADE, related_name='owned_projects')
class Account(models.Model):
username = models.CharField(max_length=50)
# Contributors are linked via M2M through AccountProjects
projects = models.ManyToManyField(
Project,
through='AccountProjects',
related_name='contributors'
)
class AccountProjects(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
Why This Works
through_fieldsRemoved
Thethrough_fieldsparameter is only needed if there’s ambiguity (e.g., multiple FKs to the same model). Here,AccountProjectshas one FK toAccountand one toProject, so Django automatically figures it out.- Explicit Foreign Keys
TheAccountProjectsmodel now has bothaccountandprojectFKs, satisfying Django’s requirement for intermediate models. - Separate Owner Field
Theowneris defined directly onProject, making it clear that ownership is distinct from contributors.
Bonus: Query Owners vs. Contributors
With the fixed setup, you can now:
- List Contributors (Excluding the Owner)
def get_contributors(project):
return Account.objects.filter(accountprojects__project=project).exclude(id=project.owner.id)
- Check if a User is a Contributor
def is_contributor(user, project):
return AccountProjects.objects.filter(account=user, project=project).exists()
- Add Roles to Contributors (e.g., “Editor”, “Viewer”)
class AccountProjects(models.Model):
ROLES = (("viewer", "Viewer"), ("editor", "Editor"))
account = models.ForeignKey(Account, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
role = models.CharField(max_length=20, choices=ROLES)
Final Thoughts
Here’s what I learned the hard way:
throughModels Are Bridges: They need explicit connections to both sides of the relationship.- Ownership ≠ Contribution: Separate direct FKs (like
owner) from M2M fields for clarity. - Django’s Errors Are Clues: They’re frustrating but often point directly to the fix.
Next steps? Run makemigrations and migrate, then test in Django Admin to ensure owners and contributors behave as expected.

