Lewati ke isi

banyak-ke-banyak

Banyak (untuk, melalui), dengan argumen yang diperlukan dan opsional melalui, mengambil kelas model target dan relasional.

Kolom dan tipe Sqlalchemy secara otomatis diambil dari model target.

  • Kolom Sqlalchemy: Kelas untuk model target kolom kunci primer
  • Tipe (untuk pydantic): jenis model target

Tentukan modelnya

 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)

Buat data sampel:

1
2
3
Guido = await Author.objeks.create (nama depan = "Guido," nama belakang = "Van Rossum")
Post = await Post.objeks.create (judul = "Hello, M2M," author = Guido)
News = await Category.obts.create (nama = "Berita")

hubungan terbalik

Bidang kunci asing secara otomatis mencatat sisi kebalikan dari hubungan.

Secara default, ini adalah nama model anak (sumber) + s, seperti pada posting dalam cuplikan kode berikut:

 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
Kategori Kelas (ormar. Model):
    ormar _ config = base _ ormar _ config (tablename = "kategori")

    Id: int = ormar. Integer (primary _ key = Benar)
    Nama: str = ormar. String (max _ length = 40)


Pos Kelas (ormar. Model):
    ormar _ config = base _ ormar _ config copy ()

    Id: int = ormar. Integer (primary _ key = Benar)
    Judul: str = ormar. String (max _ length = 200)
    Kategori: Opsional [Daftar [Kategori]] = ormar. Banyak (Kategori)

# buat beberapa data sampel
Post = await Post.objeks.create (judul = "Halo, M2M")
News = await Category.obts.create (nama = "Berita")
Menunggu post.kategory.add (berita)

# sekarang Anda dapat menanyakan dan mengakses dari kedua sisi:
post _ check = Post.objeks.select _ related ("kategori").get ()
Tegaskan posting _ periksa kategori [0] = = berita

# pertanyaan melalui sisi terbalik yang terdaftar secara otomatis
kategori _ periksa = Kategori. Objek. Pilih _ terkait ("posting").get ()
Kategori penetapan _ periksa postingan [0] = = postingan

Hubungan terbalik mengekspos API untuk mengelola objek terkait dari orang tua.

nama terkait

Secara default, nama korelasi dihasilkan dengan cara yang sama seperti hubungan kunci asing (class.name () + 's '), tetapi Anda juga dapat mengesampingkan nama ini dengan memberikan parameter nama korelasi sebagai berikut:

1
2
3
Kategori: Opsional [Persatuan [Kategori, Daftar [Kategori]]] = ormar. Banyak (
        Kategori, through=PostCategory, terkait _ name = "baru _ kategori"
    )

!!!memperingatkan ketika Anda menyediakan beberapa hubungan ke model yang sama, nama\ _ terkait tidak lagi dapat dibuat secara otomatis untuk Anda. Oleh karena itu, dalam hal ini, Anda harus memberikan\ _ nama terkait untuk semua bidang atau semua bidang terkait kecuali satu (satu dapat menjadi default dan dihasilkan).

Lewati hubungan terbalik

Jika Anda yakin tidak menginginkan hubungan yang terbalik, Anda dapat menggunakan lompatan ManyToMany\ _ reverse = Bendera asli.

Jika Anda mengatur bendera lewati\ _ mundur secara internal, bidang tersebut masih terdaftar di ujung lain hubungan, sehingga Anda dapat:

  • Filter dengan bidang model yang relevan dalam model terbalik
  • Pesanan\ _ oleh Bidang Model Terkait dalam Model Terbalik

Tapi Anda tidak bisa:

  • Gunakan\ _ nama terkait untuk mengakses bidang terkait dari model terbalik
  • Bahkan jika Anda memilih korelasi dari sisi sebaliknya model, model yang dikembalikan tidak akan diisi dalam contoh terbalik (tidak ada koneksi yang akan diblokir, sehingga Anda masih dapat menyaring dan mengurutkan hubungan).
  • Hubungan tersebut tidak dihuni dalam model\ _ dump () dan json () Ketika* dihuni dari kamus atau json (juga melalui fastapi), Anda tidak dapat melewati objek terkait yang bersarang. Tergantung pada pengaturan ekstra dalam konfigurasi pydantic, itu akan diabaikan atau kesalahan dilemparkan.

Contoh:

 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
Kategori Kelas (ormar. Model):
    ormar _ config = base _ ormar _ config (tablename = "kategori")

    Id: int = ormar. Integer (primary _ key = Benar)
    Nama: str = ormar. String (max _ length = 40)


Pos Kelas (ormar. Model):
    ormar _ config = base _ ormar _ config copy ()

    Id: int = ormar. Integer (primary _ key = Benar)
    Judul: str = ormar. String (max _ length = 200)
    Kategori: Opsional [Daftar [Kategori]] = ormar. ManyToMany (Kategori, lewati _ mundur = Benar)

# buat beberapa data sampel
Post = await Post.objeks.create (judul = "Halo, M2M")
News = await Category.obts.create (nama = "Berita")
Menunggu post.kategory.add (berita)

Tegaskan posting. Kategori [0] = = berita # ok
Tegaskan posting baru.# Kesalahan atribut!

# tapi masih bisa digunakan secara berurutan _ by
Kategori = (
    menunggu Kategori. Keberatan. pilih _ terkait ("posting"). urutan _ oleh ("posting _ _ judul"). semua ()
)
Menegaskan kategori [0]. first _ name = = "Tes"

# perhatikan bahwa posting tidak diisi untuk penulis bahkan jika secara eksplisit
# termasuk dalam pilih _ terkait - catatan tidak ada posting dalam model _ dump ()
model _ dump (tidak termasuk ={"id"}) = ={"name": "News"}

# masih dapat memfilter melalui bidang model terkait
Kategori = menunggu Kategori.objeks.filter (posting _ _ title = "Halo, M2M").all ()
Menegaskan kategori [0]. name = = "Berita"
Assert len (kategori) = = 1

Melalui model

Atau, jika Anda ingin menambahkan bidang tambahan, Anda dapat secara eksplisit membuat dan lulus kelas 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
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)

!!!Peringatan Harap dicatat bahwa meskipun Anda tidak memberikannya melalui model, itu akan dibuat secara otomatis untuk Anda dan tetap harus dimasukkan dalam contoh migrasi alembik.

!!!Tip Perhatikan bahwa jika Anda ingin menyesuaikan nama model Melalui atau database & nama tabel model, Anda perlu menyediakan model Melalui.

Jika Anda tidak menyediakan bidang "Pass," sistem akan membuatnya untuk Anda.

Konvensi penamaan default adalah:

  • Untuk nama kelas, itu adalah penyatuan dua nama kelas (induk + lainnya), jadi pada contoh di atas adalah PostCategory
  • Untuk nama tabel mirip, tetapi dengan garis bawah dan s di akhir nama kelas huruf kecil, yang pada contoh di atas adalah posting\ _ kategorys

Sesuaikan dengan nama hubungan

Secara default, nama model terkait adalah huruf kecil dengan nama hubungan model.

Jadi dalam contoh ini:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
... # deklarasi saja dihilangkan
Siswa Kelas (ormar. Model):
    ormar _ config = base _ ormar _ config copy ()

    Id: int = ormar. Integer (primary _ key = Benar)
    Nama: str = ormar. String (max _ length = 100)
    Kursus = ormar. Banyak (Kursus)

# akan menghasilkan default Melalui model seperti berikut (contoh disederhanakan)
Kelas StudentCourse (ormar. Model):
    ormar _ config = base _ ormar _ config (tablename = "siswa _ kursus")

    Id: int = ormar. Integer (primary _ key = Benar)
    Siswa = ormar. ForeignKey (Siswa) # nama default
    Kursus = ormar. ForeignKey (Kursus) # nama default

Untuk menyesuaikan nama bidang / hubungan dalam model Melalui, sekarang Anda dapat menggunakan parameter baru ManyToMany:

  • Melalui\ _ hubungan\ _ nama - nama bidang yang mengarah ke model yang menyatakan ManyToMany
  • Melalui\ _ reverse\ _ relationship\ _ name - ke nama bidang model yang mengarah ke ManyToMany

Contoh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
... # deklarasi saja dihilangkan
base _ ormar _ config = ormar. OrmarConfig (
    Database = database. Database ("sqlite: / / db.sqlite"),
    Metadata = sqlalchemy. MetaData (),
)


Siswa Kelas (ormar. Model):
    ormar _ config = base _ ormar _ config copy ()

    Id: int = ormar. Integer (primary _ key = Benar)
    Nama: str = ormar. String (max _ length = 100)
    Kursus = ormar. Banyak (Kursus,
                               melalui _ relationship _ name = "student _ id,"
                               melalui _ reverse _ relation _ name = "course _ id")

# akan menghasilkan Melalui model seperti berikut (contoh disederhanakan)
Kelas StudentCourse (ormar. Model):
    ormar _ config = base _ ormar _ config (tablename = "mahasiswa _ mata kuliah")

    Id: int = ormar. Integer (primary _ key = Benar)
    student _ id = ormar. ForeignKey (Student) # set oleh through _ relation _ name
    course _ id = ormar. ForeignKey (Course) # set oleh through _ reverse _ relation _ name

!!!Catatan: Perhatikan bahwa secara eksplisit menyatakan hubungan dalam model Melalui dilarang, jadi meskipun Anda memberikan kustom Anda sendiri Melalui model, Anda tidak dapat mengubah nama di sana dan perlu menggunakan yang sama melalui\ _ hubungan\ _ nama dan melalui\ _ mundur\ _ hubungan\ _ parameter nama ...

Melalui ladang

Bidang lulus secara otomatis ditambahkan ke sisi sebaliknya dari hubungan.

Bidang yang terbuka diberi nama huruf kecil dengan nama kelas.

Bidang terbuka secara eksplisit tidak memuat hubungan apa pun karena hubungan tersebut sudah dihuni di bidang ManyToMany, sehingga hanya berguna jika bidang tambahan disediakan pada model Through.

Dalam contoh pengaturan model, tampilannya seperti ini:

 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)

Bidang tembus dapat digunakan sebagai bidang model normal di sebagian besar operasi QuerySet.

Harap dicatat bahwa bidang pass hanya ditambahkan ke sisi yang relevan dari kueri, jadi:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Post = await Post.objeks.select _ related ("kategori").get ()
# Model sumber tidak memiliki bidang tembus
Pos pencuci mulut. Postkategori Tidak Ada
# model terkait telah melalui bidang
Tegaskan post.category [0] .postcategory bukan Tidak Ada

# sama berlaku untuk pertanyaan terbalik
Kategori = menunggu Kategori. Objek. Pilih _ terkait ("posting").get ()
Tegaskan kategori.postkategori Tidak Ada
Tegaskan kategori.post [0] .postkategori bukan Tidak Ada

Bidang dapat digunakan untuk menyaring data.

1
2
3
4
5
Posting = (
        Menunggu Post.objeks.Select _ terkait ("kategori")
        . filter (postcategory _ _ sort _ order _ _ gt = 1)
        . mendapatkan ()
        )

Tip!!!Perhatikan bahwa meskipun contoh sebenarnya tidak diisi dalam model sumber, Anda dapat mengaksesnya dari kedua sisi melalui model dalam pertanyaan, urutan dengan pernyataan, dll. Jadi kueri berikut memiliki efek yang persis sama (catatan akses berdasarkan kategori)

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

Kueri dapat digunakan secara berurutan melalui model.

1
2
3
4
5
Posting = (
        Menunggu Post.objeks.Select _ terkait ("kategori")
        . order _ by ("- postcategory _ _ sort _ order")
        . mendapatkan ()
    )

Anda juga dapat menggunakan bidang dan kecuali\ _ bidang untuk memilih subset kolom dengan gaya QuerySet normal.

1
2
3
4
5
Postingan 2 = (
        Menunggu Post.objeks.Select _ terkait ("kategori")
        tidak termasuk _ fields ("postcategory _ _ param _ name")
        . mendapatkan ()
        )

!!! Peringatan Perhatikan bahwa karena melalui bidang secara eksplisit membatalkan semua bidang relasional karena hubungan tersebut dihuni di bidang ManyToMany, metode model standar seperti menyimpan () dan memperbarui () tidak boleh digunakan sebelum memuat ulang bidang dari database.

Jika Anda ingin memodifikasi bidang pass di tempat, ingatlah untuk memuat ulang dari database. Jika tidak, Anda akan mengatur hubungan ke "tidak ada," secara efektif membuat bidang tidak berguna!

1
2
3
4
# selalu isi ulang lapangan sebelum modifikasi
Menunggu post2.category [0] .postkategory.load ()
# baru kemudian perbarui bidang
Tunggu post2.category [0] .postkategory.update (urut _ order = 3)

Perhatikan bahwa memuat ulang model sebenarnya memuat ulang hubungan sebagai model pk\ _ saja (hanya kunci utama yang diatur), sehingga tidak terisi penuh, tetapi cukup untuk mempertahankan hubungan saat diperbarui.

Peringatan!!!Jika Anda menggunakan ie fastapi, model terkait yang dimuat melalui bagian bidang dapat menyebabkan kesalahan validasi pydantic (yang merupakan alasan utama mengapa mereka tidak dihuni secara default). Jadi, Anda harus mengecualikan bidang yang relevan dari respons atau memuat model yang relevan sepenuhnya. Pada contoh di atas, ini berarti:python await post2.categories[0].postcategory.post.load() await post2.categories[0].postcategory.category.load() Atau, Anda dapat menggunakan load\ _ all ():python await post2.categories[0].postcategory.load_all()

Metode pembaruan yang disukai adalah melalui metode pembaruan proxy queryset ()

1
2
# filter model terkait yang diinginkan dengan melalui bidang dan perbarui hanya melalui bidang bidang
Tunggu post2.Category. Filter (nama = 'Kategori tes').update (postcategory ={"sort_order": 3})

Metode relasional

menambahkan

Tambahkan (item: model,**kwargs)

Memungkinkan Anda menambahkan model ke banyak-ke-banyak hubungan.

1
2
3
4
# Tambahkan kategori ke posting.
Menunggu post.kategory.add (berita)
# atau dari ujung yang lain:
Menunggu news.posts.add (posting)

!!!Peringatan: Dalam semua kasus non-Tidak ada, nilai kunci utama dari model yang relevan harus ada dalam database.

1
Jika tidak, IntegrityError akan dinaikkan oleh perpustakaan driver database Anda.

Jika Anda menyatakan model dengan model Melalui dengan bidang tambahan, Anda dapat mengisi model anak selama selain hubungan.

Untuk melakukan ini, berikan argumen kata kunci dengan nama bidang dan nilai ke panggilan tambahkan ().

Harap dicatat bahwa ini hanya berlaku untuk banyak-ke-banyak hubungan.

1
2
3
4
Post = await Post (judul = "Test post").save ()
Kategori = menunggu Kategori (nama = "Kategori tes").simpan ()
# selain dari model melewati argumen yang merujuk melalui bidang model
Tunggu post.kategory.add (kategori, urutkan _ order = 1, param _ name = 'tes')

menghilangkan

Model yang relevan dihapus satu per satu.

Juga hapus hubungan dalam database.

1
Menunggu postingan baru.hapus (posting)

bening

Satu panggilan menghapus semua model terkait.

Juga hapus hubungan dalam database.

Menunggu postingan berbaru. jelas ()

Agen Setelan Kueri

Hubungan terbalik memaparkan QuerysetProxy API, yang memungkinkan Anda untuk menanyakan model terkait seperti yang Anda lakukan pada pertanyaan normal.

Untuk mempelajari metode QuerySet mana yang tersedia, baca querysetproxy di bawah ini