Not: SQLAlchemy tabanlı SQLModel'ı incelemek isteyebilirsiniz.
Resmi belge için: Peewee Docs
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
- Nesne Tabanlı Programlama (class, instance ve object seviyesinde)
- İlişkisel Veritabanı (anlamanızı kolaylaşırır)
Bu yazıda, dilim döndüğünce, bilgim ölçüsünde Python için kolay ama etkili bir ORM olan Peewee'ye hızlı bir giriş yapıyoruz.
Yazı boyunca, kitaplık veritabanı (Github) oluşturarak Peewee'nin temel özelliklerini bu örnek üzerinden görmüş olacağız, umarım faydalı olur.
İçerik:
- Neden Peewee?
- Senaryo
- Hazırlık
- Kurulum
- Veritabanı Tanımlama
- Tabloları Modelleme
- CRUD İşlemleri
- JOIN İşlemleri
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ç
Ö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.
VERİTABANI TANIMLAMA
Kodlamaya başlıyoruz...
0) VS Code 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çindb.connect()
, kapatmak içindb.close()
kullanabiliriz.
TABLOLARI MODELLEME
Peewee'de ilişkisel veritabanı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
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
kullanarakOkuyucu
veKitap
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:
Şimdi komut satırımıza dönelim ve modellerimizi veritabanına aktaralım:
python3 models.py
CRUD İŞLEMLERİ
CRUD?
- Create - Oluştur/Ekle
- Read - Oku/Seç/Listele
- Update - Güncelle
- Delete - Sil
Hazırlık
crud.py
isminde yeni bir dosya oluşturalım ve içerisine şu şekilde bir yapı tanımlayalım:
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) okuyucusuna, k1
(Martin Eden) kitabını tanımlayalım:
o1.kitaplar.add(k1)
Hatırlatma!
Okuyucu
modelimizekitaplar
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 veint
tipinde benzersiz bir kayıtid
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 alankitaplar
kolonunun parametresi,backref="okuyucular"
şeklindeydi.
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()
veyalen(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())
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
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)
5) Son olarak crud.py
dosyamızın en sonuna fonksiyon çağrımızı da ekleyelim:
#create()
#read()
update()
Yukarıda;
create()
veread()
ç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
Ç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ıcainsert_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
listesindenindex
yardımıylaokuyucu
değişkenine aktarılıyor.- Eğer kitabı okuyan
okuyucu
birden fazla ise (yani liste tipindeyse); altfor
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.
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.
JOIN İŞLEMLERİ
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:
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; benzersizid
kolonu oluşturulmaz yerinekitap_id
ayarlanır. kitap_id,Kitap
modelininid
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:
Yukarıda;
- Yeni yazarlar kaydedilecek.
kitaplar
listesi üzerindefor
döngüsü çalışacak. Döngü boyunca; "kitap" ve "okuyucu" bilgileriModel.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 bilgilerdict
tipindeveriler
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 sayesindeYazar
modeli,Kitap
modeliyle değilKitapDetay
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()
veyanamedtuples()
kullanmak da mümkün.
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 kullanarakOkunanKitap.kitap_id
'nin kaç adet olduğunu öğrenmemizi sağlayacak.birlesik
değişkenine atadığımız kolon;concat()
metodunu kullanarakOkuyucu.isim
veKitap.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.
6) Şimdi komut satırına dönelim ve sonucu görelim:
python join.py
Böylece bu yazının sonuna geldik. Bir başka yazıda görüşmek üzere...
Top comments (0)