Skip to content

ManyToMany

In a lot of projects out there you see cases where a ManyToMany would be helpful. Django for example, has that defined as internal model when a field is declared.

In theory, when designing a database, the ManyToMany does not exist and it is not possible in a relational system.

What happens internally is the creation of an intermediary table that links the many to many tables.

How does it work

As mentioned before, a many to many it is not possible in a relational database, instead, an intermediary table needs to be created and connect the tables for the said many to many.

This is exactly what edgy does with the ManyToMany automatically.

Quick note

The ManyToMany or ManyToMany accepts both Model and string as a parameter for the to.

Example

# Using the model directly
class Profile(edgy.Model):
    users: List[User] = edgy.ManyToMany(User)

# Using a string
class Profile(edgy.Model):
    users: List[User] = edgy.ManyToMany("User")

Queries

Queries from a Many2Many field use the through model as base when embed_through is empty. This also applies for the reverse related field. If embed_through is False the virtual base changes to the target model. The same applies if embed_through is not-empty. In this case it is possible to target the embedded model via the embed_through path part. It is like a virtual path part which can be traversed via the __ path building.

Operations

With the many to many you can perform all the normal operations of searching from normal queries to the related name as per normal search.

ManyToMany allows three different methods when using it (the same applies for the reverse side).

  • add() - Adds a record to the ManyToMany.
  • create() - Create a new record and add it to the ManyToMany.
  • remove() - Removes a record to the ManyToMany.

Let us see how it looks by using the following example.

from typing import List

import edgy
from edgy import Database, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class Team(edgy.Model):
    name: str = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Organisation(edgy.Model):
    ident: str = edgy.CharField(max_length=100)
    teams: List[Team] = edgy.ManyToManyField(Team, related_name="organisation_teams")

    class Meta:
        registry = models

add()

You can now add teams to organisations, something like this.

blue_team = await Team.query.create(name="Blue Team")
green_team = await Team.query.create(name="Green Team")
organisation = await Organisation.query.create(ident="Acme Ltd")

# Add teams to the organisation
await organisation.teams.add(blue_team)
await organisation.teams.add(green_team)

create()

You can fuse this to:

organisation = await Organisation.query.create(ident="Acme Ltd")

# Add teams to the organisation
await organisation.teams.create(name="Blue Team")
await organisation.teams.create(name="Green Team")

This is also more performant because less transactions are required.

remove()

You can now remove teams from organisations, something like this.

blue_team = await Team.query.create(name="Blue Team")
green_team = await Team.query.create(name="Green Team")
red_team = await Team.query.create(name="Red Team")
organisation = await Organisation.query.create(ident="Acme Ltd")

# Add teams to organisation
await organisation.teams.add(blue_team)
await organisation.teams.add(green_team)
await organisation.teams.add(red_team)

# Remove the teams from the organisation
await organisation.teams.remove(red_team)
await organisation.teams.remove(blue_team)

Hint: when unique, remove works also without argument.

The same way you define related names for foreign keys, you can do the same for the ManyToMany.

When a related_name is not defined, Edgy will automatically generate one with the following format:

<table-to-many2many>_<through-model-name>s_set
from typing import List

import edgy
from edgy import Database, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class Team(edgy.Model):
    name: str = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Organisation(edgy.Model):
    ident: str = edgy.CharField(max_length=100)
    teams: List[Team] = edgy.ManyToManyField(Team)

    class Meta:
        registry = models
# Create some fake data
blue_team = await Team.query.create(name="Blue Team")
green_team = await Team.query.create(name="Green Team")

# Add the teams to the organisation
organisation = await Organisation.query.create(ident="Acme Ltd")
await organisation.teams.add(blue_team)
await organisation.teams.add(green_team)

# Query
await blue_team.team_organisationteams_set.filter(name=blue_team.name)

As you can see, because no related_name was provided, it defaulted to team_organisationteams_set.

from typing import List

import edgy
from edgy import Database, Registry

database = Database("sqlite:///db.sqlite")
models = Registry(database=database)


class Team(edgy.Model):
    name: str = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Organisation(edgy.Model):
    ident: str = edgy.CharField(max_length=100)
    teams: List[Team] = edgy.ManyToManyField(Team, related_name="organisation_teams")

    class Meta:
        registry = models

Tip

The way you can query using the related name are described in detail in the related name section and has the same level of functionality as per normal foreign key.

You can now query normally, something like this.

# Create some fake data
blue_team = await Team.query.create(name="Blue Team")
green_team = await Team.query.create(name="Green Team")

# Add the teams to the organisation
organisation = await Organisation.query.create(ident="Acme Ltd")
organisation.teams.add(blue_team)
organisation.teams.add(green_team)

# Query
blue_team.organisation_teams.filter(name=blue_team.name)