Django  Model操作（自分用メモ）

## Django自分用メモになります
今回はModelの操作についてまとめます


## 開発環境
OS:mac
エディタ：vscode
python:3.10.9
django:4.1.0

### モデルを作成する基本的な構文
```python:models.py
from django.db import models

class Person(models.Model):
    first_name=CharField(max_length=30)
    last_name=CharField(max_length=30)
```

### マイグレーションファイルの作成
マイグレートをする前にマイグレーションファイルを作る作業が必要
マイグレーションファイルとはmodel.pyに書かれた内容をDBに保存できる形に処理をされたファイルのこと
```:ターミナル
$ python manage.py makemigrations <アプリ名> --name <つけたい名前>
```
python manage.py makemigrationsだけでも可
マイグレーションに自動生成された名前ではなく、
migrationsフォルダに意味のある名前のフォルダを作りたければ
makemigrationsのあとに--nameとするといい

### マイグレートする

```:ターミナル
$ python manage.py migrate <アプリ名>
```


### VScodeでテーブルの中身を確認する方法

command+shift+p
sqlite open Datebaseと入力
みたいsqliteを選択するとvscode左下にSQLITE EXPLORERというタブがでるので
その上で右クリック後Show Table 'sqlite_master'を選択すると中身が見れる


### その他操作
```:ターミナル
$ python manage.py showmigrations <アプリ名>
    #適応されているマイグレーションファイルを見る
$ python manage.py migrate <アプリ名> <戻りたいマイグレーションファイル名>
    #マイグレーションファイルを指定したファイルまで適応されていない状態にする
$ python manage.py migrate <アプリ名> zero
    #全てのマイグレーションファイルの適応を外す
```
ファイルを適応されていない状態に戻したらmigrationsフォルダにある
適応しなかったファイルも消しておくこと


### 主なField一覧
| field名 | 概要 | 
|:-----------|:------------:|
| BooleanField    | 論理型、TrueとFalseを入れられる
| CharField     | 文字列型。VARCHARとしてカラムが作成される。max_lengthで長さを指定する   |
| DateField,DatetimeField       | 日付型、タイムスタンプ型        |
| EmailField         | メールアドレスを格納する。バリデーションあり。デフォルトでVARCHAR（254）         |
| FileField       |ファイルのアップロードの際のファイルパスを格納。デフォルトでVARCHAR(100)|
| DecimalField    |正確な数値を格納される|
|IntegerField,FloatField|整数型(INTEGER)、浮動小数点(FLOAT)を格納される|
|TextField|長い文字列を入れる。TEXT型としてカラムが作成される|
|URLField|URLを入れる。VARCHAR(200)としてカラムが作成される|


### model作成時に新しく学んだこと
```python:models.py
from django.db import models
from django.utils import timezone

class Person(models.Model):
    email = models.EmailField(db_index=True)#indexをはる　という高速化の処理
#全てにdb_index=Trueとするとメモリを喰うので検索したい項目にだけ設定すると⚪︎
    create_at =models.DateTimeField(default =timezone.datetime.now)
#デフォルト設定をすると登録時の時間を入れられる
```

### 作ったモデルを管理画面でみれるようにする
```python:admin.py
from django.contrib import admin
from .models import Person

admin.site.register(Person)
```

### Metaオプション
Metaはメタクラスのことでクラスの振る舞いを定義する
```python:models.py
from django.db import models
from django.utils import timezone
import pytz #timezoneを設定するモジュール

class BaseMeta(models.Model): #抽象クラス
    create_at = models.DateTimeField(default=timezone.datetime.now(pytz.timezone('Asia/Tokyo')))
    update_at = models.DateTimeField(default=timezone.datetime.now(pytz.timezone('Asia/Tokyo')))
    
    class Meta:
        abstract = True #! 1.抽象クラス(abstract)として定義するためメタクラスを設定する


class Person(BaseMeta):#! 2.共通のカラムを持ちたいとき抽象クラスを継承する
    first_name=models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    birthday = models.DateField(default='1900-01-01')
    email = models.EmailField(db_index=True)#!indexをはる　という高速化の処理
    salary = models.FloatField(null=True)
    memo = models.TextField()
    web_site = models.URLField(null=True,blank = True)
    # create_at =models.DateTimeField(default =timezone.now) #! 継承したカラムを使うためこちらは削除
    
    class Meta:
        db_table = 'person' #!テーブル名をpersonに設定
        index_together = [['first_name','last_name']] #!二つセットで検索する際に高速化できて便利
        ordering = ['salary'] #DBから取り出す際のデフォルトのorder「順番」を設定
        #降順にするときは['-salary']とする

    def __str__(self):
        return f'{self.first_name} {self.last_name}' #!データを取得した際にわかりやすくするために名前を設定
```
Metaオプションとしては他に
unique_together=[['first_name','last_name']]とすると
セットでUNIQUEとするフィールドの設定などもできる。

詳しくは[Django公式ページ](https://docs.djangoproject.com/ja/4.1/ref/models/options "Django公式ページ")



### DBにINSERTする方法３つ
```python:
#1. インスタンスのsaveメゾット（DBに同じものが存在しないときDBに挿入、存在する場合は更新
web_site=Website(url='www.sample.com',name='sample')
web_site.save() #Websiteというクラスで作られたweb_siteインスタンスをsaveする

#2. クラスメゾットのcreateメゾット(DBに同じものが存在しないときDBに挿入、存在する場合はエラー
Website.objects.create(
    url = 'www.sample.com', name = 'sample'
) #存在した場合IntegrityError発生

#3. get_or_createメゾット(DBに同じものが存在しない場合作成して返す。存在する場合はそのものを返す
obj,created = Website.objects.get_or_create(url='www.sample.com',name='sample')
#2つ変数が返ってくる。objには作成したデータを。createdには存在しなくて作られた場合はTrueがはいり、
#作成しなかったときはFalseがはいる
```

### 実際にDBにINSERTしてみる
manage.pyと同じ階層にmain.pyファイルを作成
```python:main.py
#! djangoの設定を読み込んでDBに保存する
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()


from ModelApp.models import Person

#インスタンスを作成
p = Person(
    first_name = 'Taro',
    last_name = 'Sato',
    birthday = '2000-01-01',
    email = 'aa@example.com',
    salary=None,
    memo='memo taro',
    web_site = 'https://www.com',
)
#インスタンスをsave
p.save()

#classmethod create
Person.objects.create(
    first_name = 'Jiro',
    last_name = 'Ito',
    email = 'bb@example.com',
    salary = 2000,
    memo='class method実行',
    web_site=None,
)

#get_or_create(取得or作成)
obj,created = Person.objects.get_or_create(
    first_name = 'Saburo',
    last_name = 'Ito',
    #birthdayはdefaultを設定しているため記述しない場合デフォルト値がはいる
    email = 'bb@example.com',
    salary = 2000,
    memo='class method実行',
    web_site=None,
)

print(obj) #models.pyで設定したdef __str__(self)の名前で返る
print(created) #作られた場合はTrue、すでに存在している場合はFalse
```
ファイル作成後、main.pyを右クリック→ターミナルで実行をするとDBに保存されている

※DBにおけるNullと空白は別物ということを覚えておく


### DBからSELECTする方法３つ（取得）
manage.pyと同じ階層にデータを取得するためのファイルselect_sample.pyファイルを作成
```python:select_sample.py
#データの取得
#Djangoの設定を読み込む
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()


from ModelApp.models import Person

#全て取得
persons = Person.objects.all()
for person in persons:
    print(person.id,person,person.salary)
    
#１件だけ取得
# person = Person.objects.get(first_name='Taro') #複数取得できるからエラーに
# person = Person.objects.get(first_name='taro') #１件も獲得できない時もエラーに
person = Person.objects.get(pk=1) #主キーは必ず１件なので取得できる
print(person.id,person)

#filter(絞り込み、エラーにならない、複数取得可)
persons = Person.objects.filter(first_name='Taro').all()

print(persons[0].email) #配列なのでこの形で取り出せる
for person in persons:
    print(person.id , person) #for文でも取り出せる
```

### 参考となるQiita(Queryset集)
[Django逆引きチートシート（QuerySet編）
](https://qiita.com/uenosy/items/54136aff0f6373957d22)
そもそもobjectsはモデルマネージャ。.all()や.first()はメゾットであることを知ってた？
下記が参考になる[Django objectsの使い方を簡単解説](https://se-memorandum.com/django-objects/)


### DBからの取り出し（get_object_or_404 get_list_or_404）
```python:
from django.shortcuts import get_object_or_404
get_object_or_404
#指定したモデルを呼び出し、getを行う。値を取得できなかった場合raise Http404を送出する
#例)item = get_object_or_404(Items,pk=id)

get_list_or_404
#指定したモデルを呼び出し、filterを行う。同上
#リストで取り出すためリストの中身がない状態で取り出すと４０４を返す
#例)items = get_list_or_404(Items,id__gt=2) idが２より大きいものを取り出す
```
[Django Viewの応用](https://qiita.com/Ryo_Kitamoto/items/1e13115eabeb8a3a33b0)
こちらでエラーハンドリングについてもまとめた

### データの更新(update)
manage.pyと同じ階層にデータを更新するためのファイルupdate_sample.pyファイルを作成

```python:update_sample.py
#データの更新(update)
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Person
from django.utils import timezone
import pytz

#1件だけ取得してbirthdayとupdate_atを更新する
person = Person.objects.get(id=1)
person.birthday='2001-09-01'
person.update_at=timezone.datetime.now(pytz.timezone('Asia/Tokyo'))
person.save() #saveで更新完了

#first_nameが'Taro'であるデータを全て更新する
persons = Person.objects.filter(first_name='Taro').all()
for person in persons:
    person.first_name = person.first_name.lower()
    person.update_at = timezone.datetime.now(pytz.timezone('Asia/Tokyo'))
    person.save()
#上記のやり方は一つ一つクエリを作成するため、何万件ともなると処理が遅くなってしまう

#下記はupdateで一気に実行するためこちらのほうが処理が軽くなりおすすめ
Person.objects.filter(first_name='Saburo').update(
    web_site='http://sample.jp',
    update_at = timezone.datetime.now(pytz.timezone('Asia/Tokyo'))
)
```

### データの削除(delete)
manage.pyと同じ階層にデータを削除するためのファイルdelete_sample.pyファイルを作成

```python:delete_sample.py
#データの削除(delete)
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Person

#filterを使って条件にあったデータを削除する
Person.objects.filter(first_name='Saburo').delete()
Person.objects.filter(first_name = 'taro',birthday='2001-09-01').delete()

#全件削除
Person.objects.all().delete()
```

### modelに外部キーを持たせる
テーブルに外部キー(ForeignField)を持たせることによってテーブル間の紐付けを行う
```Python:models.py
#外部キー練習
class Students(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    major = models.CharField(max_length=20)
    school = models.ForeignKey(
        'Schools', on_delete=models.CASCADE
    )
    
    class Meta:
        db_table = 'students'
        
class Schools(models.Model):
    name = models.CharField(max_length=20)
    prefecture = models.ForeignKey(
        'Prefectures',on_delete=models.CASCADE
    )
    
    class Meta:
        db_table = 'schools'
        
class Prefectures(models.Model):
    name=models.CharField(max_length=20)
    
    class Meta:
        db_table='prefectures'
```
StudentテーブルはShcoolsテーブルと１対多の関係になり
SchooolsテーブルとPrefectureテーブルも1対多の関係になっている

### on_delete=models.CASCADEを体感する
manage.pyと同じ階層にforeignkey_sample.pyファイルを作成

```python:foreignkey_sample.py
#外部キーの練習
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Students,Schools,Prefectures

prefectures = ['東京','大阪']
schools = ['東高校','西高校','北高校','南高校']
students = ['太郎','二郎','三郎']

#データの用意
def insert_records():
    for prefecture_name in prefectures:
        prefecture = Prefectures(
            name = prefecture_name
        )
        prefecture.save()
        for school_name in schools:
            school = Schools(
                name = school_name,
                prefecture = prefecture
                #左のprefectureはmodels.pyで定義した外部キーのprefecture
                #右のprefectureはfor内で作成したインスタンスのprefecture
                #こうすることで紐付けされる
            )
            school.save()
            for student_name in students:
                student = Students(
                    name = student_name,age=17,
                    major = '物理',school = school
                )
                student.save()
#データを一旦保存させてコメントアウト              
# insert_records()

#データを表示するための関数
def select_students():
    students = Students.objects.all()
    for student in students:
        print(student.id,student.name,student.age,student.major,student.school.id,student.school.name,student.school.prefecture.id,student.school.prefecture.name)
#student.school.nameやstudent.school.prefeture.nameのように外部キーを伝って紐付けされたテーブルのデータににアクセスすることが可能
        
# select_students()

#東高校を削除すると同時にstudentが3人削除される
Schools.objects.filter(id=1).delete()
# select_students()
#prefectureの'東京'を削除すると東京を持つデータがすべて削除される
Prefectures.objects.filter(name='東京').delete()
select_students()
```
### on_delete=models.CASCADE以外のオプション
```python:models.py
school = models.ForeignKey(
        #親を削除しようとするとエラーがでる
        'Schools', on_delete=models.PROTECT
        #親を参照している項目はすべてNullをいれる
        'Schools', on_delete=models.SET_NULL,null=True
)
```
### on_delete=models.CASCADE以外のオプション2 RESTRICT
Schoolsを削除したときはStudentsのRESTRICTによって保護され、
Prefectureを削除したときはCASCADEで紐付けしたために下位が全て削除される。

参照先(親)が参照先(祖父母)にCASCADEで紐付けされていれば、
祖父母を削除した時に親も子も一緒に削除というイメージ

```python:models.py
#外部キー練習
class Students(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    major = models.CharField(max_length=20)
    #ここからポイント
    school = models.ForeignKey(
        'Schools', on_delete=models.RESTRICT #
    )
    prefecture = models.ForeignKey(
        'Prefectures',on_delete=models.CASCADE #
    )
    class Meta:
        db_table = 'students'
        
class Schools(models.Model):
    name = models.CharField(max_length=20)
    prefecture = models.ForeignKey(
        'Prefectures',on_delete=models.CASCADE #
    )
    class Meta:
        db_table = 'schools'
        
class Prefectures(models.Model):
    name=models.CharField(max_length=20)
    class Meta:
        db_table='prefectures'
```
```python:foreignkey_sample.py
def insert_records():
    for prefecture_name in prefectures:
        prefecture = Prefectures(
            name = prefecture_name
        )
        prefecture.save()
        for school_name in schools:
            school = Schools(
                name = school_name,
                prefecture = prefecture
                #左のprefectureはmodels.pyで定義した外部キーのprefecture
                #右のprefectureはfor内で作成したインスタンスのprefecture
                #こうすることで紐付けされる
            )
            school.save()
            for student_name in students:
                student = Students(
                    name = student_name,age=17,
                    major = '物理',school = school,
                    #RESTRICTの場合のみ追加 
                    prefecture = prefecture, #idを持たせるためこちらを追加
                )
                student.save()



```
### OneToOneFieldは１対１でテーブルを紐づける
```python:models.py
#OneToOne
class Places(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)
    
    class Meta:
        db_table = 'places'
        
class Restaurant(models.Model):
    place = models.OneToOneField( #
        Places,
        on_delete=models.CASCADE,
        primary_key=True #
    )
    name = models.CharField(max_length=50)
    
    class Meta:
        db_table = 'restaurants'
```
manage.pyと同じ階層にonetoone_sample.pyファイルを作成
```python:onetoone_sample.py
#OneToOne
#import等省略。上記のファイルと同じ

places = [
    ('Motomachi','Yokohama'),('Tsukiji','Tokyo')
]
restaurants=['restaurantA','restaurantB']

for place_name,place_address in places:
    p = Places(name=place_name, address=place_address)
    p.save()
    for restaurant_name in restaurants:
        r = Restaurant(place=p, name=restaurant_name)
        r.save()
        #>>> 結果はrestaurantsテーブルにはplace_id1,2とrestaurantBの２つのデータしか入らない
        #インスタンスのsaveメゾットは
        #DBに同じものが存在しないときはDBに保存、存在する場合は更新なので今回はplaceが
        #primarykeyで同じものなのでnameがrestaurantBに更新される
    
        Restaurant.objects.create(
            place =p,name=restaurant_name
        )
        #クラスメゾットのcreateメゾットを使う場合
        #こちらもDBにすでに存在する場合はエラーを返すので保存はできない
```

OneToOneは１対１でテーブル間を紐づけるフィールドなので一つの他テーブルidに対して
一つのデータを持つ。

### ManyToMany
多対多の関係のテーブルを紐づけるフィールド
内部的に２つのテーブル間にもう１つ新たなテーブルが作成され、そのテーブルを使って紐づける
```python:models.py
#ManyToMany
class Authors(models.Model):
    name = models.CharField(max_length=50)
    
    def __str__(self):
        return self.name
    
    class Meta:
        db_table = 'authors'
        
class Books(models.Model):
    name = models.CharField(max_length=50)
    authors = models.ManyToManyField(Authors) #
    
    def __str__(self):
        return self.name
    
    class Meta:
        db_table = 'books'
```
manage.pyと同じ階層にmanytomany_sample.pyファイルを作成

```python:manytomany.py
#manytomany
#import等省略

#データの挿入
def insert_books():
    book1 = Books(name='Book1')
    book2 = Books(name='Book2')
    book3 = Books(name='Book3')
    book1.save()
    book2.save()
    book3.save()
    
def insert_authors():
    author1 = Authors(name='Author1')
    author2 = Authors(name='Author2')
    author3 = Authors(name='Author3')
    author1.save()
    author2.save()
    author3.save()

#関数の実行
insert_books()
insert_authors()

# データを取り出して
book1 = Books.objects.get(pk=1)
book3 = Books.objects.get(pk=3)
author1 = Authors.objects.get(pk=1)
author2 = Authors.objects.get(pk=2)
author3 = Authors.objects.get(pk=3)

#book1にaddで追加する
book1.authors.add(author1,author2)
print(book1.authors.all())

book3.authors.add(author1,author2,author3)
print(book3.authors.all())

print(book1.authors)
```
addメゾットで追加でき、saveはいらない
authorsは複数のbooksをもち
booksも複数のauthorsをもつという関係になる

### 結合したレコードからデータを取り出す
１を相手にするのか多を相手にするのかで取り出し方が異なる
manage.pyと同じ階層にselect_foreign_table.pyファイルを作成

```python:select_foreign_table.py
#! 結合したデータの取り出し
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Students,Schools,Prefectures
from ModelApp.models import Places,Restaurant
from ModelApp.models import Authors,Books


s=Schools.objects.first()
print(type(s))
print(dir(s)) #このオブジェクトが持つ使える属性を返す中にprefectureとstudents_setが存在することがわかる
# SchoolsのオブジェクトはStudentsに対しては１対多、Prefectureに対しては多対１であるため
print(s.prefecture.name)

print('*'*100)
st = s.students_set #多を取り出すには_set
print(type(st))
print(dir(st))
print(st.all())
st = st.all() #allで取得する
for s in st:
    print(s.school.prefecture.name) #大阪　を取り出せる
    
#OnetoOneからデータを取り出す
print('********onetoone*********')
p = Places.objects.first()
print(f'レストランの名前は:{p.restaurant.name}')
#逆も同じように取り出せる
r = Restaurant.objects.first()
print(f'場所は{r.place.address}の{r.place.name}')


#ManyToManyからデータを取り出す
print('***********manytomany****************')
b = Books.objects.first()
print(type(b))
print(dir(b))
print(b.authors.all())
bs = b.authors.all()
for b in bs:
    print(b.name) #Author1,Author2が表示される

a= Authors.objects.first()
print(a.books_set.all())
#models.pyのAuthorsクラスはBooksと紐づけるプロパティを持っていないのでAuthorsとBooksが１対多のような
# 関係になるからこちらは_setを使う？

a_s = a.books_set.all()
for a in a_s:
    print(a.name) # Book1,Book3が表示される

```
### 条件を指定してデータを取得
manage.pyと同じ階層にquery_01.py~query_04.pyファイルを作成
以下はすべてデータを取り出す練習

```python:query_01.py
#! データを絞り込んで取得
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Students

#全件取得
print(Students.objects.all())

#頭五件取得
print(Students.objects.all()[:5])

#5件目よりあと
print(Students.objects.all()[5:])

#5~7件目
print(Students.objects.all()[:8])
print(Students.objects.all()[5:8].query) #実際に発行しているqueryを見ることができる

#一番最初の一件
print(Students.objects.first())

#等価のものだけに絞り込む
print(Students.objects.filter(name='太郎').all())
print(Students.objects.filter(age=17).all())

#AND条件
print(Students.objects.filter(name='太郎',pk=13).all().query)
print(Students.objects.filter(name='太郎',pk__gte=13).all().query)
print(Students.objects.filter(name='太郎',pk__lte=17).all())
print(Students.objects.filter(name='太郎',pk__gte=13,pk__lte=20).all().query)

#前方一致、後方一致
print(Students.objects.all())
print(Students.objects.filter(name__startswith='太').all())
print(Students.objects.filter(name__endswith='郎').all())

#or
from django.db.models import Q
print(Students.objects.filter(Q(name='太郎')|Q(pk__gt=19)).all())
```
__gteとはgreater than equalの略
__lteとはless than equealの略

```python:query_02.py
##! データを絞り込んで取得　その２
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Students,Person

#IN
ids = [13,14,15]
print(Students.objects.filter(pk__in=ids).all())
print(Students.objects.filter(pk__in=[15,16,17]).all())
print(Students.objects.filter(name__in=['二郎','三']).all()) #二郎は取り出せるが三では三郎は取り出せない

# contain 部分一致
print(Students.objects.filter(name__contains='三').all())

#is null
print(Person.objects.filter(salary__isnull=True).all())

#レコードを取り除くexclude(filterの逆を取り出す)
print(Person.objects.exclude(salary__isnull=True).all())
print(Students.objects.exclude(name='太郎').all())

#カラムを取り出す
print(Students.objects.values('id','name','age').all().query)
students = Students.objects.values('id','name','age').all()
for student in students:
    print('id:{},名前:{}'.format(student['id'],student['name']))
    
#並び替え
print(Students.objects.order_by('name','-id').all()) #第一条件nameを昇順、第二条件idを降順に並び替える
#filterとorder_byを組み合わせることも可
print(Students.objects.filter(name='太郎').order_by('-id').all())
```
```python:query_03.py
##! データを絞り込んで取得　その3
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Students

# #件数
print(Students.objects.count()) #12
print(Students.objects.filter(name='太郎').count()) #4
print(Students.objects.filter(name='Taro').count()) #0

# #件数、最大値、最小値、平均値、合計
from django.db.models import Count,Max,Min,Avg,Sum
print(Students.objects.aggregate(Count('pk'),Max('pk'),Min('pk'),Avg('pk'),Sum('age')))
aggregate_student = Students.objects.aggregate(Count('pk'),Max('pk'),Min('pk'),Avg('pk'),Sum('age'))
#{'pk__count':12,'pk__max':24}といったように辞書型で返されるため下記のような記述で取り出せる
print(aggregate_student['pk__max'])
#別名をつけることもできる
print(Students.objects.aggregate(counted_pk= Count('pk'),max_pk=Max('pk'),min_pk=Min('pk'),avg_pk=Avg('pk'),sum_pk=Sum('age')))

#GROUP BY:ある特定のカラムで集計して、合計、最大などを求める
print(Students.objects.values('name').annotate(
    Max('pk'),Min('pk')
).query) #queryを見る
print(Students.objects.values('name','age').annotate(
    max_id = Max('pk'),min_id = Min('pk')#キーをmax_idと　min_idに変更している
)) 
#辞書型が配列の中にはいって渡されるのでfor文で一つ一つ取り出してキーを指定して取り出すことができる
for student in Students.objects.values('name','age').annotate(
    max_id = Max('pk'),min_id = Min('pk')
):
    print(student['name'],student['max_id'])
```

```python:query_04.py
#! データを絞り込んで取得　その4 外部テーブルをつかった絞り込み
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelProject.settings')
from django import setup
setup()

from ModelApp.models import Students,Schools

# for student in Students.objects.all():
#     print(student.name,student.school.name,student.school.prefecture.name)

#外部テーブルを使ったfilterができる(外部テーブル名__カラムとする)
for student in Students.objects.filter(school__name='南高校').all():
    print(student.name,student.school.name,student.school.prefecture.name)

#excludeもできる
for student in Students.objects.exclude(school__name='南高校').all():
    print(student.name,student.school.name,student.school.prefecture.name)
    
#schoolsテーブルからstudentsを取り出す
print(Schools.objects.filter(students__name='太郎').all())
for school in Schools.objects.filter(students__name='太郎').all():
    print(student.name,school.name,school.prefecture.name)
    
print('-'*100)
#順番を並び替える(order_by)
for student in Students.objects.order_by('-school__name').all():
    print(student.name,student.school.name)
print('-'*100)
for student in Students.objects.filter(school__name='西高校').order_by('-school__name').all():
    print(student.name,student.school.name)
print('*'*100)
#リストをつくってinで条件指定すると複数のvalueから一括取得できる（自作）
school_names=['南高校','西高校']
for student in Students.objects.filter(school__name__in=school_names).order_by('school__name').all():
    print(student.name,student.school.name)
    
#外部テーブルを使ったGROUP BY
from django.db.models import Count,Max
print(Students.objects.values('school__name').annotate(Count('id'),Max('id')))
#studentからSchoolのnameごとに数をカウントして返す
```

### 演習問題
![20230728-121553.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/627182/7d1ab49a-29c1-467d-48a7-a453378aa0a6.png)

手順
1.プロジェクトとアプリをつくる。マイグレートまで済ませる。
2.models.pyにモデルを作成する
3.makemigration、migrateをしてテーブルを作成。
4.データを格納する
5.テーブルからデータを取り出す

1は省略。

2から記述していく
```python:models.py
from django.db import models

class Tests(models.Model):
    name = models.CharField(max_length=50)
    
    class Meta:
        db_table = 'tests'
        
class Test_Results(models.Model):
    test=models.ForeignKey(
        'Tests',on_delete=models.CASCADE
    )
    student = models.ForeignKey(
        'Students',on_delete=models.CASCADE
    )
    score = models.IntegerField()
    
    class Meta:
        db_table = 'test_results'
        

class Students(models.Model):
    name =models.CharField(max_length=50)
    grade = models.IntegerField()
    classes = models.ForeignKey(
        'Classes',on_delete=models.CASCADE
    )
    
    class Meta:
        db_table = 'students'
        
class Classes(models.Model):
    name = models.CharField(max_length=50)
    
    class Meta:
        db_table = 'classes'
```
３も省略
4.データを格納する
```python:main.py
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelExam.settings')
from django import setup
setup()

from ModelApp.models import Tests,Test_Results,Students,Classes
import random

#classesにデータをいれる
# classes_list=['classA','classB','classC','classD','classE','classF','classG','classH','classI','classJ']
classes_list = ['class'+ c for c in 'ABCDEFGHIJ']
# students_list=['studentA','studentB','studentC','studentD','studentE','stundenF','studentG','studentH','studentI','studentJ']
students_list=['student' + c for c in 'ABCDEFGHIJ']
# print(classes_list)
tests_list=['数学','英語','国語']


#testのインスタンスを作ってリストに格納する
tests_instance_list = []
for test_name in tests_list:
    test_instance = Tests(
        name = test_name
    )
    test_instance.save()
    tests_instance_list.append(test_instance)

#データを格納する関数 
def insert_records():
    for classes_name in classes_list: 
        classes = Classes(
            name=classes_name
        )
        classes.save()
        
        for students in students_list:
            #studentインスタンスを作る
            name = classes_name +' '+students
            student = Students(
                name = name,
                grade = 1,
                classes = classes
            )
            student.save()
            for test_name in tests_instance_list:
                test_results = Test_Results(
                    test = test_name,
                    student = student,
                    score = random.randint(50,100)
                    )
                test_results.save()
    
insert_records()
```
5.テーブルからデータを取り出す
```python:show_result.py
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','ModelExam.settings')
from django import setup
setup()

from ModelApp.models import Tests,Test_Results,Students,Classes
from django.db.models import Sum,Avg,Max,Min

print('idが１のstudentの名前とテスト科目と点数を表示する')
for test_result in Test_Results.objects.filter(student__id = 1).all():
    print(
        '生徒名:{}\
        テストの科目:{}\
        点数:{}'.format(test_result.student.name,test_result.test.name,test_result.score)
    )
    
print('クラスごとの各科目のテストの合計、平均、最大、最小を表示する')
# s=Test_Results.objects.values('student__classes__name','test__name').annotate(
#     Max('score'),Min('score'),Sum('score'),Avg('score')
# )
# print(s)
for test_result in Test_Results.objects.values('student__classes__name','test__name').annotate(
    Max('score'),Min('score'),Sum('score'),Avg('score')
):
    print(
        'クラス:{}\
        科目:{}\
        最大値:{}\
        最小値:{}\
        平均値:{}\
        合計:{}'.format(test_result['student__classes__name'],test_result['test__name'],test_result['score__max'],test_result['score__min'],test_result['score__avg'],test_result['score__sum']))
    
print('idが１のstudentの名前とテスト科目と点数を表示する2')
student = Students.objects.get(id = 1) #idも可
# student = Students.objects.get(pk = 1) #pkも可
# student = Students.objects.filter(id = 1) #? こちらではtest_result_setが使えなかったのはなぜ？
#!filterはクエリセットを返し、getはオブジェクトを返す
print(student)
for test_result in student.test_results_set.all():
    print(
        '生徒名:{}\
        テストの科目:{}\
        点数:{}'.format(test_result.student.name,test_result.test.name,test_result.score)
    )
    
print('クエリをみてみる')
print(Test_Results.objects.values('student__classes__name','test__name').annotate(
    Max('score'),Min('score'),Sum('score'),Avg('score')
).query)
```

filterとgetの違いは
filterはクエリセットを返し
getはオブジェクトを返す。


以上、modelに関して新しい学びがあれば追記していく