İçeriğe geç

Django Data Migrations

Giriş.

Django migration ları denilince akla ilk olarak veritabanı şemasını doğrudan etkileyen kavramlar gelir. Bir modele yeni bir alan eklemek, bazı alanların özelliklerini değişen ihtiyaçlara göre değiştirmek vs. Django bu değişiklikleri takip eder, migration dosyaları oluşturur, uygular ve veritabanınızı kolayca yeni şemasına geçirmenize yardımcı olur.

Bunun yanında migrationlar varolan şema içersindeki verileri düzenlemek veya taşımak için de kullanılabilir. Bu tip migrationlar data migrations olarak adlandırılır. Şemayı ilgilendirmedikleri için Django tarafından takip edilmezler, geliştirici tarafından oluştururlar fakat normal şema migrationları gibi yürütülürler ve sistemin bir parçasıdırlar. Bu yazıda data migrationlarını bir örnek üzerinden inceleyeceğiz.

Data migrations.

Django şema migrationlarının aksine data migration dosyalarını otomatik olarak oluşturmaz, bundan bahsettik. Fakat bu işlemi kurgulayabilmeniz için basit ve güçlü bir altyapıya sahiptir.

Bir örnek üzerinden bu işlemi nasıl yapabileceğiniz, dikkat edilmesi gereken noktalar ve düşünülmesi gereken durumlar üzerine konuşalım.

Yazarları listelediğmiz bir projemiz olduğunu, authors uygulaması altında yazar ismi, soyismi ve doğum yılını tutan bir model bulunsun.

#authors/models.py

class Author(models.Model):

    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    birth_year = models.PositiveSmallIntegerField()

    def __str__(self) -> str:
        return f"{self.first_name} {self.last_name}"

Bu uygulama hali hazırda canlıda çalışıyor ve yazar bilgileri barındırıyor. Fakat değişen ihtiyaçlar doğrultusunda, biz artık isim ve soyismi ayrık tutmaktansa tek bir alanda birleştirmek, daha sonrasında artık kullanılmayacak olan isim ve soyisim alanlarını veri kaybetmeden, şemadan kaldırmak istiyoruz.

Canlıdaki bir uygulamanın veritabanı şemasında değişikliğine gidildiği zaman, genellikle yeni eklenen alan veri tutarsızlığına ve problemlere neden olmaması adına null=True veya default=foo gibi parametreler ile işaretlenir. Fakat veri taşınması, veya başka alanlara bağlı güncelleme gibi durumlarda default flagi işe yaramayacaktır.

Bunun için ilk olarak alanı nullable tanımlayacak, sonra var olan tüm yazarların isimlerini bir data migration yazarak taşıyacak, daha sonra nullable durumundan çıkarıp, artık kullanılmayan alanları da şemadan kaldıracağız.

İlk olarak yeni alanı modele ekleyelim.

#authors/models.py
class Author(models.Model):

    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    birth_year = models.PositiveSmallIntegerField()
    full_name = models.CharField(max_length=255, null=True)

    def __str__(self) -> str:
        return f"{self.full_name}"

Değişikler için migration oluşturup veritabanına uygulayalım. full_name alanı artık şemada bulunyor ve nullable.

$ python manage.py makemigrations authors && python manage.py migrate authors
Migrations for 'authors':
authors/migrations/0002_author_full_name.py
- Add field full_name to author
Operations to perform:
Apply all migrations: authors
Running migrations:
Applying authors.0002_author_full_name... OK

Şu anda elimizde aşağıdaki gibi bir veri şeması ve veri bulunmakta.

data table v1


Veriyi taşımak için boş bir migration dosyası oluşturalım.

$ python manage.py makemigrations --empty authors
Migrations for 'authors':
  authors/migrations/0003_auto_20210227_2239.py

Yeni migration dosyasının içeriğine bakalım.

# Generated by Django 3.1.7 on 2021-02-27 22:39
# authors/0003_auto_20210227_2239.py

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ("authors", "0002_author_full_name"),
    ]

    operations = []

Migration dosyasının bağımlı olduğu migration(varsayılan olarak kendinden bir önceki migration) ve yürütülmesi planlan boş bir operasyon listesi var.

Bağımlı olduğu migration dosyasından sonra (full_name alanının şemaya eklenmesi)

RunPython komutu tarafından yürütülmesi için buraya 1 adet fonksiyon yazacağız ve operasyonlara tanımlayacağız. RunPython komutu 2 adet parametre alarak gerekli operasyonları gerçekleştirir. İlki migration dosyası ve dolayısıyla operasyon işlenirken koşturulacak fonksiyon, ikincisi ise migration geri sarılırken koşturulacak fonksiyon. Geri sarma fonksiyonu opsiyoneldir. Geriye dönük bir işlem kurgulamayacak iseniz boş geçebilirsiniz.

Fonksiyon sistemde bulunan tüm yazarların, isim ve soyisimlerini full_name alanına taşıyacak.

# Generated by Django 3.1.7 on 2021-02-27 22:39
# authors/0003_auto_20210227_2239.py

from django.db import migrations


def set_full_name(apps, schema_editor):
    Author = apps.get_model("authors", "Author")
    for author in Author.objects.all():
        author.full_name = f"{author.first_name} {author.last_name}"
        author.save()


class Migration(migrations.Migration):

    dependencies = [
        ("authors", "0002_author_full_name"),
    ]

    operations = [migrations.RunPython(code=set_full_name)]

RunPython içerisinde yürütelecek olan fonksiyonlar, 2 adet parametre almak zorundadır. apps ve schema_editor. Bu 2 parametre üzerinde çalışılacak olan modelin, migration geçmisini takip ederek, o andaki tarihsel halini döndürür. Doğrudan modeli import etmemeniz ve migrationlara bağlı tarihsel versiyonu kullanmanız önerilmektedir.

Yeni migration dosyasını kayıt edelim ve uygulayalım.

$ python manage.py migrate authors 
Operations to perform:
  Apply all migrations: authors
Running migrations:
  Applying authors.0003_auto_20210227_2239... OK

Bu işlem sonrasında tablo aşağıdaki hale geçecektir.

data table v2Şu durumda tüm yazarların full_name alanları istenilen değere sahip. Başarılı bir şekilde veriyi taşıdık.

Yapılacak son bir işlem kaldı, first_name ve last_name alanlarının modelden kaldırılması, nullable olan full_name alanının nullable olamayacak şekilde değiştirilmesi.

Modeli değiştirelim ve migration oluşturalım.

# authors/models.py
class Author(models.Model):

    birth_year = models.PositiveSmallIntegerField()
    full_name = models.CharField(max_length=255, null=False)

    def __str__(self) -> str:
        return f"{self.full_name}"
$ python manage.py makemigrations authors
You are trying to change the nullable field 'full_name' on author to non-nullable without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration)
 3) Quit, and let me add a default in models.py
Select an option:

Bu aşamada Django size nullable bir alanı değiştirmeye çalıştığınızı ve varolan kayıtları etkileyebileceğini söyleyecektir. (Hoop hemşerim nereye?)

Yazının başında da dediğimiz gibi Django data migration işlemlerini takip etmez. Sadece sizin belirlediğiniz sıralamada uygular.  Varolan dataların tutarlılığını, bir önceki migrationda gözeterek değişikliklerimizi yaptığımız için 2.seçeneği seçerek görmezden geliyor, oluşan migration dosyasını uyguluyoruz.

Migrations for 'authors':
  authors/migrations/0004_auto_20210227_2334.py
    - Remove field first_name from author
    - Remove field last_name from author
    - Alter field full_name on author
$ python manage.py migrate authors 
Operations to perform:
  Apply all migrations: authors
Running migrations:
  Applying authors.0004_auto_20210227_2334... OK

Bu son işlemle birlikte artık kullanılmayacak olan alanları kaldırmış, yeni alanı tekrar nullable olmayacak şekilde tanımlayarak şemayı son haline getirdik. Görevimizi tamamladık.

Sonuç.

Canlıda çalışan bir sistem üzerinde data migrationlar kurgulanırken, işe stratejik yaklaşılmalı ve işin iyi analiz edilmesi gerekmektedir. Canlı sistemin verisi her zaman önceliklidir. Yukarıdaki örnek basit olmasına rağmen, 3 adet migration oluşturulması ve sırası ile uygulanması gerekti. Yapılacak işlemleri sıraya koymak, farklı caseleri iyi analiz etmek önemlidir. (Örneğin var olan dataları, unique bir alana taşırken çakışma ihtimalleri vs) Bu tarz değişikleri canlıya uygulamadan önce lokalde veya stage ortamında deneyerek, beklenmedik kırılmaları görebilir ve veri kaybının önüne geçebilirsiniz.

Tabi ki data migrationları oluşturur iken yapılabilecekler bu kadarla sınırlı değil. Raw sql de koşturabilirsiniz, veya oluşturduğunuz migrationa farklı uygulamalardaki migration dosyalarını bağımlılık olarak ekleyebilir, daha kompleks işlemler ile verileri şema içerisinde taşıyabilirsiniz. Daha detaylı bilgi için dökümantasyon linkini buraya bırakıyorum.

İyi günler!

Tarih:Blog

Tek Yorum

  1. Hakan Hakan

    Çok açıklayıcı, güzel bir yazı olmuş, teşekkürler.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle