Database Operations (Veritabanı işlemleri)
- Flask aslen veritabanlarını desteklemez. Bunun pozitif yönü; Flask'i, kuracağınız üçüncü parti yazılımla istediğiniz database yaklaşımı veya tipi ile kullanabilirsiniz. İlişkisel(Relational) veya ilişkisel olmayan(non-Relational), SQL veya NoSQL database sistemleri için yazılmış extension'ları Flask'a entegre edip kullanabilirsiniz.
- Burada biz ORM (Object Relational Mapping) kullanacağız. Bunun için uygun extension da Flask-SQLAlchemydir.
- ORM sayesinde Python'da tanımladığımız modeller veritabanında tablolara karşılık gelecek.
pip install Flask-SQLAlchemy
diyerek ihtiyacımız olan extension'ı kuruyoruz.- Biz SQLite veritabanını kullanacağız. SQLite hızlı, hafif, server kurulumu gerektirmeyen bir veritabanıdır.
- Ayrıca
pip install flask-migrate
diyerek Flask-Migrate extension'ını kuruyoruz. Bu extension bize Python tarafında modelde değişiklik yaptığımızda, mevcut veritabanını bozmadan, silmeden güncelleme imkanı verecek. Burada tablolar arası ilişkinin güncellenmesi, alanların eklenmesi-silinmesi gibi işlemlerin gerçekleşmesinden söz ediyoruz.
config.py
dosyamız üzerinde bir kaç işlem yapacağız.
xxxxxxxxxx
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'cok-gizli-anahtar'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'blog.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
- Varsayılan olarak
DATABASE_URL
tanımlı ise onun değerini alacak, tanımlı değilse projemizin ana dizininde duracak şekildeblog.db
'ye kaydolacak şekilde yerini ayarladık. SQLALCHEMY_TRACK_MODIFICATIONS
, database'de her değişiklik için bir sinyal belirten bu değeriFalse
yaparak, iptal etmiş oluyoruz.myapp/__init__.py
dosyasında bir kaç değişiklik yapalım.
xxxxxxxxxx
# external
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
#internal
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
from myapp import routes, models
db
değişkeni SQLAlchemy sınıfından oluşturulan nesne referansıdır.Migrate()
sınıfı app ve db değişkenlerini alarak, migration için bir nevi bağlantı yapıyor.
Şimdi
myapp/models.py
dosyamız ileUser
modelimizi belirleyelim.
xxxxxxxxxx
from myapp import db
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
def __repr__(self):
return '<User {}>'.format(self.username)
User
sınıfımızdb.Model
'i extend ediyor.db.Column()
parametre olarak alan tipini alır. Integer, String ..gibi. Extra parametreler indexleme ve unique olarak belirlenmesi için verilebilir.id
alanımız integer ve primary_key olacak.- username ve email
String()
sınıfı yardımı ile veritabanında verilen uzunluk kadar yerini alacak. __repr__()
methodu, debugging için ve objeleri nasıl yazacağını göstermek için kullanıyoruz.- Daha önce de söylediğimiz gibi modellerdeki değişimden sonra veritabanındaki tabloların değişmesi, silinmesi vb. durumlar için flask migrate ile database tekrar baştan oluşturulmadan, istenilen değişiklikler gerçekleştirilebiliyor.
Terminal'den:
xxxxxxxxxx
(env) kayace@kayace-K53SV ~/flask/blog $ export FLASK_APP=main.py
(env) kayace@kayace-K53SV ~/flask/blog $ flask db init
Creating directory /home/kayace/flask/blog/migrations ... done
Creating directory /home/kayace/flask/blog/migrations/versions ... done
Generating /home/kayace/flask/blog/migrations/script.py.mako ... done
Generating /home/kayace/flask/blog/migrations/env.py ... done
Generating /home/kayace/flask/blog/migrations/README ... done
Generating /home/kayace/flask/blog/migrations/alembic.ini ... done
Please edit configuration/connection/logging settings in
'/home/kayace/flask/blog/migrations/alembic.ini' before proceeding.
- Yukarıdaki komutları verdikten sonra projemize migrations adında bir klasör eklenecek.
xxxxxxxxxx
(env) kayace@kayace-K53SV ~/flask/blog $ flask db migrate -m "User table added"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'user'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
Generating
/home/kayace/flask/blog/migrations/versions/79a72da45c14_user_table_added.py ... done
Üstteki komut ile de tanımladığımız
User
modelini database'e tablo olarak yerleştirmek için hazırlıyoruz. Yani flask migrate bizim için script üretiyor.
- Modelimizi database'e yazmak için
flask db upgrade
komutunu veriyoruz.
xxxxxxxxxx
env) kayace@kayace-K53SV ~/flask/blog $ flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 79a72da45c14, User table added
- Proje dizinimize
blog.db
dosyası da eklendi. Ancak MySQL veya PostgreSQL gibi veri tabanları ile çalışıyorsanız serverda veri tabanını sizin oluşturmanız gerekiyor. - Flask-SQLAlchemy Tablo isimlerinde snake_case yapısını kullanıyor. Örneğin;
User
, veritabanındauser
olarak,AddressPhone
iseaddress_phone
olarak duruyor. - Eğer elle isim vermek isiyorsanız,
__tablename__
kullanabilirsiniz. Örnek kullanım:__tablename__ = 'tablo_adi'
- Eğer flask migrate ile üretilen scripti silmek/geri almak istiyorsanız
flask db downgrade
diyebilirsiniz.
Database relationships (Veritabanı ilişkileri)
- Projemiz için 1(one) kullanıcı(user) 1'den fazla(many) yayın(posts) yapacak şekilde One-To-Many ilişkisini yapacağız.
myapp/models.py
modülümüzü düzenleyelim:
xxxxxxxxxx
from datetime import datetime
from myapp import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
# Relation
posts = db.relationship('Post', backref='author', lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Post {}>'.format(self.body)
User
modeline ilişkiyi eklememiz gerekiyor. db.relationship( ) ilk parametresi ilişki kurulacak sınıfın adı.Post
modelindeuser_id
referansınauser.id
foreign key olarak verildi veUser
modeline bağlandı.Post
modelindedefault=datetime.utcnow
parametresi varsayılan olarak verildi. Ayrıcautcnow()
yerineutcnow
atandı. Yani sonuç yerine referans atandı. UTC ile kullanıcının bulunduğu bölgeye göre tarih, zaman bilgisini alıyoruz. Böylece farklı bölgelerdeki kullanıcılar için doğru zaman bilgisini garantiye almış oluyoruz.db.relationship()
ilk parametre olarak ilişki kurulacak sınıfın adını uppercase olarak alır.db.Foreignkey()
ise veritabanındaki tablo adı ve ilişki kurulacak alan adını lowercase olarak alır. Birden fazla kelime için snake_case kullanılır.backref='author'
; backref,many
tarafındakiposts
'lar için veritabanı ilişkisindekione
tarafını temsil eder.posts.author
diyerek verilenpost
için kullanıcısına erişebiliriz.lazy
parametresi veritabanı ilişkisinin nasıl kurulacağını belirtiyor.
Şimdi terminalden migration işlemini yapalım.
xxxxxxxxxx
(env) kayace@kayace-K53SV ~/flask/blog $ flask db migrate -m "posts table"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'post'
INFO [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'
Generating
/home/kayace/flask/blog/migrations/versions/9d8ba8bdcb4e_posts_table.py ... done
(env) kayace@kayace-K53SV ~/flask/blog $ flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade 79a72da45c14 -> 9d8ba8bdcb4e, posts table
(env) kayace@kayace-K53SV ~/flask/blog $
- Şimdi python shell'i açıp bir kaç database işlemi yapalım.
xxxxxxxxxx
>>> from myapp import db
>>> from myapp.models import User, Post
>>> u = User(username="adnan", email="adnan@kaya.com")
>>> db.session.add(u)
>>> db.session.commit()
>>> u
<User adnan>
- Database objemizi (
db
) ve modellerimizi (User, Post
) import ettik. - Yeni bir User objesi oluşturduk ve username ile email alanlarına atama yaptık.
- Değişiklikleri veritabanına uygulamak için
db.session
contextini kullanıyoruz. Yeni User nesnesininu
referansınıdb.session.add(u)
diyerek database'e yazılabilir hale getirdik. Son olarakdb.session.commit()
diyerek değişiklikleri veritabanına uyguladık. db.session.rollback()
, herhangi bir zamanda bir sessionda hata oluştuğunda, kaydedilmiş değişiklikleri iptal eder.- Bir kayıt daha ekleyelim veritabanımıza.
xxxxxxxxxx
>>> u2 = User(username="mehmet", email="mehmet@bozan.com")
>>> db.session.add(u2)
>>> db.session.commit()
>>> users = User.query.all()
>>> users
[<User adnan>, <User mehmet>]
>>> for user in users:
... print(user.id, user.username)
...
1 adnan
2 mehmet
>>> u3 = User.query.get(2)
>>> u3
<User mehmet>
- Her model
query
attribute'e sahiptir.dir(User.query)
yazarsnız python shell'de iken, kullanabileceğiniz attribute listesini görebilirsiniz. - SQL'deki gibi
select * from tablo_adi
sorgusu yerine.all()
methodu ile modele ait bütün kayıtlar geliyor. - Eğer
id
'sini biliğiniz bir kayıt getirmek istiyorsanız..get(id)
ile kaydı alabilirsiniz.
xxxxxxxxxx
>>> u = User.query.get(1)
>>> p = Post(body="Bu benim ilk yayınım", author=u)
>>> db.session.add(u)
>>> db.session.commit()
>>> posts = Post.query.all()
>>> posts
[<Post Bu benim ilk yayınım>]
>>> posts[0].author.username
'adnan'
>>> posts[0].author.email
'adnan@kaya.com'
>>> User.query.order_by(User.username.desc()).all()
[<User mehmet>, <User adnan>]
User
modelinde tanımladığımızdb.releationship()
users & posts ilişkisini sağlar. backref parametresine atadığımızauthor
alanı da user ID'leri yerine kullanıldı. Buradaauthor
, User sınıfının bir objesini temsil eder.- Kayıtlarımızı sıralı bir şekilde getirmek için order_by kullandık.
Kayıt Silmek (Deleting Record)
xxxxxxxxxx
>>> posts = Post.query.all()
>>> for p in posts:
... db.session.delete(p)
...
>>> db.session.commit()
- Kayıtları silmek için
db.session.delete(kayit_adi)
kullanıyoruz.
Flask Shell
- Python shell yerine flask shell kullanmayı görelim.
xxxxxxxxxx
(env) kayace@kayace-K53SV ~/flask/blog $ flask shell
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
App: myapp
Instance: /home/kayace/flask/blog/instance
>>> app
<Flask 'myapp'>
- Herhangi bir import yapmadan flask varsayılan olarak uygulama örneğini (application instance) dahil ediyor.
- Shell test yaparken çok kullanılan ortamlardan biridir.
blog/main.py
modülümüzü düzenleyelim.
xxxxxxxxxx
# internal
from myapp import app, db
from myapp.models import User, Post
shell_context_processor .
def make_shell_context():
return {'db':db, 'User':User, 'Post':Post}
@app.shell_context_processor
methodumuzu shell context fonksiyonu haline getirir.flask shell
komutu çalıştığı zaman tanımladığımız değerler otomatik olarak import edilmiş olur.
$ flask shell
komutunu terminalden çalıştıralım.
xxxxxxxxxx
>>> User
<class 'myapp.models.User'>
>>> Post
<class 'myapp.models.Post'>
>>> db
<SQLAlchemy engine=sqlite:////home/kayace/flask/blog/blog.db>
@app.shell_context_processor
ekledikten sonra sürekli import etmemize gerek kalmadan istediğimiz modele, modüle erişebiliriz.
Yorumlar