Related Name¶
Saffier is very flexible in the way you assemble your models and perform your queries.
One very common example is declaring ForeignKeys pointing it out to declared relationships among tables.
One situation that happens very often is the one where you would like to do the reverse query
.
What is the related name¶
Related name is an attribute that can be declared inside the ForeignKeys and can be used to specify the name of the reverse relation from the related model back to the model that defines the relation.
It is used to speficy the name of the attribute that can be used to access the related model from the reverse side of the relation.
Confusing? Nothing like a good example to clear the things out.
How does it work¶
related_name attribute¶
There are two ways of working with the related_name
.
The parameter¶
The related name can be declared directly inside the ForeignKeys
related_name
attribute where you specify explicitly which name you want to use.
Auto generating¶
This is the other automatic way. When a related_name
is not specified in the
ForeignKeys, Saffier will automatically generate the name for you with the
the following format:
<table-name>s_set
Imagine you have a model Team
that has a ForeignKey to another model
Organisation
.
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class Organisation(saffier.Model):
ident = saffier.CharField(max_length=100)
class Meta:
registry = models
class Team(saffier.Model):
org = saffier.ForeignKey(Organisation, on_delete=saffier.RESTRICT)
name = saffier.CharField(max_length=100)
class Meta:
registry = models
Because no related_name
was specified, automatically Saffier will call it organisations_set
.
Deep into the related_name¶
Let us create three models:
- Organisation
- Team
- Member
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class Organisation(saffier.Model):
ident = saffier.CharField(max_length=100)
class Meta:
registry = models
class Team(saffier.Model):
org = saffier.ForeignKey(Organisation, on_delete=saffier.RESTRICT)
name = saffier.CharField(max_length=100)
class Meta:
registry = models
class Member(saffier.Model):
team = saffier.ForeignKey(Team, on_delete=saffier.SET_NULL, null=True, related_name="members")
second_team = saffier.ForeignKey(
Team, on_delete=saffier.SET_NULL, null=True, related_name="team_members"
)
email = saffier.CharField(max_length=100)
name = saffier.CharField(max_length=255, null=True)
class Meta:
registry = models
Above we have the three models declared and inside we declared also three ForeignKeys.
org
- ForeignKey from Team to Organisation.team
- ForeignKey from Member to Team.second_team
- Another ForeignKey from Member to Team.
Let us also add some data to the models.
# This assumes you have the models imported
# or accessible from somewhere allowing you to generate
# these records in your database
acme = await Organisation.query.create(ident="ACME Ltd")
red_team = await Team.query.create(org=acme, name="Red Team")
blue_team = await Team.query.create(org=acme, name="Blue Team")
await Member.query.create(team=red_team, email="charlie@redteam.com")
await Member.query.create(team=red_team, email="brown@redteam.com")
await Member.query.create(team=blue_team, email="monica@blueteam.com")
await Member.query.create(team=blue_team, email="snoopy@blueteam.com")
We now can start querying using the related_name
.
Querying using the related_name¶
- We want to know all the teams of
acme
organisation
teams = await acme.teams_set.all()
# [<Team: Team(id=1)>, <Team: Team(id=2)>, <Team: Team(id=3)>]
Warning
Because in the org
foreign key of the Team
model no related_name
was not specified.
Saffier automatically generated the teams_set
that is accessible from the Organisation
.
Check the default behaviour to understand.
- We want the team where the members of the blue team belong to
teams = await acme.teams_set.filter(members=blue_team).get()
# <Team: Team(id=2)>
Nested transversal queries
Did you see what happened here? Did you notice the members
? The members is also a reverse query
that links the Member
model to the Team
.
This means you can also do nested and transversal queries through your models.
Let us continue with some more examples to understand this better.
- We want to know which team
charlie
belongs to
team = await acme.teams_set.filter(members__email=charlie.email).get()
# <Team: Team(id=1)>
Again, we use the members
related name declared in Member
model that is a
ForeignKey to the Team
and filter by the email
.
Nested Queries¶
This is where things get interesting. What happens if you need to go deep down the rabbit hole and do even more nested queries?
Ok, lets now add two more models to our example.
- User
- Profile
Warning
These are used only for explanation reasons and not to be perfectly aligned.
We should now have something like this:
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class Organisation(saffier.Model):
ident = saffier.CharField(max_length=100)
class Meta:
registry = models
class Team(saffier.Model):
org = saffier.ForeignKey(Organisation, on_delete=saffier.RESTRICT)
name = saffier.CharField(max_length=100)
class Meta:
registry = models
class Member(saffier.Model):
team = saffier.ForeignKey(Team, on_delete=saffier.SET_NULL, null=True, related_name="members")
second_team = saffier.ForeignKey(
Team, on_delete=saffier.SET_NULL, null=True, related_name="team_members"
)
email = saffier.CharField(max_length=100)
name = saffier.CharField(max_length=255, null=True)
class Meta:
registry = models
class User(saffier.Model):
id = saffier.IntegerField(primary_key=True)
name = saffier.CharField(max_length=255, null=True)
member = saffier.ForeignKey(
Member, on_delete=saffier.SET_NULL, null=True, related_name="users"
)
class Meta:
registry = models
class Profile(saffier.Model):
user = saffier.ForeignKey(User, on_delete=saffier.CASCADE, null=False, related_name="profiles")
profile_type = saffier.CharField(max_length=255, null=False)
class Meta:
registry = models
We now have another two foreign keys:
member
- ForeignKey from User to Member.user
- ForeignKey from Profile to User.
And the corresponding related names:
users
- The related name for the user foreign key.profiles
- The related name for the profile foreign key.
Let us also add some data into the database.
# This assumes you have the models imported
# or accessible from somewhere allowing you to generate
# these records in your database
acme = await Organisation.query.create(ident="ACME Ltd")
red_team = await Team.query.create(org=acme, name="Red Team")
blue_team = await Team.query.create(org=acme, name="Blue Team")
green_team = await Team.query.create(org=acme, name="Green Team")
await Member.query.create(team=red_team, email="charlie@redteam.com")
await Member.query.create(team=red_team, email="brown@redteam.com")
await Member.query.create(team=blue_team, email="snoopy@blueteam.com")
monica = await Member.query.create(team=green_team, email="monica@blueteam.com")
# New data
user = await User.query.create(member=monica, name="Saffier")
profile = await Profile.query.create(user=user, profile_type="admin")
This should "deep enough" to understand and now we want to query as deep as we need to.
- We want to know what team monica belongs to and we want to make sure the user name is also checked
team = await acme.teams_set.filter(
members__email=monica.email, members__users__name=user.name
).get()
# <Team: Team(id=4)>
This is great, as expected, monica
belongs to the green_team
which is the id=4
.
- We want to know what team monica belongs by checking the email, user name and the profile type
team = await acme.teams_set.filter(
members__email=monica.email,
members__users__name=user.name,
members__users__profiles__profile_type=profile.profile_type,
)
# <Team: Team(id=4)>
Perfect! We have our results as expected.
This of course in production wouldn't make too much sense to have the models designed in this way but this shows how deep you can go with the related names reverse queries.