Flask ve SQLite ile Many-to-Many Veritabanı İlişkileri Nasıl Kullanılır?
Veritabanı tasarımı, modern web uygulamalarının temel taşlarından biridir ve veriler arasındaki ilişkileri doğru bir şekilde modellemek, uygulamanın esnekliği, performansı ve sürdürülebilirliği açısından kritik öneme sahiptir. Flask gibi hafif ve esnek bir web çatısı ile SQLite gibi sunucusuz bir veritabanını birleştirdiğimizde, geliştiricilere hızlı prototipleme ve orta ölçekli uygulamalar için güçlü bir kombinasyon sunulur. Bu makale, Flask ve SQLite ikilisiyle many-to-many (çoka-çok) veritabanı ilişkilerini nasıl etkili bir şekilde kullanacağınızı ayrıntılı olarak ele alacaktır. SQLAlchemy ORM (Object Relational Mapper) kütüphanesini kullanarak, karmaşık veritabanı işlemlerini Python nesneleri aracılığıyla nasıl yöneteceğimizi adım adım inceleyeceğiz.
Many-to-Many İlişkileri Nedir ve Neden Önemlidir?
Web uygulamalarında, farklı veri varlıkları (entity) arasında çeşitli ilişkiler bulunur. Örneğin, bir kullanıcı birden fazla gönderi yazabilir (one-to-many), veya bir gönderinin sadece bir yazarı olabilir (many-to-one). Ancak bazı senaryolarda, iki varlık arasında daha karmaşık bir ilişki türü ortaya çıkar: many-to-many (çoka-çok) ilişkiler.
Many-to-Many İlişkisinin Tanımı
Many-to-many ilişkisi, A varlığındaki her bir öğenin B varlığındaki birden fazla öğeyle ilişkili olabileceği ve B varlığındaki her bir öğenin de A varlığındaki birden fazla öğeyle ilişkili olabileceği bir durumdur.
Örnek Senaryolar:
* Kitaplar ve Yazarlar: Bir kitap birden fazla yazar tarafından yazılabilir ve bir yazar birden fazla kitap yazabilir.
* Öğrenciler ve Dersler: Bir öğrenci birden fazla derse kaydolabilir ve bir derse birden fazla öğrenci kaydolabilir.
* Ürünler ve Kategoriler: Bir ürün birden fazla kategoriye ait olabilir (örn. “Akıllı Telefon” hem “Elektronik” hem de “Mobil Cihazlar” kategorisinde yer alabilir) ve bir kategori birden fazla ürün içerebilir.
* Kullanıcılar ve Rolleri: Bir kullanıcı birden fazla role sahip olabilir (örn. “Yönetici” ve “Editör”) ve bir role birden fazla kullanıcı atanabilir.
Bu tür ilişkiler, gerçek dünya senaryolarını veritabanında doğru bir şekilde temsil etmek için hayati öneme sahiptir. Eğer many-to-many ilişkileri doğru bir şekilde yönetilmezse, veri tekrarı, tutarsızlık ve sorgulama zorlukları gibi problemlere yol açabilir.
İlişkisel Veritabanlarında Many-to-Many Çözümü: Ara Tablo (Junction/Association Table)
İlişkisel veritabanı teorisinde, many-to-many ilişkileri doğrudan iki ana tablo arasında kurulamaz. Bunun yerine, bu tür ilişkiler, araya bir “ara tablo” (junction table veya association table) eklenerek çözülür. Ara tablo, her iki ana tablonun birincil anahtarlarını (primary key) yabancı anahtar (foreign key) olarak içerir. Bu sayede, her bir ana tablonun ara tablo ile one-to-many ilişkisi kurulmuş olur.
Örnek: Kitaplar ve Yazarlar
* Kitaplar tablosu: id, başlık, yayın_yılı
* Yazarlar tablosu: id, ad, soyad
* kitap_yazar_ilişkisi (ara tablo): kitap_id, yazar_id
kitap_yazar_ilişkisi tablosundaki her bir satır, belirli bir kitabın belirli bir yazarla olan ilişkisini temsil eder. Bu tablo sayesinde, bir kitap birden fazla yazar_id ile ilişkilendirilebilir ve bir yazar birden fazla kitap_id ile ilişkilendirilebilir.
Ortam Kurulumu ve Flask-SQLAlchemy Entegrasyonu
Flask uygulamamızda many-to-many ilişkilerini yönetmek için Flask-SQLAlchemy uzantısını kullanacağız. Bu uzantı, SQLAlchemy ORM’ini Flask ile sorunsuz bir şekilde entegre eder ve veritabanı işlemlerini Python kodumuz içinde daha kolay ve “Pythonic” bir şekilde yapmamızı sağlar.
Gerekli Kütüphaneler
Başlamadan önce aşağıdaki kütüphaneleri yüklememiz gerekiyor:
* Flask: Web çatısı.
* Flask-SQLAlchemy: SQLAlchemy’yi Flask ile entegre eden uzantı.
* SQLAlchemy: Python için güçlü bir ORM. (Flask-SQLAlchemy ile birlikte otomatik olarak yüklenecektir.)
* sqlite3: SQLite veritabanı sürücüsü. (Python ile birlikte gelir, ayrıca yüklemeye gerek yoktur.)
pip install Flask Flask-SQLAlchemy
Proje Yapısı
Basit bir proje yapısı aşağıdaki gibi olabilir:
my_app/
├── app.py
├── models.py
└── instance/
└── my_app.sqlite
* app.py: Flask uygulamasını başlattığımız ve rotaları tanımladığımız ana dosya.
* models.py: Veritabanı modellerimizi (tablolarımızı) tanımladığımız dosya.
* instance/my_app.sqlite: SQLite veritabanı dosyamız burada tutulacak. Flask varsayılan olarak instance klasörünü uygulama dışı veriler için kullanır.
Flask Uygulamasını Başlatma ve SQLAlchemy Entegrasyonu
İlk olarak, Flask uygulamasını başlatıp Flask-SQLAlchemy’yi entegre edelim.
# app.py
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
import os
Uygulama ve veritabanı yolu ayarları
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'instance', 'my_app.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
Modelleri içeri aktar
from models import * # Modelleri daha sonra models.py içinde tanımlayacağız
Veritabanını oluşturma (sadece bir kez çalıştırılmalı)
with app.app_context():
db.create_all()
... (Rota tanımlamaları buraya gelecek)
if __name__ == '__main__':
app.run(debug=True)
Bu kod parçacığı, Flask uygulamasını başlatır, SQLite veritabanı dosyasının yolunu ayarlar ve db nesnesini oluşturur. db.create_all() çağrısı, tanımladığımız modelleri kullanarak veritabanı tablolarını oluşturur. app.app_context() içinde çalıştırılması önemlidir, çünkü veritabanı işlemleri bir uygulama bağlamı gerektirir.
Örnek Senaryo: Kitaplar ve Yazarlar Modelleri
Şimdi, many-to-many ilişkisini somutlaştırmak için “Kitaplar” ve “Yazarlar” senaryosunu ele alalım.
* Bir Author (Yazar) birden fazla Book (Kitap) yazabilir.
* Bir Book (Kitap) birden fazla Author (Yazar) tarafından yazılabilir.
Bu ilişkiyi modellemek için bir ara tabloya ihtiyacımız olacak.
Yardımcı Tablo (Association Table) Oluşturma
models.py dosyasında, önce ara tabloyu tanımlamamız gerekiyor. Bu tablo, db.Table kullanılarak oluşturulur ve Column nesneleri ile her iki ana tablonun birincil anahtarlarını yabancı anahtar olarak içerir.
# models.py
from app import db # app.py'den db nesnesini içeri aktarıyoruz
Many-to-Many ilişkisi için ara tablo (association table)
Bu tablo, Kitaplar ve Yazarlar arasındaki bağlantıyı tutar.
authors_books = db.Table('authors_books',
db.Column('author_id', db.Integer, db.ForeignKey('author.id'), primary_key=True),
db.Column('book_id', db.Integer, db.ForeignKey('book.id'), primary_key=True)
)
Bu authors_books tablosu, author.id ve book.id sütunlarını içerir. Her iki sütun da birincil anahtarın parçasıdır (primary_key=True), bu da aynı yazarın aynı kitabı birden fazla kez yazamayacağı anlamına gelir (mantıksal olarak doğru). Ayrıca, bu sütunlar sırasıyla author ve book tablolarına yabancı anahtar olarak bağlanmıştır.
Author Modeli Tanımlama
Author modelini tanımlarken, db.relationship kullanarak Book modeliyle olan many-to-many ilişkisini belirtmemiz gerekir.
# models.py devamı
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
# Many-to-Many ilişkisi: Bir yazarın birden fazla kitabı olabilir.
# 'secondary' argümanı, ara tabloyu belirtir.
# 'back_populates' ise Book modelindeki 'authors' ilişkisini geri bağlar.
books = db.relationship('Book', secondary=authors_books, lazy='subquery',
back_populates='authors')
def __repr__(self):
return f''
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'books': [book.to_dict_simple() for book in self.books]
}
def to_dict_simple(self):
return {
'id': self.id,
'name': self.name
}
* books = db.relationship('Book', ...): Bu satır, Author modeli ile Book modeli arasında bir ilişki kurar.
* secondary=authors_books: Bu argüman, many-to-many ilişkisini kurmak için kullanılan ara tabloyu (daha önce tanımladığımız authors_books) belirtir.
* lazy='subquery': Bu, ilgili kitapların yazar sorgulandığında hemen yüklenmesini sağlar, ancak daha verimli bir şekilde (tek bir sorguda alt sorgu kullanarak) yapar. Diğer seçenekler select (her kitap için ayrı sorgu, N+1 problemi), joined (JOIN kullanarak tek sorgu) veya dynamic (ilişkiyi sorgulanabilir bir nesne olarak döndürür) olabilir.
* back_populates='authors': Bu, Book modelindeki authors ilişkisinin bu books ilişkisiyle çift yönlü olduğunu belirtir. Bu, bir yazarın kitaplarını eklediğinizde, kitabın yazarlar listesine de otomatik olarak yazarın eklenmesini sağlar.
* to_dict ve to_dict_simple metotları, model nesnelerini JSON’a dönüştürmek için yardımcı fonksiyonlardır. to_dict_simple sonsuz döngüyü engellemek için kullanılır (bir kitap yazarları listelerken yazar da kitapları listelerse).
Book Modeli Tanımlama
Benzer şekilde, Book modelini de Author modeliyle olan many-to-many ilişkisini belirterek tanımlarız.
# models.py devamı
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
publication_year = db.Column(db.Integer)
# Many-to-Many ilişkisi: Bir kitabın birden fazla yazarı olabilir.
# 'secondary' argümanı, ara tabloyu belirtir.
# 'back_populates' ise Author modelindeki 'books' ilişkisini geri bağlar.
authors = db.relationship('Author', secondary=authors_books, lazy='subquery',
back_populates='books')
def __repr__(self):
return f''
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'publication_year': self.publication_year,
'authors': [author.to_dict_simple() for author in self.authors]
}
def to_dict_simple(self):
return {
'id': self.id,
'title': self.title
}
Book modelindeki authors ilişkisi, Author modelindeki books ilişkisiyle tamamen aynı mantıkla çalışır. back_populates sayesinde, bir Book nesnesine bir Author eklediğinizde, o Author nesnesinin books listesine de ilgili Book nesnesi otomatik olarak eklenir.
Veritabanı İşlemleri: Ekleme, Sorgulama, Güncelleme, Silme
Modellerimizi tanımladığımıza göre, şimdi bu modellerle veritabanında nasıl CRUD (Create, Read, Update, Delete) işlemleri yapacağımıza bakalım.
Veri Ekleme (Creating Data)
Yeni yazarlar ve kitaplar oluşturup, aralarındaki many-to-many ilişkileri kurmak oldukça basittir.
# app.py veya ayrı bir script içinde çalıştırılabilir
from app import app, db
from models import Author, Book
with app.app_context():
# Yazarlar oluşturma
author1 = Author(name='George Orwell')
author2 = Author(name='Aldous Huxley')
author3 = Author(name='Margaret Atwood')
author4 = Author(name='J.R.R. Tolkien')
# Kitaplar oluşturma
book1 = Book(title='1984', publication_year=1949)
book2 = Book(title='Animal Farm', publication_year=1945)
book3 = Book(title='Brave New World', publication_year=1932)
book4 = Book(title='The Handmaid\'s Tale', publication_year=1985)
book5 = Book(title='The Lord of the Rings', publication_year=1954)
# Kitapları yazarlara atayarak ilişkileri kurma
author1.books.append(book1)
author1.books.append(book2)
author2.books.append(book3)
author3.books.append(book4)
author4.books.append(book5)
# Örnek: Birden fazla yazarlı kitap
# Diyelim ki 'The Silmarillion' Tolkien ve oğlu Christopher tarafından yazıldı (basitleştirilmiş)
book6 = Book(title='The Silmarillion', publication_year=1977)
author4.books.append(book6)
# Yeni bir yazar ekleyelim ve bu kitaba bağlayalım (örnek amaçlı)
author5 = Author(name='Christopher Tolkien')
author5.books.append(book6)
# Tüm nesneleri veritabanına ekle
db.session.add_all([author1, author2, author3, author4, author5, book1, book2, book3, book4, book5, book6])
db.session.commit()
print("Örnek veriler başarıyla eklendi.")
Burada dikkat edilmesi gereken nokta, author.books.append(book) veya book.authors.append(author) kullanarak ilişkileri kurmamızdır. SQLAlchemy, back_populates sayesinde bu işlemi çift yönlü olarak otomatik yönetir. Yani, author1.books.append(book1) dediğimizde, book1.authors listesine de author1 otomatik olarak eklenir. db.session.add_all() ile tüm nesneleri oturuma ekledikten sonra db.session.commit() ile değişiklikleri veritabanına kalıcı olarak kaydederiz.
Veri Sorgulama (Querying Data)
İlişkisel verileri sorgulamak, many-to-many ilişkilerinin en güçlü yönlerinden biridir.
# app.py veya ayrı bir script içinde çalıştırılabilir
from app import app, db
from models import Author, Book
with app.app_context():
# Tüm yazarları ve kitaplarını sorgula
all_authors = Author.query.all()
print("\nTüm Yazarlar ve Kitapları:")
for author in all_authors:
print(f"Yazar: {author.name}")
for book in author.books:
print(f" - Kitap: {book.title} ({book.publication_year})")
# Belirli bir yazarı ve kitaplarını sorgula
george = Author.query.filter_by(name='George Orwell').first()
if george:
print(f"\n{george.name} tarafından yazılan kitaplar:")
for book in george.books:
print(f" - {book.title} ({book.publication_year})")
# Tüm kitapları ve yazarlarını sorgula
all_books = Book.query.all()
print("\nTüm Kitaplar ve Yazarları:")
for book in all_books:
author_names = ", ".join([a.name for a in book.authors])
print(f"Kitap: {book.title} ({book.publication_year}) - Yazarlar: {author_names}")
# Belirli bir kitabı ve yazarlarını sorgula
lotr = Book.query.filter_by(title='The Lord of the Rings').first()
if lotr:
author_names = ", ".join([a.name for a in lotr.authors])
print(f"\n'{lotr.title}' kitabının yazarları: {author_names}")
# Belirli bir yazarın yazdığı belirli bir kitabı bulma (join ile)
# Bu daha çok bir SQL JOIN örneği gibi ama ORM ile de yapılabilir
book_by_author = Book.query.join(authors_books).join(Author).filter(
Author.name == 'George Orwell',
Book.title == '1984'
).first()
if book_by_author:
print(f"\nGeorge Orwell'ın yazdığı '1984' kitabı bulundu: {book_by_author.title}")
author.books veya book.authors niteliklerine eriştiğimizde, SQLAlchemy otomatik olarak ilişkili nesneleri veritabanından çeker (lazy loading stratejisine göre). lazy='subquery' kullandığımız için, ilgili veriler ana sorguyla birlikte daha verimli bir şekilde çekilecektir.
Veri Güncelleme (Updating Data)
İlişkileri güncellemek, mevcut nesnelerin ilişki listelerini değiştirmek anlamına gelir.
# app.py veya ayrı bir script içinde çalıştırılabilir
from app import app, db
from models import Author, Book
with app.app_context():
# Bir kitabın yayın yılını güncelleme
book_to_update = Book.query.filter_by(title='1984').first()
if book_to_update:
book_to_update.publication_year = 1950 # Yanlışlıkla güncelleyelim
db.session.commit()
print(f"\n'{book_to_update.title}' kitabının yayın yılı güncellendi: {book_to_update.publication_year}")
# Bir kitabın yazar listesini güncelleme (yeni yazar ekleme)
silmarillion = Book.query.filter_by(title='The Silmarillion').first()
if silmarillion:
# Diyelim ki J.R.R. Tolkien'in başka bir ortak yazarı var
new_author = Author(name='Another Co-Author')
db.session.add(new_author) # Yeni yazarı önce veritabanına eklemeliyiz
silmarillion.authors.append(new_author)
db.session.commit()
print(f"\n'{silmarillion.title}' kitabına yeni yazar eklendi.")
print(f"Yeni yazarlar: {[a.name for a in silmarillion.authors]}")
# Bir kitabın yazar listesinden yazar çıkarma
if silmarillion:
christopher = Author.query.filter_by(name='Christopher Tolkien').first()
if christopher and christopher in silmarillion.authors:
silmarillion.authors.remove(christopher)
db.session.commit()
print(f"\n'{silmarillion.title}' kitabından Christopher Tolkien çıkarıldı.")
print(f"Kalan yazarlar: {[a.name for a in silmarillion.authors]}")
İlişkili nesneleri eklemek için .append() kullanırken, çıkarmak için .remove() kullanırız. SQLAlchemy, bu değişiklikleri ara tabloda otomatik olarak yansıtır.
Veri Silme (Deleting Data)
Bir ana nesneyi sildiğimizde, ilişkilerin nasıl etkileneceği önemlidir. SQLAlchemy’de cascade seçenekleri ile bu davranış kontrol edilebilir. Varsayılan olarak, bir yazar silindiğinde, yazar ile ilgili kitap ilişkileri (ara tablodaki kayıtlar) silinir, ancak kitap nesneleri silinmez.
# app.py veya ayrı bir script içinde çalıştırılabilir
from app import app, db
from models import Author, Book
with app.app_context():
# Bir yazarı silme
author_to_delete = Author.query.filter_by(name='Aldous Huxley').first()
if author_to_delete:
db.session.delete(author_to_delete)
db.session.commit()
print(f"\n'{author_to_delete.name}' yazarı silindi.")
# 'Brave New World' kitabının yazarlarını kontrol edelim
brave_new_world = Book.query.filter_by(title='Brave New World').first()
if brave_new_world:
print(f"'{brave_new_world.title}' kitabının kalan yazarları: {[a.name for a in brave_new_world.authors]}")
else:
print(f"'{brave_new_world.title}' kitabı bulunamadı.") # Kitap silinmediği için bulunacak
# Bir kitabı silme
book_to_delete = Book.query.filter_by(title='Animal Farm').first()
if book_to_delete:
db.session.delete(book_to_delete)
db.session.commit()
print(f"\n'{book_to_delete.title}' kitabı silindi.")
# George Orwell'ın kitaplarını kontrol edelim
george = Author.query.filter_by(name='George Orwell').first()
if george:
print(f"'{george.name}' yazarının kalan kitapları: {[b.title for b in george.books]}")
Varsayılan cascade davranışı, bir nesne silindiğinde sadece o nesneyi ve ara tablodaki ilgili ilişki kayıtlarını siler. Bağlantılı nesneler (örneğin, bir yazar silindiğinde kitaplar) silinmez. Eğer bir yazar silindiğinde yazdığı tüm kitapların da silinmesini istiyorsanız, db.relationship tanımına cascade="all, delete-orphan" gibi seçenekler eklemeniz gerekir. Ancak bu, many-to-many ilişkilerinde genellikle istenmeyen bir durumdur, çünkü bir kitap birden fazla yazara ait olabilir ve bir yazar silindiğinde diğer yazarların kitaplarının da silinmesi mantıksız olur.
Flask Uygulamasına Entegre Etme: Basit Bir API
Şimdi, bu veritabanı işlemlerini Flask rotaları aracılığıyla nasıl bir web API’sine dönüştüreceğimize bakalım.
# app.py devamı
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
import os
from models import Author, Book, authors_books # models.py'den import ediyoruz
... (Uygulama ve db tanımlamaları yukarıda yapıldı)
Ana sayfa (sadece test için)
@app.route('/')
def index():
return "Flask Many-to-Many İlişkileri API'si"
Tüm yazarları listele
@app.route('/authors', methods=['GET'])
def get_authors():
authors = Author.query.all()
return jsonify([author.to_dict() for author in authors])
Belirli bir yazarı ve kitaplarını göster
@app.route('/authors/', methods=['GET'])
def get_author(author_id):
author = Author.query.get_or_404(author_id)
return jsonify(author.to_dict())
Yeni yazar ekle
@app.route('/authors', methods=['POST'])
def add_author():
data = request.get_json()
if not data or not 'name' in data:
return jsonify({'message': 'Yazar adı gerekli'}), 400
new_author = Author(name=data['name'])
db.session.add(new_author)
db.session.commit()
return jsonify(new_author.to_dict()), 201
Bir yazarı güncelle
@app.route('/authors/', methods=['PUT'])
def update_author(author_id):
author = Author.query.get_or_404(author_id)
data = request.get_json()
if 'name' in data:
author.name = data['name']
db.session.commit()
return jsonify(author.to_dict())
Bir yazarı sil
@app.route('/authors/', methods=['DELETE'])
def delete_author(author_id):
author = Author.query.get_or_404(author_id)
db.session.delete(author)
db.session.commit()
return jsonify({'message': 'Yazar başarıyla silindi'}), 200
Tüm kitapları listele
@app.route('/books', methods=['GET'])
def get_books():
books = Book.query.all()
return jsonify([book.to_dict() for book in books])
Belirli bir kitabı ve yazarlarını göster
@app.route('/books/', methods=['GET'])
def get_book(book_id):
book = Book.query.get_or_404(book_id)
return jsonify(book.to_dict())
Yeni kitap ekle ve yazarlarıyla ilişkilendir
@app.route('/books', methods=['POST'])
def add_book():
data = request.get_json()
if not data or not 'title' in data or not 'publication_year' in data:
return jsonify({'message': 'Kitap başlığı ve yayın yılı gerekli'}), 400
new_book = Book(title=data['title'], publication_year=data['publication_year'])
# Eğer yazar ID'leri sağlandıysa, yazarları bul ve kitaba ekle
if 'author_ids' in data and isinstance(data['author_ids'], list):
for author_id in data['author_ids']:
author = Author.query.get(author_id)
if author:
new_book.authors.append(author)
db.session.add(new_book)
db.session.commit()
return jsonify(new_book.to_dict()), 201
Bir kitabı güncelle
@app.route('/books/', methods=['PUT'])
def update_book(book_id):
book = Book.query.get_or_404(book_id)
data = request.get_json()
if 'title' in data:
book.title = data['title']
if 'publication_year' in data:
book.publication_year = data['publication_year']
# Yazarları güncelleme (tamamen değiştirme veya ekleme/çıkarma)
if 'author_ids' in data and isinstance(data['author_ids'], list):
book.authors = [] # Mevcut tüm yazarları kaldır
for author_id in data['author_ids']:
author = Author.query.get(author_id)
if author:
book.authors.append(author) # Yeni yazarları ekle
db.session.commit()
return jsonify(book.to_dict())
Bir kitabı sil
@app.route('/books/', methods=['DELETE'])
def delete_book(book_id):
book = Book.query.get_or_404(book_id)
db.session.delete(book)
db.session.commit()
return jsonify({'message': 'Kitap başarıyla silindi'}), 200
Bir kitaba yazar ekle (belirli bir kitap ve yazar ID'si ile)
@app.route('/books//authors/', methods=['POST'])
def add_author_to_book(book_id, author_id):
book = Book.query.get_or_404(book_id)
author = Author.query.get_or_404(author_id)
if author not in book.authors:
book.authors.append(author)
db.session.commit()
return jsonify(book.to_dict()), 200
return jsonify({'message': 'Bu yazar zaten bu kitaba atanmış'}), 409 # Conflict
Bir kitaptan yazar çıkar (belirli bir kitap ve yazar ID'si ile)
@app.route('/books//authors/', methods=['DELETE'])
def remove_author_from_book(book_id, author_id):
book = Book.query.get_or_404(book_id)
author = Author.query.get_or_404(author_id)
if author in book.authors:
book.authors.remove(author)
db.session.commit()
return jsonify(book.to_dict()), 200
return jsonify({'message': 'Bu yazar bu kitaba atanmamış'}), 404 # Not Found
... (Uygulama çalıştırma bloğu yukarıda yapıldı)
if __name__ == '__main__':
app.run(debug=True)
Bu Flask API’si, birçok temel işlemi kapsar:
* GET /authors: Tüm yazarları ve onların kitaplarını listeler.
* GET /authors/
* POST /authors: Yeni bir yazar oluşturur.
* PUT /authors/
* DELETE /authors/
* GET /books: Tüm kitapları ve onların yazarlarını listeler.
* GET /books/
* POST /books: Yeni bir kitap oluşturur ve isteğe bağlı olarak yazar ID’leri listesi ile yazarlarıyla ilişkilendirir.
* PUT /books/
* DELETE /books/
* POST /books/
* DELETE /books/
request.get_json() ile gelen JSON verilerini işleriz ve jsonify() ile Python nesnelerini JSON yanıtlarına dönüştürürüz. Modellerimizdeki to_dict() ve to_dict_simple() metotları bu dönüşümü kolaylaştırır ve sonsuz döngüleri engeller.
Gelişmiş Konular ve En İyi Uygulamalar
Many-to-many ilişkilerini kullanırken dikkate alınması gereken bazı gelişmiş konular ve en iyi uygulamalar bulunmaktadır:
lazy Yükleme Stratejileri ve N+1 Problemi
db.relationship tanımında kullanılan lazy argümanı, ilişkili verilerin ne zaman yükleneceğini belirler.
* lazy='select' (varsayılan): İlişkili verilere her erişildiğinde ayrı bir SELECT sorgusu çalıştırır. N adet ana nesne varsa, N+1 sorguya neden olabilir (N+1 problemi). Performans açısından genellikle kötüdür.
* lazy='joined': İlişkili verileri ana sorguyla birlikte bir JOIN kullanarak yükler. Genellikle daha verimlidir, ancak çok fazla JOIN gerektiren karmaşık senaryolarda sorguyu büyütebilir.
* lazy='subquery': İlişkili verileri ayrı bir alt sorgu kullanarak yükler. joined kadar olmasa da çoğu durumda verimlidir.
* lazy='dynamic': İlişkili nesneleri doğrudan yüklemez, bunun yerine sorgulanabilir bir nesne döndürür. Bu, ilişkili nesneler üzerinde ek filtreleme veya sıralama yapmak istediğinizde kullanışlıdır.
Büyük veri kümeleriyle çalışırken N+1 problemini önlemek için joinedload veya subqueryload gibi eager loading tekniklerini kullanmak önemlidir. Örneğin:
authors = Author.query.options(db.joinedload(Author.books)).all()
veya
authors = Author.query.options(db.subqueryload(Author.books)).all()
Bu, tüm yazarları ve ilgili kitaplarını tek bir sorguda veya daha az sorguda çeker.
Ara Tabloda Ek Sütunlar (Association Object)
Bazen ara tabloda sadece iki ana tablonun ID’lerini tutmak yeterli olmaz. Örneğin, bir öğrencinin bir derse ne zaman kaydolduğu veya bir yazarın bir kitabı yazmaya ne zaman başladığı gibi ek bilgiler ara tabloda tutulmak istenebilir. Bu durumda, db.Table yerine özel bir “ilişki modeli” (association object) oluşturulur.
# models.py içinde
class AuthorBookAssociation(db.Model):
__tablename__ = 'author_book_association'
author_id = db.Column(db.Integer, db.ForeignKey('author.id'), primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey('book.id'), primary_key=True)
contribution_date = db.Column(db.DateTime, default=db.func.current_timestamp()) # Ek sütun
author = db.relationship('Author', back_populates='book_associations')
book = db.relationship('Book', back_populates='author_associations')
class Author(db.Model):
# ... diğer sütunlar ...
book_associations = db.relationship('AuthorBookAssociation', back_populates='author')
books = association_proxy('book_associations', 'book') # Daha temiz erişim için
class Book(db.Model):
# ... diğer sütunlar ...
author_associations = db.relationship('AuthorBookAssociation', back_populates='book')
authors = association_proxy('author_associations', 'author') # Daha temiz erişim için
Bu yaklaşım, ara tabloda ek veriler saklamanıza olanak tanır ve association_proxy ile ilişkilere daha temiz bir şekilde erişim sağlayabilirsiniz.
Validasyon ve Hata Yönetimi
API uç noktalarınıza gelen verileri her zaman doğrulamalısınız. Örneğin, bir yazar eklerken adın boş olmamasını veya belirli bir uzunlukta olmasını sağlamak gibi. Flask-WTF veya Marshmallow gibi kütüphaneler, veri validasyonu ve serileştirmeyi kolaylaştırabilir. Hata durumlarında uygun HTTP durum kodları (400 Bad Request, 404 Not Found, 409 Conflict vb.) döndürmek önemlidir.
Veritabanı Migrasyonları (Alembic)
Uygulamanız geliştikçe veritabanı şemanız değişecektir (yeni sütunlar, yeni tablolar, indeksler vb.). Flask-Migrate (Alembic üzerine kurulu) gibi araçlar, veritabanı şema değişikliklerini yönetmenize ve üretim ortamında güvenli bir şekilde uygulamanıza olanak tanır.
Sonuç
Flask ve SQLite ile many-to-many veritabanı ilişkilerini yönetmek, Flask-SQLAlchemy’nin güçlü ORM yetenekleri sayesinde oldukça kolay ve sezgiseldir. Ara tablo kavramını anlayarak, modellerinizi doğru bir şekilde tanımlayarak ve CRUD işlemlerini uygulayarak, karmaşık gerçek dünya senaryolarını veritabanınızda etkili bir şekilde modelleyebilirsiniz.
Bu makalede ele aldığımız adımlar ve örnek kodlar, Flask uygulamanızda çoka-çok ilişkilerini başarıyla uygulamanız için sağlam bir temel sunmaktadır. Performans optimizasyonları, gelişmiş ilişki yönetimi ve sağlam hata işleme gibi konuları daha derinlemesine araştırarak uygulamanızın kalitesini ve sürdürülebilirliğini daha da artırabilirsiniz. Flask ve SQLAlchemy’nin sunduğu esneklik ve güç ile, veri yoğun web uygulamaları geliştirmek hiç bu kadar erişilebilir olmamıştı.