Перейти к содержанию

Наследие

Среди различных типов наследства моделей ORM, ormar в настоящее время поддерживает два из них:

  • Переместить
  • Наследование конкретных таблиц (родительский параметр abstract=True)

Тип наследования

Краткое описание различных видов наследования:

  • Mixins [Поддержка] -Не подстраивайте ormar.Model, просто определите поля, которые будут использоваться позднее в различных моделях (например, created_date и updated_date в каждой модели), таблицы создаются только в реальной модели, но добавьте поля в mixins
  • наследование таблицы[SUPPORTED] -означает, что родительский родитель помечен как абстрактный, и каждый дочерний элемент имеет свою собственную таблицу, содержащую столбцы от родительского и собственного дочернего столбца, что-то похожее на Mixins, но родительский родитель также является моделью.
  • Наследование одной таблицы[не поддерживается] -это означает, что создается только одна таблица, поля которой представляют собой комбинацию/сумму родительской модели и всех дочерних моделей, но в которой используется только подмножество столбцов в базе данных (все родительские модели и собственные столбцы, пропуская другие дочерние модели)
  • Наследование нескольких/таблиц соединений[не поддерживается] -это означает, что некоторые столбцы сохраняются в родительской модели, а частично в подмодели, они связаны друг с другом одним на один, и обе модели немедленно работают в фоновом режиме
  • Прокси-модель[не поддерживается] -означает, что только родительский элемент имеет реальную таблицу, а дочерний элемент просто добавляет методы, изменяет настройки и т. д.

Переместить

Чтобы использовать Mixins, просто определите класс, который не наследуется от ormar.Model, а определите ormar.Fields как переменную класса.

 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
«Органа»((
    Database=databases.Database(DATABASE),
    metadata= sqalchemy (),
    engine=sqlalchemy. create_engine (DATABASE),
)


A mixin defines the fields but is a normal python класс
Классы AuditMixin:
    created_by: str = ormar.String(max_length =100)
    updated_by: str = ormar.String(max_length =100, default="Sam")


Dateclass Филдс Mixins:
    created_date: datetime = ormar.DateTime (default= datetime.datetime.now)
    updated_date: datetime.datetime = ormar.DateTime (default= datetime.datetime.now)


A models Can Inherit from One or More Mixins
Категория (ormar.Model, DateFields Mixins, AuditMixin):
    ormar_config = base_ormar_config.copy (tablename="categories")

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

!!!tip Обратите внимание, что Mixin не является моделью, поэтому вам все равно нужно наследовать ormar.Model и определить поле ormar_config в окончательной модели.

Класс категорий выше будет иметь четыре дополнительных поля: created_date, updated_date, created_by и Updated_by.

Создается только одна таблица для категории модели (категории), в которой поля класса категории категорий объединяются со всеми полями Mixins.

Обратите внимание, что Mixin в классе не является обязательным, но это хорошая практика Python.

Таблица Наследование

Концептуально наследование конкретных таблиц очень похоже на Mixins, но в качестве базового класса используется фактический ormar.Models.

!!!предупреждает, что базовый класс установил Abstract=True в объекте ormar_config и что при попытке наследовать из неабстрактного класса тегов будет выведена ошибка ModelDefinitionError.

Поскольку эта абстрактная модель никогда не инициализируется, вы можете пропустить метаданные и базы данных в ее определении ormar_config.

Но если вы предоставите его, он наследуется, так что вам не придется предоставлять метаданные и базы данных в окончательном/конкретном классе.

Обратите внимание, что при необходимости вы всегда можете перезаписать его в подклассы/классы.

Более того, по крайней мере, один класс в цепочке наследования должен предоставлять базу данных и метаданные-в противном случае это вызовет ошибку.

 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 that bases classes have abstract=True
# since this model не будет initialized you can skip metadata и database
Класс AuditModel(Ormar):
    ormar_config = base_ormar_config.copy (abstract=True)

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


But Если вы гарантируете это, что она будет оставаться inherited - DRY (Don't Repeat Yourself) in Action
Dateclass Филдс Model (ormar.Model):
    10.00.201.201.201.2011
        abstract=True,
        Метаданные = метаданные,
        db =db,
    )

    created_date: datetime = ormar.DateTime (default= datetime.datetime.now)
    updated_date: datetime.datetime = ormar.DateTime (default= datetime.datetime.now)


Это значит, что вы не можете получить метаданные и данные в классах.
Категория (FieldsModel, AuditModel):
    ormar_config = base_ormar_config.copy (tablename="categories")

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

Список наследуемых параметров/параметров выглядит следующим образом: метаданные, базы данных и зависимости.

Конечно, все поля базового класса объединяются и создаются в конкретных таблицах окончательной модели.

!!!tip Обратите внимание, что не обязательно указывать abstarct=False в окончательном классе-это значение по умолчанию, которое не наследуется.

Переопределение полей в подклассах

Обратите внимание, что вы можете переопределить ранее созданные поля так же, как и обычное наследование классов Python.

Каждый раз, когда задается поле с тем же именем и новым определением, оно полностью заменяет ранее определенные поля.

 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
#Базовый класс
Dateclass Филдс Model (ormar.Model):
    «Органа»((
        abstract=True,
        Метаданные = метаданные,
        db =db,
        Комментарии that Uniquens Columneed sqlalchemy db columns names not the ormar ones
        constraints=[ormar.UniqueColumns("creation_date", "modification_date")
    )

    Слайд:«Слайн»((
        default= datetime.datetime.now, имя ="creation_date"
    )
    Урожание: Урожание (
        default= datetime.datetime.now, name="modification_date"
    )


Class RedefinedField(DateFieldsModel):
    «Органа»((
        tablename="redefines".
        Метаданные = метаданные,
        db =db,
    )

    id: int = ormar.Integer(primary_key =True)
    # here the created_date is replaced by the String field
    created_date: str = ormar.String(max_length =200, name="creation_date")


You Can Verify that the final field is correctly Declared and created
changed_field = RedefinedField.ormar_confields ["created_date"]
assert changed_field.default is None
assert changed_field.alias = "creation_date"
assert any(x.name == "creation_date" for x in RedefinedField. ormar_config.table.columns)
Оборотная система (
    RedefinedField. ormar_config.table.columns["creation_date"].type,
    sqlalchemy. sqltypes.String.
)

!!!предупреждает, что при использовании зависимостей UniqueColumns окончательная модель должна объявить столбец с таким же именем. В противном случае будет выведена ошибка ModelDefinitionError.

1
2
3
4
5
6
So in example above Если вы не оказываете услуги `name` for `created_date` in `RedefinedField` model
Ормар будет завершать.

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

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

Наследственные отношения

Связь можно объявить на каждом этапе наследования, будь то в родительском или подклассе.

Когда связь определяется на уровне подмодели, она перезаписывает связь, определенную в родительской модели (если используется одно и то же имя поля), или, если определяется новое отношение, доступ к ней возможен только из этой подмодели.

При наследовании необходимо всегда помнить о параметре related_name, который должен быть уникальным в связанной модели при определении нескольких подклассов, наследующих одно и то же отношение.

Если параметр related_name не указан, ormar будет вычисляться для вас. Это относится к наследованию, так как все подмодели должны иметь разные имена классов, которые используются для вычисления related_name (class.name.lower()+'s') по умолчанию.

Однако, если указать related_name, это имя не может быть повторно использовано во всех подмоделях, так как они накладываются друг на друга на конце связанной модели.

Таким образом, у вас есть два варианта:

  • Переопределение полей связей в подмодели и включение вручную другого параметра related_name
  • позволяет ormar обрабатывать-> автоматически настраиваемый related_name: исходное related_name + "_" + имя таблицы подмодели

Это может показаться сложным, но давайте посмотрим на следующий пример:

Внешние связи

 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
# Normal model used in relation
Класс Персон (модель):
    ormar_config = base_ormar_config ()

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


# parent model - needs to be abstract
Класс автомобиля (Ormar):
    ormar_config = base_ormar_config.copy (abstract=True)

    id: int = ormar.Integer(primary_key =True)
    name: str = ormar.String(max_length =50)
    owner: Person = ormar.ForeignKey(Person)
    # note that we refer to the Person model again so**have to**provide related name
    co_owner: Person = ormar.ForeignKey(Person, related_name ="coowned")
    created_date: datetime = ormar.DateTime (default= datetime.datetime.now)


class Truck(Car):
    ormar_config = base_ormar_config ()

    max_capacity: int = ormar.Integer()


Класс автобуса (Car):
    ormar_config = base_ormar_config.copy (tablename="buses")

    max_persons: int = ormar.Integer()

Теперь, когда вы проведете поля на модели Person, вы получите:

1
2
3
4
5
6
7
8
9
Person.ormar_config.model_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'>}
"""

Обратите внимание на то, что у вас есть поля грузовиков и автобусов, которые приводят к категории грузовиков и автобусов, принадлежащих данному человеку. Параметр related_name отсутствует, поэтому используется имя по умолчанию.

На автомобили, находящиеся в совместном владении, должны указываться в coowned_trucks и coowned_buses. Ormar добавляет суффиксы_trucks и_buses из названия таблицы подмоделей.

Звучит хорошо, но название по умолчанию для собственного грузовика хорошее (грузовик), но автобус некрасивый, так как мы можем его изменить?

Решение очень простое: достаточно переопределить поля в классе Bus и задать другой параметр related_name.

1
2
3
4
5
6
7
# rest of the above example remains the same
Класс автобуса (Car):
    ormar_config = base_ormar_config.copy (tablename="buses")

    # new field that changes the related name
    owner: Person = ormar.ForeignKey(Person, related_name ="buses")
    max_persons: int = ormar.Integer()

Теперь столбы выглядят намного лучше.

1
2
3
4
5
6
7
8
9
Person.ormar_config.model_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'>}
"""

!!!Обратите внимание, что для поля владельца можно также указать related_name, чтобы добавить правильный суффикс.

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

И модель fields for Person owned cars Would become `owned_trucks` and `owned_buses`

Много отношений

Точно так же вы можете наследовать модель, которая объявляет отношения ManyToMany, но с одним существенным отличием- Through Model.

Так как модель Through сможет сохранять другие поля и теперь связана только с двумя таблицами (из таблицы и из таблицы), каждый дочерний элемент, наследующий поле связи m2m, должен иметь отдельную модель Through.

Конечно, вы можете переопределить отношения в каждой подмодели, но это требует дополнительного кода и разрушает все наследование. Если вы согласны с соглашением об именовании по умолчанию, Ormar решит эту проблему за вас, и при необходимости вы можете изменить это соглашение вручную в дочернем элементе.

Опять же, давайте рассмотрим примеры, чтобы легче понять концепцию.

В предыдущем примере, описанном выше, изменим связь co_owners m2m.

 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 remain the same as above
Класс Персон (модель):
    ormar_config = base_ormar_config ()

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

Новая модель между Person and Car2
Класс PersonsCar(модель ormar):
    ormar_config = base_ormar_config.copy(tablename="cars_x_persons")

# note how co_owners is now ManyToMany relation
Класс Car2(Ormar.Model):
    ormar_config = base_ormar_config.copy (abstract=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
    # model, regardless of how many different models leads to Person
    owner: Person = ormar.ForeignKey(Person, related_name ="owned")
    В. С. М.: М.: М.
        Person, through=PersonsCar, related_name ="coowned"
    )
    created_date: datetime = ormar.DateTime (default= datetime.datetime.now)


# child models define Only Additional Fields
Класс Трюк2 (Car2):
    ormar_config = base_ormar_config.copy(tablename="trucks2")

    max_capacity: int = ormar.Integer()


Класс Bus2(Car2):
    ormar_config = base_ormar_config.copy (tablename="buses2")

    max_persons: int = ormar.Integer()

Ormar автоматически изменяет related_name поля таким образом, чтобы оно содержало подмодель. Имя по умолчанию-это исходное related_name + '_' + имя подтаблицы.

Таким образом, для класса Truck2 определяемая связь owner: Person = ormar.ForeignKey(Person, related_name="owned") становится owned_trucks2

Имя можно проверить, изучив список полей, существующих в модели человека.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Person.ormar_config.model_fields
{
Оригинальное название: How All Relation Fields Need to Be Unique on Person
# regardless if autogenerated or manually overwritten
'id':<class 'ormar.fields.model_fields.Integer'>,
«name»:<class 'ormar.fields.model_fields.String'>,
# note that we expanded on preview example so all 'old' fields are here
«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'>
}

Но это еще не все. Это внутренняя структура ormar, но она влияет на структуру данных в базе данных, поэтому давайте проверим сквозные модели моделей Bus2 и 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'

Как вы видели выше, ormar клонирует модель Through для каждого подкласса и добавляет имя подкласса в конце, а также изменяет имя таблицы для полей клонирования, используя имя таблицы для подкласса.

Обратите внимание, что исходная модель не только не используется, но и таблица модели удаляется из метаданных:

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

Поэтому обратите внимание на то, что если вы вводите наследование и преобразуете модель в абстрактную родительскую модель, то вы можете потерять данные в таблице, если вы не будете осторожны.

!!!Примечание Примечание: Исходное имя таблицы и имя модели с Through никогда не использовались. Создаются и используются только таблицы с клонами.

!!!предупреждает, что каждый подкласс модели, который определяет отношения ManyToMany, создает новую Through модель, что также означает новые таблицы базы данных.

1
2
That means that each time you define a Child model you need to either manually create
The table in the database, or run a migration (with alembic).

Исключить родительские поля

Ormar позволяет пропустить некоторые поля в унаследованной модели из родительской модели.

!!!Обратите внимание, что одно и то же поведение может быть достигнуто, разбив модель на более абстрактные модели и миксин-это предпочтительный способ в обычных ситуациях.

Чтобы пропустить некоторые поля в подмодели, перечислите все поля, которые необходимо пропустить, следующие параметры model.ormar_config.exclude_parent_fields:

 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
Bays_ormar_config (
    metadata= sa.MetaData (),
    Database=databases.Database(DATABASE),
)


Класс AuditModel(Ormar):
    ormar_config = base_ormar_config.copy (abstract=True)

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


Dateclass Филдс Model (ormar.Model):
    ormar_config = base_ormar_config.copy (abstract=True)

    Слайд:«Слайн»((
        default= datetime.datetime.now, имя ="creation_date"
    )
    Урожание: Урожание (
        default= datetime.datetime.now, name="modification_date"
    )


Категория (FieldsModel, AuditModel):
    10.00.201.201.201.2011
        tablename="категории",
        Set fields That Should Be Skipped
        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)
    Код: int = ormar.Integer()

# Note that now the update fields in Category are gone in all places -ormar fields, pydantic fields and sqlachemy table columns
# so full list of available fields in Category is: ["created_by", "created_date", "id", "name", "code"]

Обратите внимание на то, что необходимо указать только имя поля, которое исключает родительское поле независимо от того, из какой родительской модели оно исходит.

!!!Обратите внимание на то, что если требуется перезаписать поле в подмодели, то не обязательно исключать его, просто перезаписать объявление поля в подмодели с тем же именем поля.

Обратите внимание на то, что такое поведение может запутать функцию mypy и статический инспектор типов, но доступ к несуществующим полям не будет работать во время выполнения. Вот почему предпочтение отдается разделению базового класса.

Такой же эффект достигается путем разделения базового класса, например:

 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
Bays_ormar_config (
    metadata=sa.MetaData(),
    Database=databases.Database(DATABASE),
)


Class AuditCreateModel(ormar.Model):
    ormar_config = base_ormar_config.copy (abstract=True)

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


class AuditUpdateModel(ormar.Model):
    ormar_config = base_ormar_config.copy (abstract=True)

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

Class CreateDateFieldsModel(ormar.Model):
    ormar_config = base_ormar_config.copy (abstract=True)

    Слайд:«Слайн»((
        default= datetime.datetime.now, name="creation_date"
    )

class UpdateDateFieldsModel(ormar.Model):
    ormar_config = base_ormar_config.copy (abstract=True)

    Урожание: Урожание (
        default= datetime.datetime.now, name="modification_date"
    )


Категория (CreateDateFieldsModel, AuditCreateModel):
    ormar_config = base_ormar_config.copy (tablename="categories")

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

Таким образом, при необходимости можно наследовать только один из классов, созданных и обновленных. В противном случае можно наследовать только один класс.