コンテンツにスキップ

外部キー

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) ターゲットモデルクラスを採用するために必要なパラメーターがあります。

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
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)

逆の関係

外部キーフィールドは、リレーションシップの裏面を自動的に登録します。

デフォルトでは、次のコードスニペットのコースのように、サブ(ソース)モデル名+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
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())

リバースリレーションシップ公開APIは、親から関連するオブジェクトを管理します。

逆関係をスキップする

逆の関係を望まない場合は、外部キーのskip_verse=Trueフラグを使用できます。

内部的にskip_verseフラグを設定している場合は、リレーションシップの反対側にフィールドが登録されたままなので、次のことができます。

  • リバースモデルの関連するモデルフィールドによるフィルタリング
  • order_byリバースモデルの関連するモデルフィールド

しかし、あなたはできません:

  • 関連_nameを使用して、リバースモデルから関連フィールドにアクセスします
  • モデルの反対側から相関を選択しても、返されたモデルは逆インスタンスに入力されません(接続をブロックしないため、関係をフィルタリングしてソートできます)
  • この関係はmodel_dump()とmodel_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
class Author(ormar. Model):
    base_or_config. copy()

    id: int=ormar.Integer(プライマリキー=True)
    first_name: str=ormar.String(max_length=80)
    last_name: str=ormar.String(max_length=80)


class Post(ormar. Model):
    base_or_config. copy()

    id: int=ormar.Integer(プライマリキー=True)
    title: str=ormar.String(max_length=200)
    著者:オプション[著者]=ormar. ForeignKey(著者、skip_verse=True)

#サンプルデータの作成
Author=Author(first_name="Test", last_name="Author")
post=Post(title="テストポスト",著者=著者)

アサートpost.author==著者#ok
assert Author. post#属性エラー!

#order_byでも使えます
著者=(
    await Author. object.select_関連("post").order_by("post__title").all()
)
assert著者[0]. first_name=="テスト"

#明示的に投稿しても、投稿者には入力されないことに注意してください。
#select_関連に含まれる-model_dump()に投稿がないことに注意してください
assert Author. model_dump(除外={"id"})=={"first_name": "Test", "last_name": "Author"}

#関連するモデルのフィールドをフィルタリングすることができます。
Author=await Author. object.filter(post__title="テスト投稿").all()
assert著者[0]. first_name=="テスト"
assert len(著者)==1

追加する

親から子モデルを追加すると、関連するモデルが現在ロードされている親リレーションシップに追加され、子モデルの外部キー値が設定され、モデルが更新されます。

1
2
3
4
5
6
7
8
9
Department=await Department(name="Science"). save()
コース=コース(名前="数学"、完了=偽)#ノート-保存されていません

awaitdepartment.courses. add(コース)
assertcourse.pkはなし#子モデルが保存されました
#子モデルの関係を設定し、FK列をDBに保存
assertコース.部門==部門
#親モデル上の関係も設定
assertdepartment.courses[0]==コース

!!!警告関連するモデルに子モデルを追加する場合は、親モデルのプライマリキー値がデータベースに存在する必要があります。

1
2
3
4
それ以外の場合、ormarは子のForeignKeyカラム値を設定できないため、`RelationshipInstanceError` を上げます。
親モデルに主キー値がない場合。

つまり、上記の例では、`department.courses.add()` を呼び出す前に部門を保存する必要があります。

!!!警告このメソッドは多対多のリレーションシップには適用されません。この場合、リレーションシップに追加する前に、リレーションシップの両側を保存する必要があります。

排除する

関連するモデルを1つずつ削除します。

逆リレーションシップでは、remove()はサブモデルを削除せず、代わりにそのForeignKey値を空に設定します。

1
2
3
4
5
6
7
8
9
#上から続く
awaitdepartment.courses. remove(コース)
アサートの長さ(department.courses)==0
#コースはまだ存在し、削除で保存されましました。
アサートcourse.pkはなし
assertコース。部門はなし

#dbから子を削除する
コースを待ちます。delete()

しかし、リレーションシップをクリアし、同時にサブアイテムを削除したい場合は、次のように発行できます。

1
2
3
#これは関係を明確にするだけでなく
#しかし、DBから関連するコースも削除します。
awaitdepartment.courses. remove(もちろん、keep_反転=False)

クリア

関連するすべてのモデルを1回の呼び出しで削除します。

削除と同様に、デフォルトでは、clear()はサブモデルのForeigKeyカラムを空にします(ロードされているかどうかに関係なく、すべて)。

1
2
#この部門に関連するすべてのコースのnulls部門列
awaitdepartment.courses. clear()

サブアイテムをデータベースから完全に削除する場合は、keep_逆転=Falseを設定します

1
2
#この部門に関連するすべてのコースをDBから削除する
awaitdepartment.courses. clear(keep_反転=False)

クエリセットエージェント

リバースリレーションシップは、通常のクエリと同じように関連するモデルをクエリできるQuerysetProxy APIを公開します。

QuerySetで使用できるメソッドを確認するには、以下のquerysetproxyをお読みください

関連する名前

関連するモデルフィールド名は、次のように、関連する_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
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'))

!!!tipアクセス時の逆関係は、循環参照を避けるためにwekref. proxyリストを返します。

!!!は、同じモデルに複数のリレーションシップを提供しないと、関連する_nameを自動的に生成できなくなることを警告します。 したがって、この場合、1つのフィールド(1つはデフォルトで生成できます)を除くすべてのフィールド、または関連するすべてのフィールドに関連する_nameを指定する必要があります。

参照アクション

ForeignKeyが参照するオブジェクトが変更(削除または更新)されると、ormarはondeleteパラメーターとon updateパラメーターで指定されたSQL制約を設定します。

ondeleteとon updateの可能な値は、ormar. Reference Actionで見つけることができます。

!!!note ormar. Reference Actionではなく、両方の引数に文字列値を直接渡すことができますが、完全性を損なう可能性があるため、お勧めしません。

カスケード

親(参照)テーブルのローが削除(または更新)されるたびに、一致する外部キーカラムを持つ子(参照)テーブルの対応するローも削除(または更新)されます。これをカスケード削除(または更新)といいます。

制限する

参照テーブルまたはサブテーブルに、参照されているテーブルの値を参照するローがある場合、その値を更新または削除することはできません。

同様に、参照テーブルまたはサブテーブルにローへの参照がある限り、ローを削除することはできません。

設定_NULL

ForeignKeyをNoneに設定します。これは、nullableがTrueの場合にのみ可能です。

デフォルト値を設定する

ForeignKeyをデフォルトに設定します。ForeignKeyのserver_defaultを設定する必要があります。

!!!デフォルト値は許可されていないことに注意してください。これを行うには、このセクションで読むことができるserver_defaultを使用する必要があります。

何もしない

アクションなし;アクションなしと制限は非常に似ています。NO ACTIONとRESTRICTの主な違いは、NO ACTIONの場合、テーブルを変更しようとした後に参照整合性チェックが行われることです。RESTRICTは、UPDATE文またはDELETE文を実行しようとする前にチェックします。 参照整合性チェックが失敗した場合、両方の参照操作は同じように動作します。

関係の設定

さまざまな方法で関係を構築できます。

モデルインスタンス

最も明白なものは、関連する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
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)

プライマリキー値

関連するモデルのpkカラム値のみを使用してリレーションシップを設定することもできます。

 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)

辞書

次のオプションは、関連するモデルのキー値辞書を使用することです。

自分でディクショナリを構築するか、model_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)

何もない

最後に、明示的にNoneに設定できます(値が渡されない場合はデフォルトの動作)。

 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)

!!!警告None以外のすべての場合、関連するモデルのプライマリキー値はデータベースに存在する必要があります。

1
そうしないと、データベースドライバライブラリによってIntegrityErrorが発生します。