Skip to content

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."

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

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.

models.py
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.

Let's create three models:

  • Organisation
  • Team
  • Member
models.py
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 from Team to Organisation.
  • team: ForeignKey from Member to Team.
  • second_team: Another ForeignKey from Member to Team.

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.

  • 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:

models.py
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 from User to Member.
  • user: ForeignKey from Profile to User.

And their corresponding related names:

  • users: Related name for the User foreign key.
  • profiles: Related name for the Profile 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.