Aller au contenu

Patrimoine

Parmi les différents types d'héritage de modèle ORM, ormar en supporte actuellement deux:

  • Mélangé à
  • Héritage de table concret (parent défini sur abstract = True)

Types d'héritage

Un bref résumé des différents types d'héritage:

  • Mixins[Support]-Ne sous-classez pas ormar. Model, définissez simplement les champs qui seront utilisés ultérieurement sur les différents modèles (par exemple, created_date et updated_date sur chaque modèle), seule la table de création du modèle réel est disponible, mais ajoutez les champs des mixins.
  • Concrète Table Héritage[SUPPORTED]-signifie que les parents sont marqués comme abstraits et que chaque enfant a sa propre table contenant les colonnes du parent et ses propres colonnes enfants, un peu comme Mixins, mais le parent est aussi un modèle.
  • héritage de table unique[non pris en charge]-signifie qu 'une seule table est créée, les champs sont une combinaison/somme du modèle parent et de tous les sous-modèles, mais le sous-modèle utilise uniquement un sous-ensemble des colonnes de la base de données (tous les modèles parents et leurs propres colonnes, en ignorant les autres sous-modèles).
  • Multiple/Join Table Héritage[Non supporté]-signifie qu 'une partie des colonnes sont enregistrées sur le modèle parent et une partie sur le modèle enfant, elles sont connectées les unes aux autres par une relation un-à-un, et les deux modèles sont manipulés en arrière-plan immédiatement
  • Modèle proxy[non supporté]-signifie que seul le parent possède une table réelle et que les enfants ajoutent simplement des méthodes, modifient les paramètres, etc.

Mélangé à

Pour utiliser Mixins, il suffit de définir une classe qui n'hérite pas d'ormar. Model, mais qui définit ormar. Fields comme variable de classe.

 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
base_ormar_config = ormar. ormarConfig (
    database = databases. Database (DATABASE_URL),
    metadata = sqlalchemy. MetaData (),
    engine = sqlalchemy. create_engine (DATABASE_URL),
)


#un mélange définit les champs, mais est une classe python normale
classe AuditMixin:
    created_by: str = ormar. String (max_length = 100)
    updated_by: str = ormar. String (max_length = 100, default ="Sam ")


Classe DateFieldsMixins:
    created_date: datetime. datetime = ormar. DateTime (default = www.example.com)
    updated_date: datetime. datetime = ormar. DateTime (default = www.example.com)


#a models can inherit from one or more mixins
Catégorie de classe (ormar.Model, DateFieldsMixins, AuditMixin):
    ormar_config = base_ormar_config.copy(tablename="catégories")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=50, unique=True, index=True)
    code: int = ormar.Integer()

!!!tip Notez que Mixin n'est pas un modèle, vous devez donc toujours hériter de l'ormar.Model et définir le champ ormar_config dans le modèle final.

La classe Catégorie ci-dessus aura quatre champs supplémentaires: created_date, updated_date, created_by et Updated_by.

Une seule table est créée pour la catégorie du modèle (catégorie), où le champ catégorie est combiné avec tous les champs Mixins.

Notez que Mixin dans le nom de la classe est facultatif, mais c'est une bonne pratique Python.

Héritage de table spécifique

Conceptuellement, l'héritage de table concret est très similaire à Mixins, mais utilise le vrai modèle. ormar comme classe de base.

!!!avertit que la classe de base a défini Abstract=True dans l'objet ormar_config, ce qui lève une erreur de définition de modélisation si vous essayez d'hériter d'une classe de balisage non abstraite.

Puisque ce modèle abstrait n'est jamais initialisé, vous pouvez ignorer les métadonnées et les bases de données dans sa définition ormar_config.

Mais si vous le fournissez, il sera hérité, de sorte que vous n'aurez pas à fournir les métadonnées et la base de données dans la classe finale/concrète.

Notez que vous pouvez toujours le remplacer dans une sous-classe/classe spécifique si vous le souhaitez.

Plus important encore, au moins une classe de la chaîne d'héritage doit fournir la base de données et les métadonnées, sinon une erreur sera généré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
# note que les classes de base sont abstraites =True
# since ce modèle ne sera jamais initialisé, vous pouvez sauter des métadonnées et des données de base
Classe AuditModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    created_by: str = ormar.String(max_length=100)
    updated_by: str = ormar.String(max_length=100, default="Sam")


#Mais si vous le provisez, il sera inherité- DRY (Don't Repeat Yourself) en action
class DateFieldsModel(modèle ormar):
    ormar_config = base_ormar_config.copy(
        abstrait = vrai,
        Metadata = Metadata,
        database = db,
    )

    created_date: datetime. datetime = ormar. DateTime (default = www.example.com)
    updated_date: datetime. datetime = ormar. DateTime (default = www.example.com)


#Cette façon de ne pas avoir à fournir des métadonnées et des databases dans une classe concrète
Catégorie de classe (DateFieldsModel, AuditModel):
    ormar_config = base_ormar_config. copy (tablename ="catégories ")

    id: int = ormar. Integer (primary_key = True)
    name: str = ormar. String (max_length = 50, unique = True, index = True)
    code: int = ormar. Integer ()

La liste des options/paramètres hérités est la suivante: métadonnées, bases de données et contraintes.

En plus de cela, bien sûr, tous les champs de la classe de base sont combinés et créés dans des tables concrètes du modèle final.

!!!Remarquez que vous n'avez pas besoin de fournir abstarct = False dans la classe finale-c'est le paramètre par défaut qui n'est pas hérité.

Redéfinir un champ dans une sous-classe

Notez que vous pouvez redéfinir les champs précédemment créés comme vous le feriez avec l'héritage de classe Python normal.

Chaque fois que vous définissez un champ avec le même nom et une nouvelle définition, il remplace complètement le champ défini précédemment.

 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
#classe de base
class DateFieldsModel (modèle ormar):
    ormar_config = OrmarConfig (
        abstrait = vrai,
        Metadata = Metadata,
        database = db,
        #note que les colonnes uniques ont besoin de scalalchemy db colonnes noms ne sont pas les colonnes de l'ordre
        constraints =[ormar. UniqueColumns ("creation_date "," modification_date ")]
    )

    created_date: datetime.datetime = ormar.DateTime(
        default= datetime.datetime.now, name="creation_date"
    )
    updated_date: datetime.datetime = ormar.DateTime(
        default= datetime.datetime.now, name="modification_date"
    )


Classe RedefinedField(DateFieldsModel):
    ormar_config = OrmarConfig(
        tablename="redéfinitions",
        Metadata=Metadata,
        database=db,
    )

    id: int = ormar.Integer(primary_key=True)
    #ici la date_créée est replacée par le champ String
    created_date: str = ormar.String(max_length=200, name="creation_date")


#Vous pouvez vérifier que le champ final est correctement déclaré et créé
changed_field = RedefinedField.ormar_config.model_fields["created_date"]
assert change_field.default est None
assert changed_field.alias == "date_création"
assert any(x.name == "creation_date" pour x dans RedefinedField.ormar_config.table.columns)
Assert isinstance(
    RedefinedField.ormar_config.table.columns["creation_date"].type,
    sqlalchemy.sql.sqltypes.String,
)

!!!avertit que si vous déclarez une contrainte UniqueColumns avec un nom de colonne, le modèle final doit déclarer une colonne portant le même nom. sinon, une erreur ModelDefinitionError sera levée.

1
2
3
4
5
6
Ainsi dans l'exemple ci-dessus si vous ne provide pas `name` pour le `created_date` dans `RedefinedField` Modèle
Ormar va compléter.

`created_date: str = ormar.String(max_length=200) # exception`

`created_date: str = ormar.String(max_length=200, name="creation_date2") # exception`

Relations héritées

Vous pouvez déclarer une relation à chaque étape de l'héritage, que ce soit dans une classe parente ou une sous-classe.

Lorsque vous définissez une relation au niveau d'un sous-modèle, elle remplace la relation définie dans le modèle parent (si le même nom de champ est utilisé), ou elle n'est accessible que par ce sous-modèle si vous définissez une nouvelle relation.

Lorsque vous héritez d'une relation, vous devez toujours faire attention au paramètre related_name, qui doit être unique dans le modèle pertinent lorsque vous définissez plusieurs sous-classes qui héritent de la même relation.

Si vous ne fournissez pas l'argument related_name, ormar calcule pour vous. cela s'applique à l'héritage, car tous les sous-modèles doivent avoir des noms de classe différents qui sont utilisés pour calculer le nom related_name par défaut (classe.name.lower()+'s').

Toutefois, si vous fournissez un nom_related, ce nom ne peut pas être réutilisé dans tous les sous-modèles car ils se superposent mutuellement du côté du modèle associé.

Vous avez donc deux options:

  • Redéfinir les champs relationnels dans le sous-modèle et fournir manuellement un paramètre_name related différent
  • laisse le related_name d'ormar traiter-> l'ajustement automatique est: related_name + "_" + nom de la table du sous-modèle

Cela peut sembler compliqué, mais regardons l'exemple suivant:

Relations de clé étrangère

 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
#modèle normal utilisé en relation
Classe Person(modèle ormar):
    ormar_config = base_ormar_config.copy()

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


#modèle parent-nécessaire pour être abstrait
Voiture de classe (modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=50)
    owner: Person = ormar.ForeignKey(Person)
    # note que nous avons référé au modèle Personne encore une fois, de sorte que nous avons**à**provide related_name
    co_owner: Person = ormar.ForeignKey(Person, related_name="coowned")
    created_date: datetime.datetime = ormar.DateTime(default= datetime.datetime.now)


Classe Truck(auto):
    ormar_config = base_ormar_config.copy()

    max_capacity: int = ormar.Integer()


Classe Bus(voiture):
    ormar_config = base_ormar_config.copy(tablename="entreprises")

    max_persons: int = ormar.Integer()

Maintenant, lorsque vous examinez les champs sur le modèle Personne, vous obtenez:

1
2
3
4
5
6
7
8
9
Personne.ormar_config. modèle_fields
"""
{\id}:<class 'ormar.fields.model_fields.Integer'>,
'name':<class 'ormar.fields.model_fields.String'>,
'trucks':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_trucks':<class 'ormar.fields.foreign_key.ForeignKey'>,
'buss':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_buses':<class 'ormar.fields.foreign_key.ForeignKey'>}
"""

Remarquez comment vous avez des champs camions et bus qui conduisent à des catégories de camions et bus appartenant à cette personne. il n'y a pas de paramètre related_name, donc le nom par défaut est utilisé.

Les voitures copropriétaires simultanées doivent être référencées par les suffixes coowned_trucks et coowned_buses. Ormar ajoute les suffixes_trucks et_buses tirés des noms des tables de sous-modèles.

Ça a l'air bien, mais le nom par défaut pour les camions appartenant à notre propre entreprise est OK (camion), mais les bus sont laids, alors comment pouvons-nous le changer?

La solution est très simple: il suffit de redéfinir les champs de la classe Bus et de fournir un paramètre related_name différent.

1
2
3
4
5
6
7
#rester au-dessus de l'exemple remains le même
Classe Bus(voiture):
    ormar_config = base_ormar_config.copy(tablename="entreprises")

    #nouveau champ qui change le nom_related
    owner: Personne = ormar.ForeignKey(Personne, nom_related ="buses")
    max_persons: int = ormar.Integer()

Maintenant, les colonnes semblent beaucoup mieux.

1
2
3
4
5
6
7
8
9
Personne.ormar_config. modèle_fields
"""
{id}:<class 'ormar.fields.model_fields.Integer'>,
'name':<class 'ormar.fields.model_fields.String'>,
'trucks':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_trucks':<class 'ormar.fields.foreign_key.ForeignKey'>,
'buses':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_buses':<class 'ormar.fields.foreign_key.ForeignKey'>
"""

!!!Remarque Vous pouvez également fournir un nom_related pour le champ propriétaire, ce qui ajoutera le suffixe correct.

1
2
3
`owner: Person = ormar.ForeignKey(Person, related_name="owned")`

Et les champs de modèle pour les personnes oubliées voudraient devenir `owned_trucks` et `owned_buses`.

Plusieurs à plusieurs relations

De même, vous pouvez hériter d'un modèle qui a déclaré une relation ManyToMany, mais avec une différence substantielle-le modèle Through.

Puisque le modèle Through sera capable de contenir d'autres champs, et qu 'il ne lie désormais que deux tables (de et vers une table), chaque enfant qui hérite d'un champ relationnel m2m doit avoir un modèle Through distinct.

Bien sûr, vous pouvez remplacer les relations dans chaque sous-modèle, mais cela nécessite un code supplémentaire et détruit le sens de l'héritage entier. si vous êtes d'accord avec la convention de nommage par défaut, Ormar s'en occupera pour vous, et vous pouvez toujours remplacer manuellement la convention dans les enfants si vous le souhaitez.

Encore une fois, regardons des exemples pour saisir plus facilement le concept.

Nous allons modifier l'exemple précédent décrit ci-dessus pour utiliser la relation m2m de co_owners.

 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
# person remet le même sac comme au-dessus
Classe Person(modèle ormar):
    ormar_config = base_ormar_config.copy()

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

#nouveau modèle entre Person et Car2
Classe PersonsCar(modèle ormar):
    ormar_config = base_ormar_config.copy(tablename="cars_x_persons")

# note how co_owners est maintenant ManyToMany relation
Classe Car2(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=50)
    # note the related_name - needs to be unique across Person
    #modèle, regardless of how many different models leads to Person
    owner: Person = ormar.ForeignKey(Person, related_name="owned")
    co_owners: List[Person] = ormar.ManyToMany(
        Personne, through=PersonsCar, related_name="coowned"
    )
    created_date: datetime.datetime = ormar.DateTime(default= datetime.datetime.now)


# child models defend uniquement les champs additionnels
Classe Truck2(Car2):
    ormar_config = base_ormar_config.copy(tablename="trucks2")

    max_capacity: int = ormar.Integer()


Classe Bus2(Car2):
    ormar_config = base_ormar_config.copy(tablename="buses2")

    max_persons: int = ormar.Integer()

Ormar modifie automatiquement le nom related_name du champ pour inclure le nom de la table du sous-modèle. le nom par défaut est le nom original related_+ '_' + nom de la sous-table.

Ainsi, pour la classe Truck2, la relation définie owner: Person = ormar.ForeignKey(Person, related_name="owned") devient owned_trucks2

Vous pouvez vérifier les noms en examinant la liste des champs présents sur le modèle de personne.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Personne.ormar_config. modèle_fields
{
# note comment tous les champs de relation doivent être uniques sur Person
# regardless si autogénéré ou manuellement overwritten
« id.»:<class 'ormar.fields.model_fields.Integer'>,
'nom':<class 'ormar.fields.model_fields.String'>,
# note que nous avons étendu sur l'exemple précédent, donc tous les "anciens" champs sont ici
'trucks':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_trucks':<class 'ormar.fields.foreign_key.ForeignKey'>,
'buses':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_buses':<class 'ormar.fields.foreign_key.ForeignKey'>,
# newly defined related fields
'owned_trucks2':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_trucks2':<class 'abc.ManyToMany'>,
'owned_buses2':<class 'ormar.fields.foreign_key.ForeignKey'>,
'coowned_buses2': <class 'abc.ManyToMany'>
}

Mais ce n'est pas tout-c'est la structure interne d'ormar, mais elle affecte la structure des données dans la base de données, alors examinons le modèle de passage direct des modèles Bus2 et Truck2.

1
2
3
4
5
6
7
8
9
Bus2.ormar_config.model_fields ['co_owners'].through
<class 'abc.PersonsCarBus2'>
Bus2.ormar_config.model_fields ['co_owners'].through.ormar_config.tablename
'cars_x_persons_buses2'

Truck2.ormar_config.model_fields ['co_owners'].through
<class 'abc.PersonsCarTruck2'>
Truck2.ormar_config.model_fields ['co_owners'].through.ormar_config.tablename
'cars_x_persons_trucks2'

Comme vous pouvez le voir ci-dessus, ormar a cloné le modèle Through pour chaque sous-classe et a ajouté le nom de la sous-classe à la fin, tout en changeant le nom de la table pour le champ cloné, en utilisant le nom de la table pour les enfants.

Notez que non seulement le modèle original n'est pas utilisé, mais que les tables de ce modèle sont également supprimées des métadonnées:

1
2
3
Bus2.ormar_config.metadata.tables.keys()
dict_keys(['test_date_models', 'categories', 'subjects', 'persons', 'trucks', 'buses', etc.),
           'cars_x_persons_trucks2', 'trucks2', 'cars_x_persons_buses2', 'buses2'])

Notez donc que si vous introduisez l'héritage jusqu 'au bout et convertissez le modèle en un modèle parent abstrait, vous risquez de perdre des données dans la table si vous ne faites pas attention.

!!!Notez que le nom de table original et le nom de modèle du modèle Through ne sont jamais utilisés. seules les tables de modèle clonées sont créées et utilisées.

Attention!!!Notez que chaque sous-classe du modèle pour lequel la relation ManyToMany est définie génère un nouveau modèle Through, ce qui signifie également de nouvelles tables de base de données.

1
2
Cela signifie que chaque fois que vous définissez un modèle d'enfant, vous devez créer manuellement.
la table dans la base de données, ou exécuter une migration (avec alambic).

Exclure le champ parent

Ormar vous permet de sauter certains champs du modèle parent dans le modèle hérité.

!!!Notez que le même comportement peut être obtenu en divisant le modèle en modèles plus abstraits et en mixins-c'est la façon préférée normalement.

Pour ignorer certains champs dans un sous-modèle, liste tous les champs à ignorer model.ormar_config.exclude_parent_fields paramètres 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
base_ormar_config = OrmarConfig(
    metadata=sa.MetaData(),
    database=databases.Database(DATABASE_URL),
)


Classe AuditModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    created_by: str = ormar.String(max_length=100)
    updated_by: str = ormar.String(max_length=100, default="Sam")


class DateFieldsModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    created_date: datetime.datetime = ormar.DateTime(
        default= datetime.datetime.now, name="creation_date"
    )
    updated_date: datetime.datetime = ormar.DateTime(
        default= datetime.datetime.now, name="modification_date"
    )


Catégorie de classe (DateFieldsModel, AuditModel):
    ormar_config = base_ormar_config.copy(
        tablename="catégories"
        # Set fields qui devraient être skippés
        exclude_parent_fields=["updated_by", "updated_date"],
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=50, unique=True, index=True)
    code: int = ormar.Integer()

#Remarque que maintenant les champs de mise à jour dans la catégorie sont gone in all places -> champs d'ordre, champs pydantiques et colonnes de table sqlachemy
#donc la liste complète des champs disponibles dans la catégorie est: ["created_by", "created_date", "id", "name", "code"]

Notez que vous n'avez qu'à fournir le nom du champ et que cela exclut le champ parent, quel que soit le modèle parent d'où provient le champ.

!!!Remarque Notez que si vous souhaitez remplacer un champ dans un sous-modèle, vous n'avez pas besoin de l'exclure, il suffit de remplacer la déclaration de champ dans le sous-modèle par le même nom.

Attention, ce comportement peut confondre mypy et les vérificateurs de type statique, mais l'accès à un champ inexistant échouera à l'exécution. c'est pourquoi la classe de base fractionnée est préférée.

Le même effet peut être obtenu en divisant la classe de base, par exemple:

 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
base_ormar_config = OrmarConfig(
    metadata=sa.MetaData(),
    database=databases.Database(DATABASE_URL),
)


Classe AuditCreateModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    created_by: str = ormar.String(max_length=100)


Classe AuditUpdateModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    updated_by: str = ormar.String(max_length=100, default="Sam")

class CreateDateFieldsModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    created_date: datetime.datetime = ormar.DateTime(
        default= datetime.datetime.now, name="creation_date"
    )

class UpdateDateFieldsModel(modèle ormar):
    ormar_config = base_ormar_config.copy(abstrait =True)

    updated_date: datetime.datetime = ormar.DateTime(
        default= datetime.datetime.now, name="modification_date"
    )


Catégorie de classe (CreateDateFieldsModel, AuditCreateModel):
    ormar_config = base_ormar_config.copy(tablename="catégories")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=50, unique=True, index=True)
    code: int = ormar.Integer()

De cette façon, vous pouvez hériter de la création et de la mise à jour des classes si nécessaire, sinon vous ne pouvez hériter que d'une seule classe.