Ga naar inhoud

veel-op-veel

ManyToMany (tot, door), met het vereiste argument tot en optioneel tot, neemt de doel- en relationele modelklassen.

Sqlalchemy-kolommen en -typen worden automatisch opgehaald uit het doelmodel.

  • Sqlalchemiekolom: Klasse voor primaire sleutelkolom van het doelmodel
  • Type (voor pydantic): het type doelmodel

Definieer het model

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from typing import List, Optional

import databases
import ormar
import sqlalchemy

DATABASE_URL = "sqlite:///test.db"

ormar_base_config = ormar.OrmarConfig(
    database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)


class Author(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="authors")

    id: int = ormar.Integer(primary_key=True)
    first_name: str = ormar.String(max_length=80)
    last_name: str = ormar.String(max_length=80)


class Category(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="categories")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=40)


class Post(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="posts")

    id: int = ormar.Integer(primary_key=True)
    title: str = ormar.String(max_length=200)
    categories: Optional[List[Category]] = ormar.ManyToMany(Category)
    author: Optional[Author] = ormar.ForeignKey(Author)

Voorbeeldgegevens maken:

1
2
3
Guido = await Author.objects.create (voornaam = "Guido", achternaam = "Van Rossum")
Post = await Post.objects.create (title = "Hallo, M2M", auteur = Guido)
Nieuws = afwachten Category.objects.create (naam = "Nieuws")

omgekeerde relatie

Het veld vreemde sleutel registreert automatisch de keerzijde van de relatie.

Standaard is het de kind (bron) modelnaam + s, zoals in de post in het volgende codefragment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Klasse Categorie (ormar. Model):
    ormar _ config = basis _ ormar _ config (tabelnaam = "categorieën")

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Naam: str = ormar. String (max _ length = 40)


Klasse Post (ormar. Model):
    ormar _ config = basis _ ormar _ config kopie ()

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Titel: str = ormar. String (max _ length = 200)
    Categorieën: Optioneel [Lijst [Categorie]] = ormar. ManyToMany (Categorie)

# enkele voorbeeldgegevens aanmaken
Post = wacht op Post.objects.create (title = "Hallo, M2M")
Nieuws = afwachten Category.objects.create (naam = "Nieuws")
In afwachting van post.category.add (nieuws)

# nu kunt u vragen en toegang van beide kanten:
post _ check = Post.objects.select _ gerelateerd ("categorieën") .get ()
Bevestig post _ check categorieën [0] = = nieuws

# query door auto geregistreerde achterkant
category _ check = Category.objects.select _ gerelateerd ("berichten") .get ()
Categorie _ controleer berichten [0] = = post

Omgekeerde relaties leggen API 's bloot om gerelateerde objecten van de ouder te beheren.

verwante naam

Standaard wordt de correlatienaam op dezelfde manier gegenereerd als de relatie met een vreemde sleutel (class.name () + 's '), maar u kunt deze naam ook overschrijven door de parameter correlatienaam als volgt op te geven:

1
2
3
Categorieën: Facultatief [Unie [Categorie, Lijst [Categorie]]] = ormar. ManyToMany (
        Categorie, through=PostCategory, gerelateerde _ naam = "nieuwe _ categorieën"
    )

!!!waarschuwt wanneer u meerdere relaties aan hetzelfde model levert, anders kan gerelateerde\ _ naam niet langer automatisch voor u worden gegenereerd. Daarom moet u in dit geval een gerelateerde\ _ naam opgeven voor alle velden of alle gerelateerde velden behalve één (één kan de standaard zijn en worden gegenereerd).

Sla de omgekeerde relatie over

Als je zeker weet dat je geen omgekeerde relatie wilt, kun je de vlag skip\ _ reverse = True van ManyToMany gebruiken.

Als u de skip\ _ reverse vlag intern instelt, is het veld nog steeds geregistreerd aan de andere kant van de relatie, dus u kunt:

  • Filteren op relevante modelvelden in het omgekeerde model
  • Bestellen\ _ door gerelateerde modelvelden in omgekeerd model

Maar je kunt niet:

  • Gebruik gerelateerde\ _ naam om toegang te krijgen tot gerelateerde velden vanuit het omgekeerde model
  • Zelfs als u de correlatie aan de achterkant van het model selecteert, wordt het geretourneerde model niet in de omgekeerde instantie ingevuld (er worden geen verbindingen geblokkeerd, dus u kunt de relatie nog steeds filteren en sorteren).
  • De relatie wordt niet ingevuld in model\ _ dump () en json () Wanneer* wordt ingevuld vanuit een woordenboek of json (ook via fastapi), kunt u geneste gerelateerde objecten niet doorgeven. Afhankelijk van de extra instellingen in de pydantische configuratie wordt deze genegeerd of wordt er een fout opgeworpen.

Voorbeeld:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Klasse Categorie (ormar. Model):
    ormar _ config = basis _ ormar _ config (tabelnaam = "categorieën")

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Naam: str = ormar. String (max _ length = 40)


Klasse Post (ormar. Model):
    ormar _ config = basis _ ormar _ config kopie ()

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Titel: str = ormar. String (max _ length = 200)
    Categorieën: Optioneel [Lijst [Categorie]] = ormar. ManyToMany (Categorie, skip _ reverse = Waar)

# enkele voorbeeldgegevens aanmaken
Post = wacht op Post.objects.create (title = "Hallo, M2M")
Nieuws = afwachten Category.objects.create (naam = "Nieuws")
In afwachting van post.category.add (nieuws)

Bevestig post.categorieën [0] = = nieuws # ok
Bevestig news.posts # Kenmerkfout!

# maar kan nog steeds gebruiken in volgorde _ by
Categorieën = (
    wacht op Categorie. Objecten. select _ gerelateerd ("berichten"). order _ by ("berichten _ _ titel"). all ()
)
Categorieën bevestigen [0]. voornaam = = "Test"

# Merk op dat berichten niet worden ingevuld voor auteurs, zelfs als ze expliciet zijn
# opgenomen in select _ gerelateerd - let op geen berichten in model _ dump ()
model _ dump (uitsluiten ={"id"}) = ={"name": "News"}

# kan nog steeds filteren door velden van gerelateerde modellen
Categorieën = wachten Categories.objects.filter (berichten _ _ titel = "Hallo, M2M") .all ()
Categorieën bevestigen [0]. name = = "Nieuws"
Stel len (categorieën) = = 1

Door het model

Of als u extra velden wilt toevoegen, kunt u de modelklasse expliciet maken en doorgeven.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import databases
import ormar
import sqlalchemy

DATABASE_URL = "sqlite:///test.db"

ormar_base_config = ormar.OrmarConfig(
    database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)


class Category(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="categories")

    id = ormar.Integer(primary_key=True)
    name = ormar.String(max_length=40)


class PostCategory(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="posts_x_categories")

    id: int = ormar.Integer(primary_key=True)
    sort_order: int = ormar.Integer(nullable=True)
    param_name: str = ormar.String(default="Name", max_length=200)


class Post(ormar.Model):
    ormar_config = ormar_base_config.copy()

    id: int = ormar.Integer(primary_key=True)
    title: str = ormar.String(max_length=200)
    categories = ormar.ManyToMany(Category, through=PostCategory)

!!!Waarschuwing Houd er rekening mee dat zelfs als u het niet via het model verstrekt, het automatisch voor u wordt gemaakt en nog steeds moet worden opgenomen in het voorbeeld van de alembische migratie.

!!!Tip Merk op dat als u de Through-modelnaam of de database- en tabelnaam van het model wilt aanpassen, u het Through-model moet opgeven.

Als u het veld "Pass" niet opgeeft, zal het systeem het voor u genereren.

De standaard naamgevingsconventie is:

  • Voor klasnamen is het de vereniging van twee klasnamen (ouder + andere), dus in het bovenstaande voorbeeld zou het PostCategory zijn
  • Voor tabelnamen is het vergelijkbaar, maar met een onderstrepingsteken en s aan het einde van de klassenaam in kleine letters, wat in het bovenstaande voorbeeld berichten\ _ categorys zou zijn

Aanpassen op relatienaam

Standaard is de bijbehorende modelnaam kleine letters door de modelrelatienaam.

Dus in dit voorbeeld:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
... # cursusaangifte weggelaten
Klassestudent (ormar. Model):
    ormar _ config = basis _ ormar _ config kopie ()

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Naam: str = ormar. String (max _ length = 100)
    Cursussen = ormar. ManyToMany (Cursus)

# zal standaard produceren Door model als volgt (voorbeeld vereenvoudigd)
Klasse StudentenCursus (ormar. Model):
    ormar _ config = basis _ ormar _ config (tabelnaam = "studenten _ cursussen")

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Student = ormar. ForeignKey (Student) # standaardnaam
    Cursus = ormar. ForeignKey (Cursus) # standaardnaam

Om de veld- / relatienamen in het Through-model aan te passen, kunt u nu de nieuwe parameters van ManyToMany gebruiken:

  • Via\ _ relatie\ _ naam - veldnaam die leidt naar het model dat ManyToMany verklaart
  • Via\ _ reverse\ _ relatie\ _ naam - naar de veldnaam van het model waar ManyToMany naar toe leidt

Voorbeeld:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
... # cursusaangifte weggelaten
basis _ ormar _ config = ormar. OrmarConfig (
    Database = databanken. Database ("sqlite: / / db.sqlite"),
    Metadata = sqlalchemie. MetaData (),
)


Klassestudent (ormar. Model):
    ormar _ config = basis _ ormar _ config kopie ()

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    Naam: str = ormar. String (max _ length = 100)
    Cursussen = ormar. ManyToMany (Cursus,
                               door _ relatie _ naam = "student _ id",
                               door _ omgekeerde _ relatie _ naam = "cursus _ id")

# zal produceren Door model als volgt (voorbeeld vereenvoudigd)
Klasse StudentenCursus (ormar. Model):
    ormar _ config = basis _ ormar _ config (tabelnaam = "cursisten _ cursussen")

    Id: int = ormar. Geheel getal (primaire _ sleutel = waar)
    student _ id = ormar. ForeignKey (Student) # ingesteld door door _ relatie _ naam
    cursus _ id = ormar. ForeignKey (cursus) # ingesteld door via _ omgekeerde _ relatie _ naam

!!!Opmerking: Merk op dat het expliciet declareren van relaties in het Through-model verboden is, dus zelfs als u uw eigen aangepaste Through-model opgeeft, kunt u de naam daar niet wijzigen en moet u dezelfde gebruiken via\ _ relatie\ _ naam en via\ _ reverse\ _ relatie\ _ naam parameters ...

Door de velden

Het passveld wordt automatisch toegevoegd aan de achterkant van de relatie.

Blootgestelde velden worden in kleine letters genoemd door de klassenaam.

Het blootgestelde veld laadt expliciet geen relatie omdat de relatie al is ingevuld in het ManyToMany-veld, dus het is alleen nuttig als er aanvullende velden worden opgegeven in het Through-model.

In de voorbeeldmodelopstelling ziet het er als volgt uit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import databases
import ormar
import sqlalchemy

DATABASE_URL = "sqlite:///test.db"

ormar_base_config = ormar.OrmarConfig(
    database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)


class Category(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="categories")

    id = ormar.Integer(primary_key=True)
    name = ormar.String(max_length=40)


class PostCategory(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="posts_x_categories")

    id: int = ormar.Integer(primary_key=True)
    sort_order: int = ormar.Integer(nullable=True)
    param_name: str = ormar.String(default="Name", max_length=200)


class Post(ormar.Model):
    ormar_config = ormar_base_config.copy()

    id: int = ormar.Integer(primary_key=True)
    title: str = ormar.String(max_length=200)
    categories = ormar.ManyToMany(Category, through=PostCategory)

Het doorlopende veld kan bij de meeste QuerySet-bewerkingen worden gebruikt als een normaal modelveld.

Houd er rekening mee dat het pasveld alleen wordt toegevoegd aan de relevante kant van de zoekopdracht, dus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Post = afwachten Post.objects.select _ gerelateerd ("categorieën") .get ()
# Het bronmodel heeft geen doorgaand veld
Beweren post. postcategorie is Geen
# gerelateerde modellen hebben door velden
Beweren post.categorieën [0] .postcategorie is geen Geen

# hetzelfde geldt voor omgekeerde query 's
Categorie = wachten Category.objects.select _ gerelateerd ("berichten") .get ()
Beweren categorie.postcategorie is Geen
Beweren categorie.posten [0] .postcategorie is niet Geen

Velden kunnen worden gebruikt om gegevens te filteren.

1
2
3
4
5
Plaats = (
        wacht op Post.objects.select _ gerelateerd ("categorieën")
        . filter (postcategorie _ _ sorteren _ bestellen _ _ gt = 1)
        . krijgen ()
        )

!!!tip Merk op dat hoewel de eigenlijke instantie niet in het bronmodel is ingevuld, u deze van beide kanten via het model kunt openen in queries, volgorde op instructies, enz. De volgende query heeft dus exact hetzelfde effect (toegang tot notities per categorie)

1
2
3
4
5
6
7
```python
post = (
    await Post.objects.select_related("categories")
    .filter(categories__postcategory__sort_order__gt=1)
    .get()
    )
```

Query 's kunnen achtereenvolgens via het model worden gebruikt.

1
2
3
4
5
Plaats = (
        wacht op Post.objects.select _ gerelateerd ("categorieën")
        . bestellen _ op ("- postcategorie _ _ sorteren _ bestellen")
        . krijgen ()
    )

U kunt ook velden en behalve\ _ velden gebruiken om een subset van kolommen te selecteren op de normale QuerySet-manier.

1
2
3
4
5
Post2 = (
        wacht op Post.objects.select _ gerelateerd ("categorieën")
        _ velden uitsluiten ("postcategory _ _ param _ name")
        . krijgen ()
        )

!!! Waarschuwing Merk op dat aangezien thru-velden alle relationele velden expliciet ongeldig maken omdat de relatie is ingevuld in het veld ManyToMany, standaardmodelmethoden zoals opslaan () en bijwerken () niet mogen worden gebruikt voordat velden uit de database opnieuw worden geladen.

Als u het pasveld ter plaatse wilt wijzigen, vergeet dan niet om het opnieuw te laden vanuit de database. Anders stelt u de relatie in op "none", waardoor het veld effectief onbruikbaar wordt!

1
2
3
4
# laad het veld altijd opnieuw voordat u het wijzigt
In afwachting van post2.categorieën [0] .postcategory.load ()
# alleen dan het veld bijwerken
Wacht op post2.categorieën [0] .postcategory.update (sort _ order = 3)

Merk op dat het herladen van het model de relatie eigenlijk herlaadt als een pk\ _ only-model (alleen de primaire sleutel is ingesteld), dus ze zijn niet volledig ingevuld, maar genoeg om de relatie te behouden wanneer ze worden bijgewerkt.

!!!Waarschuwing Als u ie fastapi gebruikt, kunnen gerelateerde modellen die via de velden sectie worden geladen, pydantische validatiefouten veroorzaken (wat de belangrijkste reden is waarom ze niet standaard worden ingevuld). U moet dus relevante velden uitsluiten van het antwoord of de relevante modellen volledig laden. In het bovenstaande voorbeeld betekent dit:python await post2.categories[0].postcategory.post.load() await post2.categories[0].postcategory.category.load() Of u kunt load\ _ all () gebruiken:python await post2.categories[0].postcategory.load_all()

De geprefereerde updatemethode is via de queryset proxy update () methode

1
2
# filter de gewenste gerelateerde modellen met doorlopende velden en update alleen via veldparameters
Wacht op post2.categories.filter (naam = 'Testcategorie') .update (postcategory ={"sort_order": 3})

Relationele methode

toevoegen

Toevoegen (item: model,**kwargs)

Hiermee kunt u modellen toevoegen aan veel-op-veel relaties.

1
2
3
4
# Voeg een categorie toe aan een bericht.
In afwachting van post.category.add (nieuws)
# of vanaf de andere kant:
In afwachting van news.posts.add (post)

!!!Waarschuwing: in alle niet-geengevallen moet de primaire sleutelwaarde van het relevante model in de database aanwezig zijn.

1
Anders wordt er een IntegrityError gegenereerd door uw database driver library.

Als u een model met een Through-model met aanvullende velden declareert, kunt u de kindmodellen invullen tijdens de toevoeging aan de relatie.

Geef hiervoor het sleutelwoordargument met de veldnaam en waarde door aan de add () -aanroep.

Houd er rekening mee dat dit alleen van toepassing is op veel-op-veel relaties.

1
2
3
4
Post = afwachten Post (title = "Testpost") .save ()
Categorie = wacht op Categorie (naam = "Testcategorie") .save ()
# afgezien van argumenten voor het doorgeven van modellen die verwijzen naar modelvelden
Wacht op post.category.add (categorie, sort _ order = 1, param _ name = 'test')

elimineren

De desbetreffende modellen worden één voor één geschrapt.

Verwijder ook de relatie in de database.

1
In afwachting van news.posts.delete (post)

duidelijk

Een enkele oproep verwijdert alle gerelateerde modellen.

Verwijder ook de relatie in de database.

Wachten op news.posts.clear ()

Query instellen Agent

De omgekeerde relatie legt de QuerysetProxy API bloot, waarmee u gerelateerde modellen kunt opvragen, net als bij een normale zoekopdracht.

Lees de onderstaande querysetproxy om te zien welke methoden van QuerySet beschikbaar zijn