📝 Add docs
This commit is contained in:
182
docs/tutorial/many-to-many/create-data.md
Normal file
182
docs/tutorial/many-to-many/create-data.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Create Data with Many-to-Many Relationships
|
||||
|
||||
Let's continue from where we left and create some data.
|
||||
|
||||
We'll create data for this same **many-to-many** relationship with a link table:
|
||||
|
||||
<img alt="many-to-many table relationships" src="/img/tutorial/many-to-many/many-to-many.svg">
|
||||
|
||||
We'll continue from where we left off with the previous code.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Create Heroes
|
||||
|
||||
As we have done before, we'll create a function `create_heroes()` and we'll create some teams and heroes in it:
|
||||
|
||||
```Python hl_lines="11"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:42-60]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
This is very similar to what we have done before.
|
||||
|
||||
We create a couple of teams, and then three heroes.
|
||||
|
||||
The only new detail is that instead of using an argument `team` we now use `teams`, because that is the name of the new **relationship attribute**. And more importantly, we pass a **list of teams** (even if it contains a single team).
|
||||
|
||||
See how **Deadpond** now belongs to the two teams?
|
||||
|
||||
## Commit, Refresh, and Print
|
||||
|
||||
Now let's do as we have done before, `commit` the **session**, `refresh` the data, and print it:
|
||||
|
||||
```Python hl_lines="22-25 27-29 31-36"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:42-75]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Add to Main
|
||||
|
||||
As before, add the `create_heroes()` function to the `main()` function to make sure it is called when running this program from the command line:
|
||||
|
||||
```Python hl_lines="22-25 27-29 31-36"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:78-80]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Run the Program
|
||||
|
||||
If we run the program from the command line, it would output:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
// Insert the hero data first
|
||||
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [generated in 0.00041s] ('Deadpond', 'Dive Wilson', None)
|
||||
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [cached since 0.001942s ago] ('Rusty-Man', 'Tommy Sharp', 48)
|
||||
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [cached since 0.002541s ago] ('Spider-Boy', 'Pedro Parqueador', None)
|
||||
// Insert the team data second
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00037s] ('Z-Force', 'Sister Margaret’s Bar')
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [cached since 0.001239s ago] ('Preventers', 'Sharp Tower')
|
||||
// Insert the link data last, to be able to re-use the created IDs
|
||||
INFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00026s] ((2, 3), (1, 1), (2, 1), (2, 2))
|
||||
// Commit and save the data in the database
|
||||
INFO Engine COMMIT
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
// Refresh the data
|
||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [generated in 0.00019s] (1,)
|
||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.001959s ago] (2,)
|
||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.003215s ago] (3,)
|
||||
|
||||
// Print Deadpond
|
||||
Deadpond: name='Deadpond' age=None id=1 secret_name='Dive Wilson'
|
||||
|
||||
// Accessing the .team attribute triggers a refresh
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team, heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
|
||||
INFO Engine [generated in 0.00025s] (1,)
|
||||
|
||||
// Print Deadpond's teams, 2 teams! 🎉
|
||||
Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar'), Team(id=2, name='Preventers', headquarters='Sharp Tower')]
|
||||
|
||||
// Print Rusty-Man
|
||||
Rusty-Man: name='Rusty-Man' age=48 id=2 secret_name='Tommy Sharp'
|
||||
|
||||
// Accessing the .team attribute triggers a refresh
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team, heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.001716s ago] (2,)
|
||||
|
||||
// Print Rusty-Man teams, just one, but still a list
|
||||
Rusty-Man Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')]
|
||||
|
||||
// Print Spider-Boy
|
||||
Spider-Boy: name='Spider-Boy' age=None id=3 secret_name='Pedro Parqueador'
|
||||
|
||||
// Accessing the .team attribute triggers a refresh
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team, heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.002739s ago] (3,)
|
||||
|
||||
// Print Spider-Boy's teams, just one, but still a list
|
||||
Spider-Boy Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')]
|
||||
|
||||
// Automatic roll back any previous automatic transaction, at the end of the with block
|
||||
INFO Engine ROLLBACK
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Recap
|
||||
|
||||
After setting up the model link, using it with **relationship attributes** is fairly straighforward, just Python objects. ✨
|
||||
200
docs/tutorial/many-to-many/create-models-with-link.md
Normal file
200
docs/tutorial/many-to-many/create-models-with-link.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Create Models with a Many-to-Many Link
|
||||
|
||||
We'll now support **many-to-many** relationships using a **link table** like this:
|
||||
|
||||
<img alt="many-to-many table relationships" src="/img/tutorial/many-to-many/many-to-many.svg">
|
||||
|
||||
Let's start by defining the class models, including the **link table** model.
|
||||
|
||||
## Link Table Model
|
||||
|
||||
As we want to support a **many-to-many** relationship, now we need a **link table** to connect them.
|
||||
|
||||
We can create it just as any other **SQLModel**:
|
||||
|
||||
```Python hl_lines="6-12"
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:1-12]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
This is a **SQLModel** class model table like any other.
|
||||
|
||||
It has two fields, `team_id` and `hero_id`.
|
||||
|
||||
They are both **foreign keys** to their respective tables. We'll create those models in a second, but you already know how that works.
|
||||
|
||||
And **both fields are primary keys**. We hadn't used this before. 🤓
|
||||
|
||||
## Team Model
|
||||
|
||||
Let's see the `Team` model, it's almost identical as before, but with a little change:
|
||||
|
||||
```Python hl_lines="8"
|
||||
# Code above ommited 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:15-20]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
The **relationship attribute `heroes`** is still a list of heroes, annotatted as `List["Hero"]`. Again, we use `"Hero"` in quotes because we haven't declared that class yet by this point in the code (but as you know, editors and **SQLModel** understand that).
|
||||
|
||||
We use the same **`Relationship()`** function.
|
||||
|
||||
We use **`back_populates="teams"`**. Before we referenced an attribute `team`, but as now we can have many, we'll rename it to `teams` when creating the `Hero` model.
|
||||
|
||||
And here's the important part to allow the **many-to-many** relationship, we use **`link_model=HeroTeamLink`**. That's it. ✨
|
||||
|
||||
## Hero Model
|
||||
|
||||
Let's see the other side, here's the `Hero` model:
|
||||
|
||||
```Python hl_lines="9"
|
||||
# Code above ommited 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:23-29]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
We **removed** the previous `team_id` field (column) because now the relationship is done via the link table. 🔥
|
||||
|
||||
The relationship attribute is now named **`teams`** instead of `team`, as now we support multiple teams.
|
||||
|
||||
It is no longer an `Optional[Team]` but a list of teams, annotated as **`List[Team]`**.
|
||||
|
||||
We are using the **`Relationship()`** here too.
|
||||
|
||||
We still have **`back_populates="heroes"`** as before.
|
||||
|
||||
And now we have a **`link_model=HeroTeamLink`**. ✨
|
||||
|
||||
## Create the Tables
|
||||
|
||||
The same as before, we will have the rest of the code to create the **engine**, and a function to create all the tables `create_db_and_tables()`.
|
||||
|
||||
```Python hl_lines="9"
|
||||
# Code above ommited 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:32-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
And as in previous examples, we will add that function to a function `main()`, and we will call that `main()` function in the main block:
|
||||
|
||||
```Python hl_lines="4"
|
||||
# Code above ommited 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:78-79]!}
|
||||
# We will do more stuff here later 👈
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:83-84]!}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Run the Code
|
||||
|
||||
If you run the code in the command line, it would output:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Boilerplate ommited 😉
|
||||
|
||||
INFO Engine
|
||||
CREATE TABLE team (
|
||||
id INTEGER,
|
||||
name VARCHAR NOT NULL,
|
||||
headquarters VARCHAR NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
|
||||
|
||||
INFO Engine [no key 0.00033s] ()
|
||||
INFO Engine
|
||||
CREATE TABLE hero (
|
||||
id INTEGER,
|
||||
name VARCHAR NOT NULL,
|
||||
secret_name VARCHAR NOT NULL,
|
||||
age INTEGER,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
|
||||
|
||||
INFO Engine [no key 0.00016s] ()
|
||||
INFO Engine
|
||||
|
||||
// Our shinny new link table ✨
|
||||
CREATE TABLE heroteamlink (
|
||||
team_id INTEGER,
|
||||
hero_id INTEGER,
|
||||
PRIMARY KEY (team_id, hero_id),
|
||||
FOREIGN KEY(team_id) REFERENCES team (id),
|
||||
FOREIGN KEY(hero_id) REFERENCES hero (id)
|
||||
)
|
||||
|
||||
|
||||
INFO Engine [no key 0.00031s] ()
|
||||
INFO Engine COMMIT
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Recap
|
||||
|
||||
We can support **many-to-many** relationships between tables by declaring a link table.
|
||||
|
||||
We can create it the same way as with other **SQLModel** classes, and then use it in the `link_model` parameter to `Relationship()`.
|
||||
|
||||
Now let's work with data using these models in the next chapters. 🤓
|
||||
176
docs/tutorial/many-to-many/index.md
Normal file
176
docs/tutorial/many-to-many/index.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Many to Many - Intro
|
||||
|
||||
We saw how to work with <abbr title="Also called Many-to-One">One-to-Many</abbr> relationships in the data.
|
||||
|
||||
But how do you handle **Many-to-Many** relationships?
|
||||
|
||||
Let's explore them. 🚀
|
||||
|
||||
## Starting from One-to-Many
|
||||
|
||||
Let's start with the familiar and simpler option of **One-to-Many**.
|
||||
|
||||
We have one table with teams and one with heroes, and for each **one** team, we can have **many** heroes.
|
||||
|
||||
As each team could have multiple heroes, we wouldn't be able to put the Hero IDs in columns for all of them in the `team` table.
|
||||
|
||||
But as each hero can belong **only to one** team, we have a **single column** in the heroes table to point to the specific team (to a specific row in the `team` table).
|
||||
|
||||
The `team` table looks like this:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>id</th><th>name</th><th>headquarters</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! tip
|
||||
Notice that it doesn't have any foreign key to other tables.
|
||||
|
||||
And the `hero` table looks like this:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team_id</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
We have a column in the `hero` table for the `team_id` that points to the ID of a specific team in the `team` table.
|
||||
|
||||
This is how we connect each `hero` with a `team`:
|
||||
|
||||
<img alt="table relationships" src="/img/databases/relationships.svg">
|
||||
|
||||
Notice that each hero can only have **one** connection. But each team can receive **many** connections. In particular, the team **Preventers** has two heroes.
|
||||
|
||||
## Introduce Many-to-Many
|
||||
|
||||
But let's say that as **Deadpond** is a great chracter, they recruit him to the new **Preventers** team, but he's still part of the **Z-Force** team too.
|
||||
|
||||
So, now, we need to be able to have a hero that is connected to **many** teams. And then, each team, should still be able to receive **many** heroes. So we need a **Many-to-Many** relationship.
|
||||
|
||||
A naive approach that wouldn't work very well is to add more columns to the `hero` table. Imagine we add two extra columns. Now we could connect a single `hero` to 3 teams in total, but not more. So we haven't really solved the problem of supporting **many** teams, only a very limited fixed number of teams.
|
||||
|
||||
We can do better! 🤓
|
||||
|
||||
## Link Table
|
||||
|
||||
We can create another table that would represent the link between the `hero` and `team` tables.
|
||||
|
||||
All this table contains is two columns, `hero_id` and `team_id`.
|
||||
|
||||
Both columns are **foreign keys** pointing to the ID of a specific row in the `hero` and `team` tables.
|
||||
|
||||
As this will represent the **hero-team-link**, let's call the table `heroteamlink`.
|
||||
|
||||
It would look like this:
|
||||
|
||||
<img alt="many-to-many table relationships" src="/img/tutorial/many-to-many/many-to-many.svg">
|
||||
|
||||
Notice that now the table `hero` **doesn't have a `team_id`** column anymore, it is replaced by this link table.
|
||||
|
||||
And the `team` table, just as before, doesn't have any foreign key either.
|
||||
|
||||
Specifically, the new link table `heroteamlink` would be:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>hero_id</th><th>team_id</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td><td>1</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! info
|
||||
Other names used for this **link table** are:
|
||||
|
||||
* association table
|
||||
* secondary table
|
||||
* junction table
|
||||
* intermediate table
|
||||
* join table
|
||||
* through table
|
||||
* relationship table
|
||||
* connection table
|
||||
|
||||
I'm using the term "link table" because it's short, doesn't collide with other terms already used (e.g. "relationship"), it's easy to remember how to write it, etc.
|
||||
|
||||
## Link Primary Key
|
||||
|
||||
Cool, we have a link table with **just two columns**. But remember that SQL databases [require each row to have a **primary key**](../../databases.md#identifications-primary-key){.internal-link target=_blank} that **uniquely identifies** the row in that table?
|
||||
|
||||
Now, what is the **primary key** in this table?
|
||||
|
||||
How to we identify each unique row?
|
||||
|
||||
Should we add another column just to be the **primary key** of this link table? Nope! We don't have to do that. 👌
|
||||
|
||||
**Both columns are the primary key** of each row in this table (and each row just has those two columns). ✨
|
||||
|
||||
A primary key is a way to **uniquely identify** a particular row in a **single table**. But it doesn't have to be a single column.
|
||||
|
||||
A primary key can be a group of the columns in a table, which combined are unique in this table.
|
||||
|
||||
Check the table above again, see that **each row has a unique combination** of `hero_id` and `team_id`?
|
||||
|
||||
We cannot have duplicated primary keys, which means that we cannot have duplicated links between `hero` and `team`, exactly what we want!
|
||||
|
||||
For example, the database will now prevent an error like this, with a duplicated row:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>hero_id</th><th>team_id</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td><td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3 🚨</td><td>1 🚨</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
It wouldn't make sense to have a hero be part of the **same team twice**, right?
|
||||
|
||||
Now, just by using the two columns as the primary keys of this table, SQL will take care of **preventing us from duplicating** a link between `hero` and `team`. ✅
|
||||
|
||||
## Recap
|
||||
|
||||
An intro with a recap! That's weird... but anyway. 🤷
|
||||
|
||||
Now you have the theory about the **many-to-many** relationships, and how to solve them with tables in SQL. 🤓
|
||||
|
||||
Now let's check how to write the SQL and the code to work with them. 🚀
|
||||
400
docs/tutorial/many-to-many/link-with-extra-fields.md
Normal file
400
docs/tutorial/many-to-many/link-with-extra-fields.md
Normal file
@@ -0,0 +1,400 @@
|
||||
# Link Model with Extra Fields
|
||||
|
||||
In the previous example we never interacted directly with the `HeroTeamLink` model, it was all through the automatic **many-to-many** relationship.
|
||||
|
||||
But what if we needed to have additional data to describe the link between the two models?
|
||||
|
||||
Let's say that we want to have an extra field/column to say if a hero **is still training** in that team or if they are already going on missions and stuff.
|
||||
|
||||
Let's see how to achieve that.
|
||||
|
||||
## Link Model with Two One-to-Many
|
||||
|
||||
The way to handle this is to explicitly use the link model, to be able to get and modify its data (apart from the foreign keys pointing to the two models for `Hero` and `Team`).
|
||||
|
||||
In the end, the way it works is just like two **one-to-many** relationships combined.
|
||||
|
||||
A row in the table `heroteamlink` points to **one** particular hero, but a single hero can be connected to **many** hero-team links, so it's **one-to-many**.
|
||||
|
||||
And also, the same row in the table `heroteamlink` points to **one** team, but a single team can be connected to **many** hero-team links, so it's also **one-to-many**.
|
||||
|
||||
!!! tip
|
||||
The previous many-to-many relationship was also just two one-to-many relationships combined, but now it's going to be much more explicit.
|
||||
|
||||
## Update Link Model
|
||||
|
||||
Let's update the `HeroTeamLink` model.
|
||||
|
||||
We will add a new field `is_training`.
|
||||
|
||||
And we will also add two **relationship attributes**, for the linked `team` and `hero`:
|
||||
|
||||
```Python hl_lines="10 12-13"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:6-16]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
The new **relationship attributes** have their own `back_populates` pointing to new relationship attributes we will create in the `Hero` and `Team` models:
|
||||
|
||||
* `team`: has `back_populates="hero_links"`, because in the `Team` model, the attribute will contain the links to the **team's heroes**.
|
||||
* `hero`: has `back_populates="team_links"`, because in the `Hero` model, the attribute will contain the links to the **hero's teams**.
|
||||
|
||||
!!! info
|
||||
In SQLAlchemy this is called an Association Object or Association Model.
|
||||
|
||||
I'm calling it **Link Model** just because that's easier to write avoiding typos. But you are also free to call it however you want. 😉
|
||||
|
||||
## Update Team Model
|
||||
|
||||
Now let's update the `Team` model.
|
||||
|
||||
We no longer have the `heroes` relationship attribute, and instead we have the new `hero_links` attribute:
|
||||
|
||||
```Python hl_lines="8"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:19-24]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Update Hero Model
|
||||
|
||||
The same with the `Hero` model.
|
||||
|
||||
We change the `teams` relationship attribute for `team_links`:
|
||||
|
||||
```Python hl_lines="9"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:27-33]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Create Relationships
|
||||
|
||||
Now the process to create relationships is very similar.
|
||||
|
||||
But now we create the **explicit link models** manually, pointing to their hero and team instances, and specifying the additional link data (`is_training`):
|
||||
|
||||
```Python hl_lines="21-30 32-35"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:46-85]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
We are just adding the link model instances to the session, because the link model instances are connected to the heroes and teams, they will be also automatically included in the session when we commit.
|
||||
|
||||
## Run the Program
|
||||
|
||||
Now, if we run the program, it will show almost the same output as before, because it is generating almost the same SQL, but this time including the new `is_training` column:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
|
||||
// Insert the heroes
|
||||
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [generated in 0.00025s] ('Deadpond', 'Dive Wilson', None)
|
||||
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [cached since 0.00136s ago] ('Spider-Boy', 'Pedro Parqueador', None)
|
||||
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [cached since 0.001858s ago] ('Rusty-Man', 'Tommy Sharp', 48)
|
||||
|
||||
// Insert the teams
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00019s] ('Z-Force', 'Sister Margaret’s Bar')
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [cached since 0.0007985s ago] ('Preventers', 'Sharp Tower')
|
||||
|
||||
// Insert the hero-team links
|
||||
INFO Engine INSERT INTO heroteamlink (team_id, hero_id, is_training) VALUES (?, ?, ?)
|
||||
INFO Engine [generated in 0.00023s] ((1, 1, 0), (2, 1, 1), (2, 2, 1), (2, 3, 0))
|
||||
// Save the changes in the transaction in the database
|
||||
INFO Engine COMMIT
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
|
||||
// Automatically fetch the data on attribute access
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [generated in 0.00028s] (1,)
|
||||
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
|
||||
FROM heroteamlink
|
||||
WHERE ? = heroteamlink.team_id
|
||||
INFO Engine [generated in 0.00026s] (1,)
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [generated in 0.00024s] (1,)
|
||||
|
||||
// Print Z-Force hero data, including link data
|
||||
Z-Force hero: name='Deadpond' age=None id=1 secret_name='Dive Wilson' is training: False
|
||||
|
||||
// Automatically fetch the data on attribute access
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [cached since 0.008822s ago] (2,)
|
||||
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
|
||||
FROM heroteamlink
|
||||
WHERE ? = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.005778s ago] (2,)
|
||||
|
||||
// Print Preventers hero data, including link data
|
||||
Preventers hero: name='Deadpond' age=None id=1 secret_name='Dive Wilson' is training: True
|
||||
|
||||
// Automatically fetch the data on attribute access
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.004196s ago] (2,)
|
||||
|
||||
// Print Preventers hero data, including link data
|
||||
Preventers hero: name='Spider-Boy' age=None id=2 secret_name='Pedro Parqueador' is training: True
|
||||
|
||||
// Automatically fetch the data on attribute access
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.006005s ago] (3,)
|
||||
|
||||
// Print Preventers hero data, including link data
|
||||
Preventers hero: name='Rusty-Man' age=48 id=3 secret_name='Tommy Sharp' is training: False
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Add Relationships
|
||||
|
||||
Now, to add a new relationship, we have to create a new `HeroTeamLink` instance pointing to the hero and the team, add it to the session, and commit it.
|
||||
|
||||
Here we do that in the `update_heroes()` function:
|
||||
|
||||
```Python hl_lines="10-15"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:88-103]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Run the Program with the New Relationship
|
||||
|
||||
If we run that program, we will see the output:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
|
||||
// Select the hero
|
||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||
FROM hero
|
||||
WHERE hero.name = ?
|
||||
INFO Engine [no key 0.00014s] ('Spider-Boy',)
|
||||
|
||||
// Select the team
|
||||
INFO Engine SELECT team.id, team.name, team.headquarters
|
||||
FROM team
|
||||
WHERE team.name = ?
|
||||
INFO Engine [no key 0.00012s] ('Z-Force',)
|
||||
|
||||
// Create the link
|
||||
INFO Engine INSERT INTO heroteamlink (team_id, hero_id, is_training) VALUES (?, ?, ?)
|
||||
INFO Engine [generated in 0.00023s] (1, 2, 1)
|
||||
|
||||
// Automatically refresh the data on attribute access
|
||||
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
|
||||
FROM heroteamlink
|
||||
WHERE ? = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.01514s ago] (1,)
|
||||
INFO Engine COMMIT
|
||||
INFO Engine BEGIN (implicit)
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.08953s ago] (2,)
|
||||
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
|
||||
FROM heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id
|
||||
INFO Engine [generated in 0.00018s] (2,)
|
||||
|
||||
// Print updated hero links
|
||||
Updated Spider-Boy's Teams: [
|
||||
HeroTeamLink(team_id=2, is_training=True, hero_id=2),
|
||||
HeroTeamLink(team_id=1, is_training=True, hero_id=2)
|
||||
]
|
||||
|
||||
// Automatically refresh team data on attribute access
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [cached since 0.1084s ago] (1,)
|
||||
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
|
||||
FROM heroteamlink
|
||||
WHERE ? = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.1054s ago] (1,)
|
||||
|
||||
// Print team hero links
|
||||
Z-Force heroes: [
|
||||
HeroTeamLink(team_id=1, is_training=False, hero_id=1),
|
||||
HeroTeamLink(team_id=1, is_training=True, hero_id=2)
|
||||
]
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Update Relationships with Links
|
||||
|
||||
Now let's say that **Spider-Boy** has been training enough in the **Preventers**, and they say he can join the team full time.
|
||||
|
||||
So now we want to update the status of `is_training` to `False`.
|
||||
|
||||
We can do that by iterating on the links:
|
||||
|
||||
```Python hl_lines="8-10"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:88-89]!}
|
||||
|
||||
# Code here omitted 👈
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py[ln:105-113]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial003.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Run the Program with the Updated Relationships
|
||||
|
||||
And if we run the program now, it will output:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Automatically fetch team data on attribute access
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [generated in 0.00015s] (2,)
|
||||
|
||||
// Update link row
|
||||
INFO Engine UPDATE heroteamlink SET is_training=? WHERE heroteamlink.team_id = ? AND heroteamlink.hero_id = ?
|
||||
INFO Engine [generated in 0.00020s] (0, 2, 2)
|
||||
|
||||
// Save current transaction to database
|
||||
INFO Engine COMMIT
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
|
||||
// Automatically fetch data on attribute access
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.2004s ago] (2,)
|
||||
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
|
||||
FROM heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id
|
||||
INFO Engine [cached since 0.1005s ago] (2,)
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [cached since 0.09707s ago] (2,)
|
||||
|
||||
// Print Spider-Boy team, including link data, if is training
|
||||
Spider-Boy team: headquarters='Sharp Tower' id=2 name='Preventers' is training: False
|
||||
|
||||
// Automatically fetch data on attribute access
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [cached since 0.2097s ago] (1,)
|
||||
|
||||
// Print Spider-Boy team, including link data, if is training
|
||||
Spider-Boy team: headquarters='Sister Margaret’s Bar' id=1 name='Z-Force' is training: True
|
||||
INFO Engine ROLLBACK
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Recap
|
||||
|
||||
If you need to store more information about a **many-to-many** relationship you can use an explicit link model with extra data in it. 🤓
|
||||
251
docs/tutorial/many-to-many/update-remove-relationships.md
Normal file
251
docs/tutorial/many-to-many/update-remove-relationships.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Update and Remove Many-to-Many Relationships
|
||||
|
||||
Now we'll see how to update and remove these **many-to-many** relationships.
|
||||
|
||||
We'll continue from where we left off with the previous code.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Get Data to Update
|
||||
|
||||
Let's now create a function `update_heroes()`.
|
||||
|
||||
We'll get **Spider-Boy** and the **Z-Force** team.
|
||||
|
||||
As you already know how these goes, I'll use the **short version** and get the data in a single Python statement.
|
||||
|
||||
And because we are now using `select()`, we also have to import it.
|
||||
|
||||
```Python hl_lines="3 7-12"
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:1-3]!}
|
||||
|
||||
# Some code here omitted 👈
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-83]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
And of course, we have to add `update_heroes()` to our `main()` function:
|
||||
|
||||
```Python hl_lines="6"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:100-107]!}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Add Many-to-Many Relationships
|
||||
|
||||
Now let's imagine that **Spider-Boy** thinks that the **Z-Force** team is super cool and decides to go there and join them.
|
||||
|
||||
We can use the same **relationship attributes** to include `hero_spider_boy` in the `team_z_force.heroes`.
|
||||
|
||||
```Python hl_lines="10-12 14-15"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-90]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
!!! tip
|
||||
Because we are accessing an attribute in the models right after we commit, with `hero_spider_boy.teams` and `team_z_force.heroes`, the data is refreshed automatically.
|
||||
|
||||
So we don't have to call `session.refresh()`.
|
||||
|
||||
We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm.
|
||||
|
||||
Notice that we only `add` **Z-Force** to the session, then we commit.
|
||||
|
||||
We never add **Spider-Boy** to the session, and we never even refresh it. But we still print his teams.
|
||||
|
||||
This still works correctly because we are using `back_populates` in the `Relationship()` in the models. That way, **SQLModel** (actually SQLAlchemy) can keep track of the changes and updates, and make sure they also happen on the relationships in the other related models. 🎉
|
||||
|
||||
## Run the Program
|
||||
|
||||
You can confirm it's all working by running the program in the command line:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Create the new many-to-many relationship
|
||||
INFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00020s] (1, 3)
|
||||
INFO Engine COMMIT
|
||||
|
||||
// Start a new automatic transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
|
||||
// Automatically refresh the data while accessing the attribute .teams
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [generated in 0.00044s] (3,)
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team, heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.1648s ago] (3,)
|
||||
|
||||
// Print Spider-Boy teams, including Z-Force 🎉
|
||||
Updated Spider-Boy's Teams: [
|
||||
Team(id=2, name='Preventers', headquarters='Sharp Tower'),
|
||||
Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar')
|
||||
]
|
||||
|
||||
// Automatically refresh the data while accessing the attribute .heores
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero, heroteamlink
|
||||
WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
|
||||
INFO Engine [cached since 0.1499s ago] (1,)
|
||||
|
||||
// Print Z-Force heroes, including Spider-Boy 🎉
|
||||
Z-Force heroes: [
|
||||
Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson'),
|
||||
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', teams=[
|
||||
Team(id=2, name='Preventers', headquarters='Sharp Tower'),
|
||||
Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar', heroes=[...])
|
||||
])
|
||||
]
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Remove Many-to-Many Relationships
|
||||
|
||||
Now let's say that right after joining the team, **Spider-Boy** realized that their "life preserving policies" are much more relaxed than what he's used to. 💀
|
||||
|
||||
And their *occupational safety and health* is also not as great... 💥
|
||||
|
||||
So, **Spider-Boy** decides to leave **Z-Force**.
|
||||
|
||||
Let's update the relationships to remove `team_z_force` from `hero_spider_boy.teams`.
|
||||
|
||||
Because `hero_spider_boy.teams` is just a list (a special list managed by SQLAlchemy, but a list), we can use the standard list methods.
|
||||
|
||||
In this case, we use the method `.remove()`, that takes an item and removes it from the list.
|
||||
|
||||
```Python hl_lines="17-19 21-22"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-97]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
And this time, just to show again that by using `back_populates` **SQLModel** (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the `hero_spider_boy` object (modifying `hero_spider_boy.teams`), we are adding `team_z_force` to the **session**. And we commit that, without even add `hero_spider_boy`.
|
||||
|
||||
This still works because by updating the teams in `hero_spider_boy`, because they are synchronized with `back_populates`, the changes are also reflected in `team_z_force`, so it also has changes to be saved in the DB (that **Spider-Boy** was removed).
|
||||
|
||||
And then we add the team, and commit the changes, which updates the `team_z_force` object, and because it changed the table that also had a connection with the `hero_spider_boy`, it is also marked internally as updated, so it all works.
|
||||
|
||||
And then we just print them again to confirm that everything worked correctly.
|
||||
|
||||
## Run the Program Again
|
||||
|
||||
To confirm that this last part worked, you can run the program again, it will output something like:
|
||||
|
||||
<div style="font-size: 1rem;" class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Delete the row in the link table
|
||||
INFO Engine DELETE FROM heroteamlink WHERE heroteamlink.team_id = ? AND heroteamlink.hero_id = ?
|
||||
INFO Engine [generated in 0.00043s] (1, 3)
|
||||
// Save the changes
|
||||
INFO Engine COMMIT
|
||||
|
||||
// Automatically start a new transaction
|
||||
INFO Engine BEGIN (implicit)
|
||||
|
||||
// Automatically refresh the data while accessing the attribute .heroes
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team
|
||||
WHERE team.id = ?
|
||||
INFO Engine [generated in 0.00029s] (1,)
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero, heroteamlink
|
||||
WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
|
||||
INFO Engine [cached since 0.5625s ago] (1,)
|
||||
|
||||
// Print the Z-Force heroes after reverting the changes
|
||||
Reverted Z-Force's heroes: [
|
||||
Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson')
|
||||
]
|
||||
|
||||
// Automatically refresh the data while accessing the attribute .teams
|
||||
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
|
||||
FROM hero
|
||||
WHERE hero.id = ?
|
||||
INFO Engine [cached since 0.4209s ago] (3,)
|
||||
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
|
||||
FROM team, heroteamlink
|
||||
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
|
||||
INFO Engine [cached since 0.5842s ago] (3,)
|
||||
|
||||
// Print Spider-Boy's teams after reverting the changes
|
||||
Reverted Spider-Boy's teams: [
|
||||
Team(id=2, name='Preventers', headquarters='Sharp Tower')
|
||||
]
|
||||
|
||||
// Automatically roll back any possible previously unsaved transaction
|
||||
INFO Engine ROLLBACK
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Recap
|
||||
|
||||
Updating and removing many-to-many relationships is quite straightforward after setting up the **link model** and the relationship attributes.
|
||||
|
||||
You can just use common list operation. 🚀
|
||||
Reference in New Issue
Block a user