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Â
Project
 should have one owner (anÂAccount
). - MultipleÂ
Account
 users could be contributors to aÂProject
.
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 assumedÂthrough_fields=('owner', 'project')
 meant “useÂowner
 fromÂAccount
 andÂproject
 fromÂProject
“. Nope. Django was yelling:Â “YourÂowner
 field points toÂAccount
, notÂProject
!”Missing foreign key to 'Account' or 'Project'
MyÂAccountProjects
 model 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_fields
 Removed
TheÂthrough_fields
 parameter is only needed if there’s ambiguity (e.g., multiple FKs to the same model). Here,ÂAccountProjects
 has one FK toÂAccount
 and one toÂProject
, so Django automatically figures it out.- Explicit Foreign Keys
TheÂAccountProjects
 model now has bothÂaccount
 andÂproject
 FKs, satisfying Django’s requirement for intermediate models. - Separate Owner Field
TheÂowner
 is defined directly onÂProject
, 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:
through
 Models 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.