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.
Related name¶
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
Example without related name¶
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
.
Example with related name¶
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)