Skip to content

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.

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

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
Saffier will use the lowercased model name of the related model to create the reverse relation.

Imagine you have a model Team that has a ForeignKey to another model Organisation.

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

Let us create three models:

  • Organisation
  • Team
  • Member
models.py
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.

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

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