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?