Related Name¶
Edgy offers a high degree of flexibility in how you define your models and execute queries.
A common practice involves declaring ForeignKeys to establish relationships between tables.
A frequent scenario is the need to perform a "reverse query."
What is a Related Name?¶
A related name is an attribute declared within ForeignKeys that specifies the name of the reverse relation from the related model back to the model defining the relation.
It designates the attribute name used to access the related model from the opposite side of the relation.
Confused? Let's clarify with an example.
How it Works¶
related_name
Attribute¶
There are two approaches to using related_name
.
Explicit Parameter¶
The related name can be directly declared within the ForeignKeys related_name
attribute, explicitly specifying the desired name.
Automatic Generation¶
Alternatively, if a related_name
is not specified in the ForeignKeys, Edgy will automatically generate the name using the following format:
<table-name>s_set
for non-unique relations, and
<table-name>
for unique relations.
Edgy will use the lowercase model name of the related model to create the reverse relation.
For instance, consider a Team
model with a ForeignKey to an Organisation
model.
import edgy
from edgy import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class Organisation(edgy.Model):
ident: str = edgy.CharField(max_length=100)
class Meta:
registry = models
class Team(edgy.Model):
org: Organisation = edgy.ForeignKey(Organisation, on_delete=edgy.RESTRICT)
name: str = edgy.CharField(max_length=100)
class Meta:
registry = models
Since no related_name
was provided, Edgy will automatically assign the name organisations_set
.
Deep Dive into related_name
¶
Let's create three models:
- Organisation
- Team
- Member
import edgy
from edgy import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class Organisation(edgy.Model):
ident: str = edgy.CharField(max_length=100)
class Meta:
registry = models
class Team(edgy.Model):
org: Organisation = edgy.ForeignKey(Organisation, on_delete=edgy.RESTRICT)
name: str = edgy.CharField(max_length=100)
class Meta:
registry = models
class Member(edgy.Model):
team: Team = edgy.ForeignKey(Team, on_delete=edgy.SET_NULL, null=True, related_name="members")
second_team: Team = edgy.ForeignKey(
Team, on_delete=edgy.SET_NULL, null=True, related_name="team_members"
)
email: str = edgy.CharField(max_length=100)
name: str = edgy.CharField(max_length=255, null=True)
class Meta:
registry = models
We have declared three models with three ForeignKeys:
org
: ForeignKey fromTeam
toOrganisation
.team
: ForeignKey fromMember
toTeam
.second_team
: Another ForeignKey fromMember
toTeam
.
Now, let's populate the models with data.
# 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 can now begin querying using related_name
.
Querying with related_name
¶
- Retrieve all teams belonging to the
acme
organization.
teams = await acme.teams_set.all()
# [<Team: Team(id=1)>, <Team: Team(id=2)>, <Team: Team(id=3)>]
Warning
Because the org
foreign key in the Team
model lacked a related_name
, Edgy automatically generated teams_set
, accessible from Organisation
. Refer to default behavior for more information.
- Find the team to which the members of the
blue_team
belong.
teams = await acme.teams_set.filter(members=blue_team).get()
# <Team: Team(id=2)>
Nested Traversal Queries
Notice the use of members
? It's another reverse query linking the Member
model to Team
.
This illustrates how to perform nested and traversal queries across your models.
Let's explore further examples.
- Determine the team to which
charlie
belongs.
team = await acme.teams_set.filter(members__email=charlie.email).get()
# <Team: Team(id=1)>
Again, we use the members
related name, declared in the Member
model as a ForeignKey to Team
, and filter by email
.
Nested Queries¶
This is where it gets interesting. What if you need to perform deeper nested queries?
Let's add two more models:
- User
- Profile
Warning
These are used for illustrative purposes and may not represent optimal model design.
Our models now look like this:
import edgy
from edgy import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class Organisation(edgy.Model):
ident: str = edgy.CharField(max_length=100)
class Meta:
registry = models
class Team(edgy.Model):
org: Organisation = edgy.ForeignKey(Organisation, on_delete=edgy.RESTRICT)
name: str = edgy.CharField(max_length=100)
class Meta:
registry = models
class Member(edgy.Model):
team: Team = edgy.ForeignKey(Team, on_delete=edgy.SET_NULL, null=True, related_name="members")
second_team: Team = edgy.ForeignKey(
Team, on_delete=edgy.SET_NULL, null=True, related_name="team_members"
)
email: str = edgy.CharField(max_length=100)
name: str = edgy.CharField(max_length=255, null=True)
class Meta:
registry = models
class User(edgy.Model):
name: str = edgy.CharField(max_length=255, null=True)
member: Member = edgy.ForeignKey(
Member, on_delete=edgy.SET_NULL, null=True, related_name="users"
)
class Meta:
registry = models
class Profile(edgy.Model):
user: User = edgy.ForeignKey(User, on_delete=edgy.CASCADE, null=False, related_name="profiles")
profile_type: str = edgy.CharField(max_length=255, null=False)
class Meta:
registry = models
We have added two more foreign keys:
member
: ForeignKey fromUser
toMember
.user
: ForeignKey fromProfile
toUser
.
And their corresponding related names:
users
: Related name for theUser
foreign key.profiles
: Related name for theProfile
foreign key.
Let's populate the database with data.
# 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="Edgy")
profile = await Profile.query.create(user=user, profile_type="admin")
This setup is sufficient to illustrate deep nested queries.
- Find the team to which
monica
belongs, and verify the user's name.
team = await acme.teams_set.filter(
members__email=monica.email, members__users__name=user.name
).get()
# <Team: Team(id=4)>
As expected, monica
belongs to green_team
, which has id=4
.
- Find the team to which
monica
belongs, verifying the email, user name, and 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 the expected results.
While this model design might not be ideal for production, it demonstrates the depth achievable with related name reverse queries.