Aller au contenu

Clé étrangère

ForeignKey(to: Model, *, name: str = None, unique: bool = False, nullable: bool = True, related_name: str = None, virtual: bool = False, onupdate: Union[ReferentialAction, str] = None, ondelete: Union[ReferentialAction, str] = None, **kwargs: Any) Possède les paramètres nécessaires pour adopter la classe de modèle cible.

Les colonnes et les types Sqlalchemy sont automatiquement extraits du modèle cible.

  • Colonne Sqlalchemy: classe de la colonne de clé primaire du modèle cible
  • Type (pour pydantic): type du modèle cible

Définir un modèle

Pour définir une relation, ajoutez un champ de clé étrangère qui pointe vers le modèle associé.

 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
from typing import Optional

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

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


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Department] = ormar.ForeignKey(Department)

Relation inverse

Le champ de clé étrangère enregistre automatiquement le revers de la relation.

Par défaut, il s'agit du nom du modèle enfant (source)+ s, comme dans la leçon dans l'extrait de code ci-dessous:

 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
37
38
39
40
41
42
43
44
45
import asyncio
from typing import Optional

import databases
import ormar
import sqlalchemy
from examples import create_drop_database

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

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


class Department(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="departments")

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


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

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Department] = ormar.ForeignKey(Department)


@create_drop_database(base_config=ormar_base_config)
async def verify():
    department = await Department(name="Science").save()
    course = Course(name="Math", completed=False, department=department)
    print(department.courses[0])
    # Will produce:
    # Course(id=None,
    #        name='Math',
    #        completed=False,
    #        department=Department(id=None, name='Science'))
    await course.save()


asyncio.run(verify())

Une relation inverse expose une API pour gérer les objets associés depuis le parent.

Sauter les relations inverses

Si vous êtes sûr de ne pas vouloir une relation inverse, vous pouvez utiliser l'attribut skip_reverse = True de la clé étrangère.

Si vous définissez l'attribut skip_reverse en interne, le champ est toujours enregistré à l'autre bout de la relation, vous pouvez donc:

  • Filtrer par champ de modèle pertinent dans le modèle inverse
  • order_by Champ de modèle pertinent dans le modèle inverse

Mais vous ne pouvez:

  • Utiliser related_name pour accéder aux champs pertinents à partir du modèle inverse
  • Même si vous sélectionnez une relation du côté opposé du modèle, le modèle retourné ne sera pas rempli dans l'instance inverse (les connexions ne seront pas bloquées, vous pouvez donc toujours filtrer et trier les relations).
  • La relation n'est pas remplie dans modèle_dump() et modèle_dump_json(). Lorsque* est rempli à partir d'un dictionnaire ou d'un fichier json (également via fastapi), vous ne pouvez pas passer les objets liés imbriqués. il sera ignoré ou une erreur sera générée en fonction des paramètres supplémentaires dans la configuration pydantique.

Exemples:

 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
Auteur de classe (modèle ormar):
    ormar_config = base_ormar_config.copy()

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


Classe Post(modèle ormar):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    title: str = ormar.String(max_length=200)
    author: Optional[Author] = ormar.ForeignKey(Author, skip_reverse=True)

#Créer des données d'échantillon
author = Author(premier_nom ="Test", dernier_nom ="Author")
Post = Post(title="Test Post", author=author)

assert post.author == author # ok
assert author.posts # Attribute Error!

#mais peut être utilisé dans order_by
Auteurs = (
    await Author.objects.select_related("posts").order_by("posts__title").all()
)
assert authors[0].first_name == "Test"

# note que les messages ne sont pas populaires pour l'auteur même si explicitement
# included in select_related - note no posts in model_dump()
assert author.model_dump(exclude={"id"}) =={"first_name": "Test", "last_name": "Author"}

# still peut filtrer à travers les champs de modèles liés
authors = await Author.objects.filter(posts__title="Test Post").all()
assert authors[0].first_name == "Test"
assert len(author) == 1

Ajouter à

L'ajout d'un modèle enfant à partir d'un parent entraîne l'ajout du modèle associé à la relation parentale actuellement chargée, la définition d'une valeur de clé externe au modèle enfant et la mise à jour du modèle.

1
2
3
4
5
6
7
8
9
department = await Department(name="Science").save()
course = Course(name="Math", completed=False) # note -pas enregistré

await department.courses.add(course)
assert course.pk n'est pas None # child modèle était sauvé
# relation sur le modèle de l'enfant est défini et FK colonne sauvé en db
assert course.department == department
# Relation sur le modèle parental est aussi bien défini
assert department.courses[0] == cours

!!!Si vous souhaitez ajouter un modèle enfant sur un modèle associé, la valeur de clé primaire du modèle parent doit exister dans la base de données.

1
2
3
4
Otherwise ormar raise `RelationshipInstanceError` comme il ne peut pas définir la valeur de la colonne ForeignKey de l'enfant
Si le modèle parent n'a pas de valeur clé primaire.

Cela signifie que, par exemple, au-dessus du département a été sauvé avant que vous puissiez appeler `department.courses.add()`.

!!!Attention Cette méthode ne fonctionne pas pour les relations plusieurs-à-plusieurs-dans ce cas, les deux parties de la relation doivent être enregistrées avant d'être ajoutées à la relation.

Éliminer

Les modèles concernés sont supprimés un par un.

Dans une relation inverse, l'appel à remove() ne supprime pas le sous-modèle, mais définit sa valeur ForeignKey à vide.

1
2
3
4
5
6
7
8
9
#continuer d'en haut
await department.courses.remove(course)
assert len(department.courses) == 0
# course still existait et était sauvé à la retraite
assert course.pk n'est pas None
assert course.department is None

#Remplacer l'enfant de db
await course.delete()

Mais si vous souhaitez effacer la relation et supprimer les enfants en même temps, vous pouvez émettre:

1
2
3
#Ce ne sera pas seulement clair la relation
#mais aussi supprimer la course relative de db
await department.courses.remove(course, keep_reversed=False)

Clear à

Un seul appel supprime tous les modèles associés.

Comme pour la suppression, clear() vide par défaut les colonnes ForeigKey sur les sous-modèles (toutes, qu 'elles soient chargées ou non).

1
2
# nulls colonne de département sur tous les cours liés à ce département
await department.courses.clear()

Si vous souhaitez supprimer complètement les enfants de la base de données, définissez keep_reversed=False.

1
2
# deletes de db tous les cours liés à ce département
await department.courses.clear(keep_reversed=False)

Query Set Agent pour

La relation inverse expose l'API QuerysetProxy, qui vous permet d'interroger les modèles pertinents comme vous le feriez pour une requête normale.

Pour savoir quelles méthodes QuerySet sont disponibles, lisez Querysetproxy ci-dessous.

Les noms associés

Vous pouvez remplacer le nom du champ de modèle associé en fournissant le paramètre related_name comme suit:

 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
37
38
39
40
from typing import 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 Department(ormar.Model):
    ormar_config = ormar_base_config.copy()

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


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

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Department] = ormar.ForeignKey(
        Department, related_name="my_courses"
    )


department = Department(name="Science")
course = Course(name="Math", completed=False, department=department)

print(department.my_courses[0])
# Will produce:
# Course(id=None,
#        name='Math',
#        completed=False,
#        department=Department(id=None, name='Science'))

!!!La relation inverse lors de l'accès par tip renvoie une liste wekref.proxy pour éviter les références circulaires.

!!!avertit que lorsque vous fournissez plusieurs relations au même modèle, sinon vous ne pourrez plus générer automatiquement le nom related_name. dans ce cas, vous devez donc fournir le nom related_name pour tous les champs sauf un (qui peut être par défaut et généré) ou pour tous les champs associés.

Action de référence

Lorsqu 'un objet référencé par ForeignKey est modifié (supprimé ou mis à jour), ormar définit les contraintes SQL spécifiées par les arguments ondelete et onupdate.

Les valeurs possibles pour ondelete et onupdate se trouvent dans ormar.referentialAction:

!!!note Vous pouvez passer la valeur de la chaîne directement à ces deux paramètres au lieu d'ormar.ReferentialAction, mais cela n'est pas recommandé car cela briserait l'intégrité.

Cascade à

Chaque fois qu 'une ligne est supprimée (ou mise à jour) dans la table parent (référencée), la ligne correspondante dans la table enfant (référencée) avec la colonne correspondante de clé étrangère est également supprimée (ou mise à jour).

Limitation des limites

Lorsqu 'une ligne dans une table ou une sous-table de référence fait référence à une valeur dans la table référencée, la valeur ne peut pas être mise à jour ou supprimée.

De même, vous ne pouvez pas supprimer une ligne tant qu 'il y a une référence à une ligne dans la table ou la sous-table de référence.

Définir_NULL

Définissez ForeignKey sur None; ceci n'est possible que si nullable est vrai.

Définir une valeur par défaut

Définissez ForeignKey sur sa valeur par défaut; le serveur_default de ForeignKey doit être défini.

Note!!!Notez que la valeur par défaut n'est pas autorisée, vous devez le faire via server_default, que vous pouvez lire dans cette section.

Ne rien faire du tout

ne prend aucune mesure; La principale différence entre NO ACTION et RESTRICT est que, pour NO ACTION, la vérification de l'intégrité référentielle est effectuée après une tentative de modification de la table. RESTRICT vérifie avant d'essayer d'exécuter une instruction UPDATE ou DELETE. Si la vérification de l'intégrité référentielle échoue, les deux opérations de référence se comportent de la même manière: l'instruction UPDATE ou DELETE entraînera une erreur.

Relation Settings

Vous pouvez établir des liens relationnels de plusieurs façons.

Exemples de modèles

Le plus évident est de passer l'instance de modèle associée au constructeur.

 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
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

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


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

Valeur clé primaire

Vous pouvez également définir une relation en utilisant uniquement la valeur de la colonne pk du modèle associé.

 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
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

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


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

Dictionnaire

La prochaine option consiste à utiliser un dictionnaire de valeur-clé du modèle associé.

Vous pouvez construire le dictionnaire vous-même ou l'obtenir à partir d'un modèle existant en utilisant la méthode modèle_dump().

 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
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

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


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

Sans aucune

Enfin, vous pouvez explicitement le définir sur None (comportement par défaut si aucune valeur n'est passée).

 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
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

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


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

!!!Dans tous les cas non None, la valeur de clé primaire du modèle associé doit exister dans la base de données.

1
Otherwise Une erreur d'intégralité sera traitée par votre bibliothèque de pilotes de base.