Python ORM: Peewee
Tags: orm, database, python, programming
Published: 2023-02-04 21:17:55

> **Not:** SQLAlchemy tabanlı [SQLModel](https://sqlmodel.tiangolo.com/)'ı incelemek isteyebilirsiniz.

> Resmi belge için: [Peewee Docs](http://docs.peewee-orm.com/en/latest/index.html)

> Aşağıdaki kavramları/teknolojileri *en azından* **temel seviyede** bildiğinizi varsayıyorum:

> * Komut Satırı (basit dosya/dizin işlemleri seviyesinde)
* [Python Programlama Dili](https://python-istihza.yazbel.com/)
* [Nesne Tabanlı Programlama](https://www.w3schools.com/python/python_classes.asp) (class, instance ve object seviyesinde)
* [İlişkisel Veritabanı](https://sqlmodel.tiangolo.com/databases/) (anlamanızı kolaylaşırır)

---

Bu yazıda, dilim döndüğünce, bilgim ölçüsünde Python için **kolay** ama **etkili** bir [ORM](https://www.kadinyazilimci.com/ormobject-relational-mapping-nedir/) olan Peewee'ye hızlı bir giriş yapıyoruz.

Yazı boyunca, **kitaplık veritabanı** ([Github](https://github.com/abdullahemrgl/kitaplik)) oluşturarak Peewee'nin temel özelliklerini bu örnek üzerinden görmüş olacağız, umarım faydalı olur.

İçerik:
* [Neden Peewee?](#neden-peewee)
* [Senaryo](#senaryo)
* [Hazırlık](#hazirlik)
* [Kurulum](#kurulum)
* [Veritabanı Tanımlama](#veritabani-tanimlama)
* [Tabloları Modelleme](#tablolari-modelleme)
* [CRUD İşlemleri](#crud-islemleri)
* [JOIN İşlemleri](#join-islemleri)

# NEDEN PEEWEE?

***Kolay;*** öğrenme eğrisi düşük.
***Yeterli konsept;*** sahip olduğu konseptler, muhtemelen işinizi görecek kadar yeterli.
***Başlangıç için ideal;*** ORM mantığını kavramak ve kısa zaman içerisinde elle tutulur çalışmalar ortaya çıkarmak mümkün. Bu, motivasyonu arttırabilir.
***Bilinir;*** Python camiasında bilinen bir ORM, yani bir şekilde destek bulursunuz. (Fakat popüler değil)
***Türkçe kaynak katkısı*** yapmayı amaçladım.

# SENARYO

Modellerimiz (Veritabanı Tablolarımız):
* Kitap
* Okuyucu
* OkunanKitap
* Yazar
* KitapDetay

Ulaşmak istediğimiz bazı veriler:
* Kitap listesi.
* Kitabı okuyanlar kimler?
* Okuyucu listesi ve her birinin kaç kitap okuduğu.
* Okuyucu hangi kitapları okudu?
* Kitap bilgileri.

---

# HAZIRLIK

> **Hatırlatma!** 
> Windows kullanıcıları `python3` yerine, `py` kullanmalıdır.

***Komut satırını başlatıyoruz...***

`kitaplik` isminde bir **dizin/klasör** oluşturup, geçiş yapalım:
```
mkdir kitaplik
cd kitaplik
```

# KURULUM

Python Paket Yöneticisi (PIP) ile kurmak için:
```
pip3 install peewee
```

## Sanal Ortama Kurulum | [Bu adımı geç](#veritabani-tanimlama)

> **Önemli!** Bazı GNU/Linux dağıtımlarında `python3-venv` tarzı bir paket kurmak gerekebiliyor.

1) Sanal ortamı oluşturalım:
```
python3 -m venv env
```
> **Hatırlatma!** 
> "env" yerine istediğiniz bir ismi de yazabilirsiniz (ör: sanal_peewe.) Bir sonraki adımda **env** yerine **belirlediğiniz ismi** yazmanız gerekir.

2) Sanal ortamı aktif hale getirelim:
```
source env/bin/activate
```
> **Hatırlatma!** Eğer Windows kullanıyorsanız şu komutu vermelisiniz:
> ```
env\Scripts\activate.bat
```

3) Paket yöneticimizi (PIP) güncelleyelim:
```
python3 -m pip install --upgrade pip
```

4) Ve kurulum komutumuzu verelim:
```
python3 -m pip install peewee
```
Böylece *kitaplik* klasörümüze sanal ortamımızı, sanal ortamımıza da Peewee'yi kurmuş olduk.

5) Kontrol edelim:
![GNU/Linux üzerinde komut satırı](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/410teefqutqzp103ibe0.png)

---

# VERİTABANI TANIMLAMA <a name="veritabani-tanimlama"></a>

***Kodlamaya başlıyoruz...***

0) [VS Code](https://code.visualstudio.com/) kullanıyorsak editörümüzü, *"kitaplik"* dizinimiz üzerinde başlatalım:
```
code .
```

1) `models.py` isminde bir dosya oluşturalım ve açalım.

2) `peewee` modülünü `pw` olarak kullanmak üzere `models.py` dosyamıza tanımlayalım/aktaralım:
```
import peewee as pw
```

3) Veritabanımızı tanımlayalım:
```
import peewee as pw

db = pw.SqliteDatabase("kitaplik.db")
```
> **Ek bilgi**
> Veritabanını **açmak** için `db.connect()`, **kapatmak** için `db.close()` kullanabiliriz.

---

# TABLOLARI MODELLEME

Peewee'de [ilişkisel veritabanı](https://www.oracle.com/tr/database/what-is-a-relational-database/)na karşılık gelen kavramlar şu şekildedir:

Kavram                             | Karşılığı
-----------------------------------|-------------------------
Model Sınıfı (Model Class)         | Veritabanı Tablosu
Kolon/Alan Örneği (Field Instance) | Veritabanı Tablo Kolonu
Model Örneği (Model Instance)      | Veritabanı Tablo Satırı

Şimdi, yukarıdaki bilgileri senaryomuz üzerinde görelim.

İlişkisel veritabanında *"kitap"*, *"okuyucu"* ve *"okunan_kitap"* isimli tablolar oluşturmanın Peewee'deki karşılığı şu şekilde olur:

1) Tablomuzu `peewee.Model` sınıfından türeterek, sınıf olarak tanımlarız:
```
class Kitap(pw.Model):
```
> **Ek bilgi**
> Şu güncellemeyi incelemek isteyebilirsiniz: [3.15.4](https://github.com/coleifer/peewee/releases/tag/3.15.4)

2) Kolonlarımızı/Alanlarımızı `peewee.NameField` sınıflarını kullanarak tanımlarız:
```
class Kitap(pw.Model):
    isim = pw.CharField(max_length=250, null=False, unique=True)
```
> Yukarıda `isim` kolonu/alanı;
> * En fazla 250 karakter olabilir.
> * Boş olamaz.
> * Tekrar edemez (benzersiz olmalıdır.)

3) Modelimizin **hangi veritabanını** kullanacağını ve diğer ayarları, alt bir `Meta` sınıfı yazarak tanımlarız:
```
class Kitap(pw.Model):
    isim = pw.CharField(max_length=250, null=False, unique=True)
    
    Class Meta:
        database = db
        legacy_table_names = False
```
> Yukarıda `Kitap` modelimiz;
> * `id` isminde ve her kayıtta değeri otomatik olarak **1 er artacak** bir alan/kolon oluşacak:
>
> > `id = pw.IntegerField(primary_key=True)`
>
> * Veritabanı olarak -daha önce tanımladığımız- `db` değişkenini kullanacak.
> * Veritabanında tablo ismi, `kucuk_isim` biçiminde oluşacak.

4) Şimdi `Okuyucu` modelimizi tanımlayabiliriz.

Ama öncesinde `models.py` dosyamızın baş tarafına `Modelimiz` isminde şöyle bir sınıf tanımlayalım:
```
import peewee as pw

db = pw.SqlDatabase("kitaplik.db")

class Modelimiz(pw.Model):
    class Meta:
        database = db
        legacy_table_names = False
```
Kaldığımız yerden devam edelim ve `Okuyucu` modelimizi, `Modelimiz` sınıfından türeterek tanımlayalım:
```
class Okuyucu(Modelimiz):
    isim = pw.CharField(max_length=250, null=False, unique=True)
```
`Okuyucu` modelimiz ile, `Kitap` modelimiz arasındaki ilişkiyi de tanımlayalım:
```
class Okuyucu(Modelimiz):
    isim = pw.CharField(max_length=250, null=False, unique=True)
    kitaplar = pw.ManyToManyField(Kitap, backref="okuyucular")

OkunanKitap = Okuyucu.kitaplar.get_through_model()
```
> Yukarıda;
> * `Meta` sınıfı yazmamıza gerek kalmadı. Çünkü `Modelimiz` sınıfından türeterek tanımladık.
* `peewee.ManyToManyField` kullanarak `Okuyucu` ve `Kitap` arasında **çoka-çok ilişki** tanımladık. `backref` parametresini kullanarak; kitap üzerinden okuyuculara ulaşabilmeyi sağlayan bir **anahtar ifade** tanımlamış olduk.
* `OkunanKitap`, iki model arasındaki ilişki için gereklidir.

### *Çoka-çok İlişki?*
Çoka-çok ilişki bize şunu söyler; *okuyucu*, **birden fazla kitabı** okumuş olabilir.

Aynı şekilde *kitabı*, **birden fazla okuyucu** okumuş olabilir.

> **Ek bilgi**
> Eğer elimizde *okuyucu* varsa, **okuduğu kitaplara** şu şekilde ulaşırız:
> ```
okuyucu.kitaplar
```

> Eğer elimizde *kitap* varsa, **kitabı okuyanlara** şu şekilde ulaşırız:
> ```
kitap.okuyucular
```

5) Son olarak *modellerimizi veritabanına aktarabilmek* için, `models.py` dosyamıza `db.create_tables([])` eklememiz gerekiyor:
```
db.create_tables([
    Kitap,
    Okuyucu,
    OkunanKitap
])
```

`models.py` dosyamızın son hali:

![models.py modülü](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qgitqt28wkr8e96xbc72.png)

Şimdi **komut satırımıza** dönelim ve modellerimizi veritabanına aktaralım:
```
python3 models.py
```

---

# CRUD İŞLEMLERİ <a name="crud-islemleri"></a>

### *CRUD?*
* **C**reate - Oluştur/Ekle
* **R**ead - Oku/Seç/Listele
* **U**pdate - Güncelle
* **D**elete - Sil

## Hazırlık

`crud.py` isminde yeni bir dosya oluşturalım ve içerisine şu şekilde bir yapı tanımlayalım:

![crud.py modülü (şablon)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gbcyoqz9wd4nfup8nzw.png)

> Yukarıda;
> Sadece istediğimiz **kod bloğunu** çalıştırabilmek için, dosyamızı **fonksiyon parçalarına** böldük.   

## Kayıt Ekleme (Create)

***İlk kısım olan, create() fonksiyonumuzu kodluruyoruz...***

1) Kitap ve okuyucu kaydı tanımlayalım:
```
k1 = Kitap(isim="Martin Eden")
k1.save()
```
> Yukarıda;
> * `Kitap` modelimizden yeni bir *kitap örneği* oluşturduk.
* `save()` metodu ile kaydımızı gerçekleştirdik.

2) Bu sefer `create` metodu ile *okuyucu* kaydı tanımlayalım:
```
o1 = Okuyucu.create(isim="Abdullah")
```

3) Şimdi de `o1`(Abdullah) **okuyucu**suna, `k1`(Martin Eden) kitabını tanımlayalım:
```
o1.kitaplar.add(k1)
```
> **Hatırlatma!**
> `Okuyucu` modelimize `kitaplar` isminde kolon tanımlamıştık.

4) İsterseniz **farklı bir yöntem** kullanarak *yeni bir kitap* kaydı tanımlayalım:
```
kitap_id = Kitap.insert(isim="Cinayet Alfabesi").execute()
```
> **Ek bilgi**
> `execute()` metodu, kaydı gerçekleştirir ve `int` tipinde benzersiz bir kayıt `id` değeri döndürür.

5) Şimdi `Model.get()` kullanarak az önceki kitap kaydımıza ulaşalım.
Ardından `o1`(Abdullah) okuyucusuna `k2`(Cinayet Alfabesi) kitabımızı tanımlayalım:
```
k2 = Kitap.get(Kitap.id == kitap_id)
k2.okuyucular.add(o1)
```
> **Hatırlatma!**
> `Okuyucu` modelimizde yer alan `kitaplar` kolonunun parametresi, `backref="okuyucular"` şeklindeydi.

![crud.py modülünün, create() fonksiyonu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9y4i0j8ny0b91t9vl75b.png)

## Kayıt Listeleme (Read)
***read() fonksiyonumuzu kodluyoruz...***

1) `Model.select()` ve `for` döngüsü kullanarak kitaplarımızı listeleyebiliriz:
```
kitaplar = Kitap.select()
for kitap in kitaplar:
    print(kitap.id, kitap.isim)
```
*"Kitabı oyuyan her bir kişiyi"* listelemek için **alt** `for` döngüsü tanımlayalım:
```
    for okuyucu in kitap.okuyucular:
        print(okuyucu.isim)
```
> **Ek bilgi**
> * `Model.select().count()` veya `len(Model.select())` kullanarak, sorgu sonucunun **kaç adet kayıt** barındırdığını öğrenebiliriz.
* `Model.select().sql()` kullanarak da sorgunun **SQL karşılığına** ulaşabiliriz.

2) Okuyucuları ve okudukları **kitap sayısını** listeleyelim:
```
for okuyucu in Okuyucu.select():
    print(okuyucu.isim, okuyucu.kitaplar.count())
```

![crud.py modülünün, read() fonksiyonu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2m768iwacr1kh5p7z4t.png)

Buraya kadar yaptığımız değişiklikleri görmek için;
`crud.py` dosyamızın en altına, *fonksiyon çağrımızı* ekleyelim:
``` 
create()
read()
```

Şimdi **komut satırımıza** dönelim ve sonucu görelim:
```
python3 crud.py
```

![GNU/Linux üzerinde komut satırı](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vu4yev8m0m81xcwyp4g6.png)

## Kayıt Güncelleme (Update)
***update() fonksiyonumuzu kodluyoruz...***

1) İlk olarak, kitabımıza ulaşmak için gerekli tanımlamayı yapalım:
```
martin_eden = Kitap.get(Kitap.isim == "Martin Eden")
```

2) Değişikliğimizi tanımlayalım:
```
martin_eden.isim = "Martin Ede"
```

3) Kaydı gerçekleştirmek ve değişikliği ekrana yazdırabilmek için ekleyelim:
```
martin_eden.save()
print(martin_eden.isim)
```

4) Şimdi de **farklı bir yöntem** kullanarak güncelleme tanımlaması yapalım:
```
Kitap.update(isim="Martin Eden").where(Kitap.id == martin_ed
den.id).execute()
```
> `execute()` metodu, işlemi gerçekleştirir ve güncellenen **satır sayısını** `int` tipinde döndürür.

5) Hemen ardından değişikliği görebilmek için ekleyelim:
```
martin_eden = Kitap.get(Kitap.id == martin_eden.id)
print(martin_eden.isim)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x412paezbka20sf7bqm1.png)

5) Son olarak `crud.py` dosyamızın en sonuna *fonksiyon çağrımızı* da ekleyelim:
```
#create()
#read()
update()
```
> Yukarıda;
* `create()` ve `read()` çağrılarımızı *"yorum satırına"* döndürdük ki çalışmasınlar.

Şimdi **komut satırımıza** dönelim ve sonucu görelim:
```
python3 crud.py
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tq8xke19rwa154pi5wy4.png)

## Çoklu Kayıt
***multi_create() fonksiyonumuzu kodluyoruz...***

1) Öncelikle şu şekilde `tuple` barındıran bir liste tanımlıyoruz:
```
veriler = [("Musullu Süleyman",),
           ("İsimsiz",),
           ("İnsanları Okumak",),
           ("Bilim Ne değildir?",)]
```
> Yukarıda;
> `tuple` satır kolonlarını temsil ediyor, örneğimizde tek kolon olduğu için, `("Veri1",)` şeklinde yazdık.

2) Kitapları kaydetmek için ekleyelim:
```
Kitap.insert_many(veriler, fields=[Kitap.isim]).execute()
```

3) Okuyucuları **farklı bir yöntemle** kaydetmek için, şu şekilde tanımlama yapalım:
```
veriler = [{"isim": "İsimsiz"},
           {"isim": "Sefa"}]
Okuyucu.insert_many(veriler).execute()
```
> Bu seferki örneğimizde `veriler` isimli listemiz, `dict` tipinde kolonlar barındırıyor. Ayrıca `insert_many()` metodu, `fields=[]` parametresi almıyor.

*Kaydettiğimiz kitaplara, kaydettiğimiz okuyucuları tanımla işlemiyle devam ediyoruz...*

4) Okuyuculara ulaşalım:
```
isimsiz = Okuyucu.get(Okuyucu.isim.contains("siz"))
abdullah = Okuyucu.get(Okuyucu.isim.startswith("A"))
sefa = Okuyucu.get(Okuyucu.isim.endswith("fa"))
```
> Yukarıda;
> * "siz" ifadesi geçen kayıt getirilecek.
> * "A" ile **başlayan** kayıt getirilecek.
> * "fa" ile **biten** kayıt getirilecek.
>
> **Ek bilgi**
> * Bu metotlar, `where()` içeriside de kullanılabilir.
> * Ek olarak; `between()`, `in_()`, `not_in()`, ... gibi birçok metot mevcut.

5) Aranacak **kitap** ve tanımlanacak **okuyucu** listeleri oluşturalım:
```
aranacaklar = ["isi", "mus", "oku", "bil"]
okuyucular = [isimsiz, [abdullah, sefa], abdullah, [abdullah, sefa]]
```
> Yukarıda;
> 2 liste de **aynı sayıda** eleman içermekte, birden fazla okuyucu **alt liste** içerisinde tanımlanmakta.

6) `for` döngüsü yardımıyla tanımlamaları gerçekleştirelim:
```
index = 0
for aranan in aranacaklar:
    kitap = Kitap.get(Kitap.isim.contains(aranan))
    okuyucu = okuyucular[index]
        
    if type(okuyucu) == list:
        for _okuyucu in okuyucu:
            kitap.okuyucular.add(_okuyucu)
    else:
        kitap.okuyucular.add(okuyucu)
        
    index += 1
```
> Yukarıda;
> * `index` değişkeni, listelerin **index değerini** barındırıyor.
> * Döngü boyunca kitap aranıyor ve bulunan kitap, `kitap` değişkenine aktarılıyor. Kitabı okuyan okuyucu ise, `okuyucular` listesinden `index` yardımıyla `okuyucu` değişkenine aktarılıyor.
> * Eğer kitabı okuyan `okuyucu` **birden fazla** ise (yani liste tipindeyse); **alt** `for` döngüsü çalışıyor, **her bir okuyucu, kitaba tanımlanıyor.**
> * Eğer kitabı okuyan okuyucu **tek** ise; kitaba okuyucu tanımlanıyor.
> * Döngü boyunca `index` değeri 1 arttırılıyor.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sakxj35xevnkk9ux8wjb.png)

## Kayıt Silme (Delete)
***Delete() fonksiyonumuzu kodluyoruz...***

1) Silinecek kayıtarımızı tanımlayalım:
```
kitap = Kitap.get(Kitap.isim == "İsimsiz")
okuyucu = Okuyucu.get(Okuyucu.isim == "İsimsiz")
```

2) **Okuyucuya** tanımlanan **kitabı** silebilmek için ekleyelim:
```
okuyucu.kitaplar.remove(kitap)
```
Ya da tam tersi:
```
kitap.okuyucular.remove(okuyucu)
```
> **Ek bigi**
> `clear()` metodu ilişkili bütün kayıtları siler.

3) Şimdi kitabı silebilmek için tanımlalım:
```
kitap.delete_instance()
```

4) Son olarak, okuyucuyu **farklı şekilde** silebilmek için ekleyelim:
```
Okuyucu.delete().where(Okuyucu.id == okuyucu.id).execute()
```
> `execute()` metodu, işlemi gerçekleştirir ve silinen **satır sayısını** `int` tipinde döndürür.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rlrs99356mjz6maspfx2.png)

---

# JOIN İŞLEMLERİ <a name="join-islemleri"></a>
## *JOIN?*
Birden fazla modelden **tek seferde** veri çekilmesi gerektiğinde devreye join işlemi girer. Modellerimize tanımladığımız `peewee.ForeignKeyField`, `peewee.ManyToManyField` gibi **ilişki kolonları** sayesinde modelleri birleştirebiliyoruz.

## Hazırlık
1) `join.py` isminde yeni bir dosya oluşturalım ve içerisine şu şekilde bir yapı tanımlayalım:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eb0ypk4mqwsvgropnwmd.png)

> Yukarıda;
> * Tarih işlemleri için `datetime` modülünü içe aktardık.
> * `models.py` dosyamızda yer alan ve ihtiyacımız olacak modelleri içe aktardık.
> * İlk defa `peewee.IntegerField`, `peewee.DateField`, `peewee.FloatField` kullanan modeller tanımladık.
> * `peewee.ForeignKeyField` kullanarak modeller arası **bire-bir**(Kitap-KitapDetay) ve **bire-çok**(Yazar-Kitap) ilişkiler tanımladık.

### *Bire-bir İlişki?*
Bire-bir ilişki bize şunu söyler; **bir** kitabı, **bir** yazar yazabilir veya bir kitabın, bir tane fiyatı olabilir.

Yani `KitapDetay` modelinde yer alan `kitap` kolonu, **bir kez** kullanılabilir, aynı kitap tekrar edemez.
```
class KitapDetay(Modelimiz):
    kitap = pw.ForeignKeyField(Kitap, backref="kitaplar", primary_key=True)
``` 
> Yukarıda;
> `primary_key=True` parametresi sayesinde; benzersiz `id` kolonu oluşturulmaz yerine `kitap_id` ayarlanır. kitap_id, `Kitap` modelinin `id` kolonununa karşılık gelir. (KitapDetay.kitap_id == Kitap.id)

### *Bire-çok İlişki?*
Bire-çok ilişki bize şunu söyler; yazar, **birden çok** kitap yazmış olabilir.

Yani `KitapDetay` modelinde yer alan `yazar` kolonu, **birden fazla** kez tekrar edebilir.

```
class KitapDetay(Modelimiz):
    kitap = pw.ForeignKeyField(Kitap, backref="kitaplar", primary_key=True)
    yazar = pw.ForeignKeyField(Yazar, backref="yazarlar")
```
> Somut bir örnek olarak;
> *"Martin Eden"* kitabı **bir kere** yer alabilirken, *"Jack London"* yazarı **birden fazla** kez yer alabilir. (Jack London'ın başka bir kitabını eklemek isteyebiliriz ;)

2) Daha önceki bilgilerimizi kullanarak `create()` fonksiyonumuzu kodlayalım:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/peczv7vwievtlogaxuap.png)

> Yukarıda;
> * Yeni yazarlar kaydedilecek.
> * `kitaplar` listesi üzerinde `for` döngüsü çalışacak. Döngü boyunca; *"kitap"* ve *"okuyucu"* bilgileri `Model.get()` ve -aynı görevi gören- `Model.select().where().get()` ile alınacak.
> * `index` değişkeni sayesinde karşılık gelen *"tarih"* ve *"fiyat"* bilgileri de alınacak, ardından bütün bu bilgiler `dict` tipinde `veriler` listesine eklenecek.
> * Son olarak döngü sonucunda oluşan `veriler` listesi, `KitapDetay` modelimize kaydedilecek.

## Kitap Detaylarını Listeleme

Bunun için `KitapDetay`, `Kitap` ve `Yazar` modellerini birleştirmemiz gerekecek.

1) İlk tanımlamamızı gerçekleştirelim:
```
kitaplar = (KitapDetay
            .select(Kitap.isim,
                    Yazar.isim.alias("yazar"),
                    KitapDetay.tarih,
                    KitapDetay.fiyat))
```
> Yukarıda;
> * Sorgumuzu `()` içerine alarak, **çok satırlı** bir şekilde yazım gerçekleştirebildik.
> * `alias()` metodu, kolonlara **arzuladığımız bir isim** verebilmeyi sağlıyor. (Örneğimizde hem kitap ismi, hem de yazar ismi aynı anda yer aldığı için, karışmasını engelledik.)

2) `join()` metodunu kullanarak `Kitap` modelimizi, `switch() ve join()` metotlarıyla da `Yazar` modelimizi birleştirmek için parantezin içerisine ekleyelim:
```
...
.join(Kitap)
.switch(KitapDetay)
.join(Yazar))
```
> Yukarıda;
> * `switch()` metodu sayesinde `Yazar` modeli, `Kitap` modeliyle değil `KitapDetay` modeliyle birleşecek. Tersi durumda bu iki tablo arasında **bir ilişki olmadığı** için hata alacaktık.
>
> **Ek bilgi**
> * Yalnızca `where()` kullanarak veya şu şekilde zincirleme ifadeler kullanarak filtrelemeler yapmak mümkün:
> ```
...
.join(Yazar)
.where(Yazar.isim == "Martin Eden")
.where(KitapDetay.fiyat < 60)
.orwhere(KitapDetay.tarih.year >= 1995))
```
> **Hatırlatma;** bu ifadelerin `SQL` karşılığını görmek için; `kitaplar.sql()` kullanabiliriz.

3) Listeyi **tarihe göre** sıralamak için parantezin içine ekleyelim:
```
...
.order_by(KitapDetay.tarih.desc()))
```
> Yukarıda;
> `desc()` **son tarih başta** olsun demek oluyor. Eğer sadece kolon ismi belirtirsek, `asc()` kullanılmış gibi varsayılır ve bu durumda, **ilk tarih başta** olsun demiş oluruz.
>
> **Ek bilgi**
> Sorgumuzun sonuna; `limit(3)`(Kaç adet kayıt listeleneceği), `distinct()`(Yalnızca tekrar etmeyen kayıtların listeleneceği) gibi metotlar ekleyebiliriz.

4) Son olarak listeleme işlemini gerçekleştirmek için tanımlayalım:
```
for kitap in kitaplar.dicts():
        print(kitap)
```
> Yukarıda;
> * `dicts()`, satırları *"sözlük veri tipinde(dict)"* almamızı sağlar.
>
> **Ek bilgi**
> `tuples()` veya `namedtuples()` kullanmak da mümkün.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wql10nd69fsgm8iypo0t.png)

## Okuyucuları Listeleme

*read() fonksiyonumuzu kaldığımız yerden kodlamaya devam ediyoruz...*

1) Öncelikle bu kez `select()`'de kullanacağımız kolonları ayrı tanımlayalım:
```
miktar = pw.fn.COUNT(OkunanKitap.kitap_id).alias("okunan_miktar")
birlesik = Okuyucu.isim.concat("+").concat(Kitap.isim).alias("birlesik")
```
> Yukarıda;
> * `miktar` değişkenine atadığımız kolon; `peewee.fn.COUNT()` 'u kullanarak `OkunanKitap.kitap_id`'nin kaç adet olduğunu öğrenmemizi sağlayacak.
> * `birlesik` değişkenine atadığımız kolon; `concat()` metodunu kullanarak `Okuyucu.isim` ve `Kitap.isim` kolonlarını aralarına "+" işareti koyarak birleştirmemizi (tek kolon haline getirmemizi) sağlayacak.
>
> **Ek bilgi**
> `alias()` metodunu modeller için de kullanabiliriz:
>
> `model = Model.alias("model_name")`

2) Bu adımda `okuyucular` değişkenimizin sorgusunu tanımlamaya başlayalım:
```
okuyucular = (Okuyucu
              .select(Okuyucu.isim, miktar, birlesik)
```
3) `OkunanKitap` ve `Kitap` modellerimizi birleştirelim:
```
...
.join(OkunanKitap, pw.JOIN.LEFT_OUTER,
      on=(Okuyucu.id == OkunanKitap.okuyucu_id))
.join(Kitap, pw.JOIN.LEFT_OUTER,
      on=(Kitap.id == OkunanKitap.kitap_id))
```
> Yukarıda;
> `join()` metodunu, **birleşim türü** (LELFT_OUTER, RIGHT_OUTER, vb.) ve **birleşim tercihi** (on=()) ile beraber kullandık.

4) Her bir okuyucunun okuduğu kitap sayısını listelettireceğimiz için, *okuyucu grubu* tanımlayalım: 
```
...
.group_by(Okuyucu.id)
.order_by(miktar, Okuyucu.isim))
```
> Yukarıda;
> `order_by()` metoduna, **birden fazla** sıralanacak kolon tanımlanıyor.

5) Son olarak okuyucular listemizi görüntüleyelim:
```
for okuyucu in okuyucular.objects():
   print(okuyucu.isim,
         okuyucu.okunan_miktar,
         okuyucu.birlesik)
```
> Yukarıda;
> `objects()` her bir satırı **nesne olarak** kullanmamızı sağlıyor.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iviftnfqlhbth81x6kkp.png)

6) Şimdi **komut satırına** dönelim ve sonucu görelim:
```
python join.py
```

![GNU/Linux üzerinde komut satırı](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qba5304crro46ltwjtep.png)

Böylece bu yazının sonuna geldik. Bir başka yazıda görüşmek üzere...