Thank you for the feedback. I had assumed that concretely inheriting from Person to create new Leader and Member classes wouldn’t work without also using a generic foreign key to relate both of these new classes to Team. But I just tried it and it seems to work if I simply leave Person as the foreign key. By “work”, I mean that creating a Report and calling report.person will return a Leader or Member instead of a Person, if a Leader or Member was assigned to the person field in the Report.
Below are the new class definitions, updated with some additional fields to distinguish between classes:
from django.db import models
class Team(models.Model):
name = models.CharField(max_length=255)
class Person(models.Model):
name = models.CharField(max_length=255)
class Leader(Person):
leader_field = models.CharField(max_length=255)
team = models.OneToOneField(Team, on_delete=models.CASCADE)
class Member(Person):
member_field = models.CharField(max_length=255)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
class Report(models.Model):
team = models.ForeignKey(Team, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
Going into the shell with python manage.py shell and creating some objects like this…
from myapp.models import Team, Person, Leader, Member, Report
team = Team.objects.create(name="A")
alice = Leader.objects.create(name="Alice", leader_field="Alice Leader", team=team)
bob = Member.objects.create(name="Bob", member_field="Bob Member", team=team)
charlie = Member.objects.create(name="Charlie", member_field="Charlie Member", team=team)
report_a = Report.objects.create(team=team, person=alice)
report_b = Report.objects.create(team=team, person=bob)
report_c = Report.objects.create(team=team, person=charlie)
…then Alice is a Leader, and is returned as a Leader from report_a, as shown below. This means that fields defined in Person (the name field) and Leader (the leader_field) can both be accessed, even though the foreign key in Report is to a Person instead of a Leader (I didn’t expect this behavior):
>>> report_a.person
<Leader: Leader object (1)>
>>> report_a.person.name
'Alice'
>>> report_a.person.leader_field
'Alice Leader'
Similarly for Bob, a Member:
>>> report_b.person.name
'Bob'
>>> report_b.person.member_field
'Bob Member'
The team leader (as an object) and members (as a query set) can be accessed directly from a Team instance:
>>> team.leader
<Leader: Leader object (1)>
>>> team.leader.name
'Alice'
>>> team.member_set.all()
<QuerySet [<Member: Member object (2)>, <Member: Member object (3)>]>
>>> for member in team.member_set.all():
... print(member.name)
...
Bob
Charlie
Trying to add a second leader fails, as expected, due to the one-to-one constraint from Leader to Team:
>>> Leader.objects.create(name="Dave", team=team)
django.db.utils.IntegrityError: UNIQUE constraint failed: myapp_leader.team_id
Adding more members works, as expected:
>>> Member.objects.create(name="Dave", team=team)
<Member: Member object (4)>
I thought about doing this the other way, too, without concrete inheritance… If instead of doing inheritance as above, I added a role field to the Person class with options of “Leader” and “Member”, I think I would need to add a custom save() method to prevent more than one Person with a role of Leader from being added to a Team. Additionally, I think I’d need to add methods (or @property) to the Team class to be able to directly return team.leader and team.member_set as an object and a query set, respectively, i.e., instead of writing queries to access them each time, just put the queries in methods or @property in Team. (accessing them directly from team would make templating easier, etc.)
All of that behavior comes automatically when using concrete inheritance, though, so inheritance seems like a more direct approach in this case. I’ve seen people say that concrete inheritance is not a great idea, though. What are the potential pitfalls?