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

Больше чем больше

ManyToMany(to, through) имеет необходимые параметры to и дополнительные значения, он принимает классы целевых и реляционных моделей.

Столбцы и типы Sqlalchemy автоматически извлекаются из целевой модели.

  • Столбец Sqlalchemy: класс ключевых столбцов целевой модели
  • Тип (для pydantic): Тип целевой модели

Определение модели

 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)

Создание данных выборки:

1
2
3
guido = await Author.objects.create (first_name ="Guido", last_name ="Van Rossum")
post = await Post.objects.create(title="Hello, M2M", author=guido)
news = await Category.objects.create(name="News")

Обратная связь

Поле внешнего ключа автоматически регистрирует обратную сторону отношений.

По умолчанию это имя дочерней (исходной) модели + s, как сообщение в следующем фрагменте кода:

 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
Категория (Ormar.Model):
    ormar_config = base_ormar_config.copy (tablename="categories")

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


Класс Post(Ormar.Model):
    ormar_config = base_ormar_config ()

    id: int = ormar.Integer(primary_key =True)
    title: str = ormar.String(max_length =200)
    Категория: Optional[List] = ormar.ManyToMany(Category)

# create of the Sample data
post = await Post.objects.create(title="Hello, M2M")
news = await Category.objects.create(name="News")
await post.categories.add (новости)

# Now you can query and access from both sides:
post_check = Post.objects.select ("categories").get(
assert post_check.categories [0] = news

# Query Through auto registered reverse side
category_check = Category.objects.select ("posts").get()
assert category_check.posts [0] = post

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

Связанные с этим названия

По умолчанию связанные имена генерируются таким же образом, как и внешние ключи (class.name.lower ()+'s'), но вы также можете переопределить это имя, указав соответствующие параметры имени, как показано ниже:

1
2
3
Категория: Категория: Слайд ((((()))
        Категория, through=PostCategory, related_name ="new_categories"
    )

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

Пропустить обратную связь

Если вы уверены, что не хотите обратной связи, используйте флаг skip_reverse =True.

Если вы установили флаг skip_reverse внутри, поле все еще регистрируется на другом конце отношения, поэтому вы можете:

  • Фильтрация по соответствующим полям модели в обратной модели
  • order_by Связанные поля модели в обратной модели

Но вы не можете:

  • Использование related_name для доступа к соответствующим полям обратной модели
  • Даже если зависимость выбрана с противоположной стороны модели, возвращаемая модель не заполняется обратным экземпляром (соединения не блокируются, поэтому взаимосвязи можно фильтровать и сортировать)
  • Эта связь не заполняется моделями_dump () и json(). Когда* заполняется из словаря или json (также через fastapi), вы не можете передать вложенные связанные объекты. В соответствии с дополнительными настройками в конфигурации pydantic, он будет проигнорирован или вызовет ошибку.

Примеры:

 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
Категория (Ormar.Model):
    ormar_config = base_ormar_config.copy (tablename="categories")

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


Класс Post(Ormar.Model):
    ormar_config = base_ormar_config ()

    id: int = ormar.Integer(primary_key =True)
    title: str = ormar.String(max_length =200)
    Категория: Optional[List] = ormar.ManyToMany(Категория, skip_reverse =True)

# create of the Sample data
post = await Post.objects.create(title="Hello, M2M")
news = await Category.objects.create(name="News")
await post.categories.add (новости)

assert post.categories [0] = news # ok
Article # Attribute Error!

But Still Can use in Order
Категория:(
    Category.objects.select ("posts"). order_by ("posts_title").all()
)
assert categories[0]. first_name == "Test"

# note that posts are not populated for author even if explicitly
# included in select_related- note no posts in model_dump ()
Model_dump (exclude={"id"}) ={"name": "News"}

# still can filter through fields of related model
categories = await Category.objects.filter(posts__title ="Hello, M2M").all()
assert categories[0].name == "Новости"
assert len(категория) = 1

Через модель.

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

 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)

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

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

Если поле пройдено не задано, оно будет создано для вас.

По умолчанию используется следующее соглашение по именованию:

  • Для класса, это сочетание двух классов (родительский + другой), поэтому в приведенном выше примере это будет PostCategory.
  • Для имен таблиц он похож, но в конце названия класса в строчном регистре есть нижние линии и s, в приведенном выше примере будут posts_categorys

Настройка по имени связи

По умолчанию имя связанной модели определяется строчными строчными именами.

Так что в таких примерах:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
... # course declaration omitted
Класссный студент (Ormar):
    ormar_config = base_ormar_config ()

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

# will produce default Through model like follows (example simplified)
Class StudentCourse(модель):
    ormar_config = base_ormar_config.copy(tablename="students_courses")

    id: int = ormar.Integer(primary_key =True)
    student = ormar.ForeignKey(Student) # default name
    course = ormar.ForeignKey(Course) # default name

Чтобы настроить имя поля/связи в модели Through, теперь можно использовать новые параметры ManyToMany:

  • Through_relation_name-имя поля, ведущего к модели, объявленной ManyToMany
  • Through_reverse_relation_name-имя поля, ведущего к модели, к которой ведет ManyToMany

Примеры:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
... # course declaration omitted
«Органа»((
    Database=databases.Database("sqlite:///db.sqlite")
    metadata= sqalchemy (),
)


Класссный студент (Ormar):
    ormar_config = base_ormar_config ()

    id: int = ormar.Integer(primary_key =True)
    name: str = ormar.String(max_length =100)
    Courses = ormar.ManyToMany(Course,
                               Through_relation_name ="student_id",
                               Through reverse_relation_name ="course_id")

# will produce Through model like follows (example Simplified)
Class StudentCourse(модель):
    ormar_config = base_ormar_config.copy(tablename="student_courses")

    id: int = ormar.Integer(primary_key =True)
    student_id = ormar.ForeignKey(Student) # set by through relation_name
    course_id = ormar.ForeignKey(Course) # set by through reverse_relation_name

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

Через поле.

Поля автоматически добавляются к обратной стороне связи.

Открытые поля называются строчными буквами имени имени класса.

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

В примере параметров модели показано следующее:

 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)

Поле через него можно использовать в качестве обычного поля модели в большинстве операций QuerySet.

Обратите внимание на то, что поля прикрепляются только к соответствующему концу запроса, поэтому:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
post = await Post.objects.select ("categories").get(
Источник: No Through Field
Postcategory is None
# Related Models Have Through field
assert post.categories[0].postcategory is not None

# same is applicable for reversed query
category = await Category.objects.select ("posts").get(
Postcategory is None
assert category.posts [0].postcategory is not None

Поля используются для фильтрации данных.

1
2
3
4
5
== (
        Post.objects.select ("категория")
        .filter(postcategory_sort_order_gt =1)
        .get()
        )

!!!tip Обратите внимание, что, хотя фактические экземпляры не заполнены в исходной модели, в запросах, инструкциях order by и т. д. Вы можете получить доступ к модели с обеих сторон. Таким образом, следующие запросы имеют точно такой же эффект (доступ к категориям)

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

Модель позволяет последовательно использовать запросы.

1
2
3
4
5
== (
        Post.objects.select ("категория")
        . order_by ("-postcategory__sort_order")
        .get()
    )

Также можно использовать fields и except_fields для выбора подмножества столбцов с помощью обычного метода QuerySet.

1
2
3
4
5
Пост2 = (
        Post.objects.select ("категория")
        .exclude_fields ("postcategory__param_name")
        .get()
        )

!!! Обратите внимание, что, поскольку через fields все поля связей явно недействительны, так как отношения заполняются в поле ManyToMany, стандартные методы модели, такие как save() и update(), не следует использовать до перезагрузки поля из базы данных.

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

1
2
3
4
Always reload the field before modification
await post2.categories[0]. postcategory.load ()
#Только then Update the field
await post2.categories[0]. postcategory.update (sort_order =3)

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

!!!предупреждает, что если вы используете ie fastapi, связанные модели, которые частично загружаются через поля, могут привести к ошибкам проверки pydantic (основная причина, по которой они не заполняются по умолчанию). Таким образом, вам нужно либо исключить связанные поля из ответа, либо загрузить связанные модели полностью. В приведенном выше примере это означает:python await post2.categories[0].postcategory.post.load() await post2.categories[0].postcategory.category.load() или вы можете использовать load_all ():python await post2.categories[0].postcategory.load_all()

Предпочтительным способом обновления является метод queryset update()

1
2
filter the desired related model with through field and update Only through field params
await post2.categories.filter(name ='Test category').update(postcategory={"sort_order": 3})

Закон о взаимоотношениях

Добавить

Добавлено (элементы: Модель,**kwargs)

Позволяет добавлять модели в отношения "Мно-ко".

1
2
3
4
# Add a category to a post.
await post.categories.add (новости)
# or from the other end:
await news.posts.add (post)

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

1
Otherwise an IntegrityError will be raised by your driver library.

Если модель объявляется с помощью Through модели с дополнительными полями, их можно заполнить во время добавления к связи дочерних моделей.

Для этого передайте ключевые параметры с именем поля и значением вызова add().

Обратите внимание, что это относится только к взаимоотношениям "много-ко".

1
2
3
4
post = await Post(title="Test post").save(
Категория = await Категория (name="Test category").save()
# apart from model pass arguments referencing through model fields
await post.categories.add (категория, sort_order =1, param_name ='test')

Устранение

Соответствующая модель исключена.

Также удаляются связи из базы данных.

1
await news. posts.remove (post)

Очистить

Удалите все связанные модели за один вызов.

Также удаляются связи из базы данных.

Ждать news.posts.clear ()

Агент набора запросов

Обратные отношения раскрывают API QuerysetProxy, который позволяет вам запрашивать связанные модели, как обычные запросы.

Чтобы узнать, какие методы доступны в Querysetproxy, прочитайте ниже