# Dokumentasi Django 6.0.7

# Bagaimana membuat perpindahan basisdata

Dokumen ini menjelaskan bagaimana menyusun dan menulis perpindahan basisdata untuk skenario berbeda anda mungkin hadapi. Untuk bahan perkenalan pada perpindahan, lihat the topic guide.

## Perpindahan data dan banyak basisdata

Ketika menggunakan banyak basisdata, anda mungkin butuh memahami apakat atau tidak menjalankan perpindahan terhadap basisdata tertentu. Sebagai contoh, anda ingin hanya menjalankan perpindahan pada basisdata tertentu.

Untuk melakukan itu anda dapat memeriksa nama lain hubungan basisdata didalam operasi `RunPython` dengan mencari atribut `schema_editor.connection.alias`:

    from django.db import migrations

    def forwards(apps, schema_editor):
        if schema_editor.connection.alias != "default":
            return
        # Your migration code goes here

    class Migration(migrations.Migration):
        dependencies = [
            # Dependencies to other migrations
        ]

        operations = [
            migrations.RunPython(forwards),
        ]

You can also provide hints that will be passed to the `allow_migrate()` method of database routers as `**hints`:

`myapp/dbrouters.py`

    class MyRouter:
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            if "target_db" in hints:
                return db == hints["target_db"]
            return True

Kemudian, untuk mempengaruhi ini dalam pemindahan anda, lakukan berikut:

    from django.db import migrations

    def forwards(apps, schema_editor):
        # Your migration code goes here
        ...

    class Migration(migrations.Migration):
        dependencies = [
            # Dependencies to other migrations
        ]

        operations = [
            migrations.RunPython(forwards, hints={"target_db": "default"}),
        ]

Jika operasi `RunPython` atau `RunSQL` anda hanya berpengaruh pada satu model, adalah latihan bagus untuk melewatkan `model_name` sebagai sebuah bayangan untuk membuatnya se transparan mungkin ke router. Ini khususnya penting untuk aplikasi digunakan kembali dan pihak ketiga.

## Perpindahan yang menambah bidang unik

Memberlakukan perpindahan "plain" yang menambahkan bidang bukan null unik ke sebuah tabel dengan baris yang ada akan menampilkan sebuah kesalahan karena nilai digunakan untuk mengumpulkan baris-baris yang ada dibangkitkan hanya sekali, demikian merusak pembatas unik.

Karena itu, langkah-langkah berikut harus diambil. Dalam contoh ini, kami akan menambahkan `UUIDField` bukan null dengan nilai awal. Rubah bidang masing-masing menurut kebutuhan anda.

- Tambah bidang pada model anda dengan argumen `default=uuid.uuid4` dan `unique=True` (pilih awal yang sesuai untuk jenis dari bidang anda sedang tambahkan).

- Jalankan perintah `makemigrations`. Ini harus membangkitkan perpindahan dengan tindakan `AddField`.

- Bangkitkan dua berkas-berkas perpindahan kosong untuk aplikasi sama dengan menjalankan `makemigrations myapp --empty` dua kali. Kami telah menamai kembali berkas-berkas perpindahan untuk memberikan mereka nama berarti di contoh dibawah.

- Salin operasi `AddField` dari perpindahan dibangkitkan otomatis (pertama dari tiga berkas-berkas baru) ke perpindahan terakhir, rubah `AddField` ke `AlterField`, dan impor dari `uuid` dan `models`. Sebagai contoh:

  `0006_remove_uuid_null.py`

      # Generated by Django A.B on YYYY-MM-DD HH:MM
      from django.db import migrations, models
      import uuid

      class Migration(migrations.Migration):
          dependencies = [
              ("myapp", "0005_populate_uuid_values"),
          ]

          operations = [
              migrations.AlterField(
                  model_name="mymodel",
                  name="uuid",
                  field=models.UUIDField(default=uuid.uuid4, unique=True),
              ),
          ]

- Sunting berkas perpindahan pertama. Kelas perpindahan dibangkitkan harus kelihatan mirip seperti ini:

  `0004_add_uuid_field.py`

      class Migration(migrations.Migration):
          dependencies = [
              ("myapp", "0003_auto_20150129_1705"),
          ]

          operations = [
              migrations.AddField(
                  model_name="mymodel",
                  name="uuid",
                  field=models.UUIDField(default=uuid.uuid4, unique=True),
              ),
          ]

  Rubah `unique=True` menjadi `null=True` -- ini akan membuat perantara bidang null dan menunda pembuatan pembatas unik sampai kami telah mengumpulkan nilai-nilai unik pada semua baris.

- Dalam berkas perpindahan kosong pertama, tambah sebuah tindakan `RunPython` atau `RunSQL` untuk membangkitkan nilai unik (UUID dalam contoh) untuk setiap baris yang yang ada. Juga tambah sebuah impor dari `uuid`. Sebagai contoh:

  `0005_populate_uuid_values.py`

      # Generated by Django A.B on YYYY-MM-DD HH:MM
      from django.db import migrations
      import uuid

      def gen_uuid(apps, schema_editor):
          MyModel = apps.get_model("myapp", "MyModel")
          for row in MyModel.objects.all():
              row.uuid = uuid.uuid4()
              row.save(update_fields=["uuid"])

      class Migration(migrations.Migration):
          dependencies = [
              ("myapp", "0004_add_uuid_field"),
          ]

          operations = [
              # omit reverse_code=... if you don't want the migration to be reversible.
              migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
          ]

- Sekarang anda dapat berlakukan perpindahan seperti biasa dengan perintah `migrate`

  Catat ada kondisi jarang jika anda mengizinkan obyek untuk dibuat selagi perpindahan ini berjalan. Obyek dibuat setelah `AddField` dan sebelum `RunPython` akan mempunyai `uuid` asli mereka ditulis kembali.

### Perpindahan Non-atomic

Pada basisdata yang mendukung transaksi DDL (SQLite dan PostgreSQL), perpindahan akan berjalan di dalam sebuah transaksi secara awal. Untuk penggunakan kasus seperti melakukan perpindahan data pada tabel-tabel besar, anda mungkin ingin mencegah perpindahan dari berjalan dalam sebuah transaksi dengan mengatur atribut `atomic` menjadi `False`:

    from django.db import migrations

    class Migration(migrations.Migration):
        atomic = False

Within such a migration, all operations are run without a transaction. It's possible to execute parts of the migration inside a transaction using `atomic()` or by passing `atomic=True` to `RunPython`.

Ini adalah sebuah contoh dari perpindahan data non-atomic yang memperbaharui tabel besar dalam kumpulan-kumpulan kecil:

    import uuid

    from django.db import migrations, transaction

    def gen_uuid(apps, schema_editor):
        MyModel = apps.get_model("myapp", "MyModel")
        while MyModel.objects.filter(uuid__isnull=True).exists():
            with transaction.atomic():
                for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
                    row.uuid = uuid.uuid4()
                    row.save()

    class Migration(migrations.Migration):
        atomic = False

        operations = [
            migrations.RunPython(gen_uuid),
        ]

Atribut `atomic` tidak mempunyai pengaruh pada basisdata yang tidak mendukung transaksi DDL (yaitu MySQL, Oracle). (atomic DDL statement support MySQL mengacu pada pernyataan masing-masing daripada banyak pernyataan dibungkus dalam sebuah transaksi yang dapat di kembalikan.)

## Mengendalikan urutan dari pemindahan

Django menentukan urutan dalam perpindahan mana harus diberlakukan tidak berdasarkan nama berkas dari setiap perpindahan, tetapi dengan membangun sebuah grafik menggunakan dua alat pada kelas `Migration`: `dependencies` dan `run_before`.

Jika anda telah menggunakan perintah `makemigrations` anda mungkin seudah melihat `dependencies` dalam tindakan karena perpindahan dibuat-otomatis mempunyai penentuan ini sebagai bagian dari pengolahan pembuatan mereka.

Properti `dependencies` diumumkan seperti ini:

    from django.db import migrations

    class Migration(migrations.Migration):
        dependencies = [
            ("myapp", "0123_the_previous_migration"),
        ]

Biasanya ini akan cukup, tetapi dari waktu ke waktu anda mungkin butuh memastikan bahwa perpindahan anda berjalan sebelum perpindahan lain. Ini berguna, sebagai contoh, untuk membuat perpindahan aplikasi pihak ketiga berjalan setelah pergantian `AUTH_USER_MODEL` anda.

Untuk mencapai ini, tempatkan semua perpindahan yang harus tergantung pada diri anda dalam atribut `run_before` pada kelas `Migration` anda.

    class Migration(migrations.Migration):
        ...

        run_before = [
            ("third_party_app", "0001_do_awesome"),
        ]

Lebih suka menggunakan `dependencies` daripada `run_before` ketika memungkinkan. Anda harus hanya menggunakan `run_before` jika dia tidak diinginkan atau tidak praktis untuk menentukan `dependencies` dalam perpindahan yang anda ingin jalankan setelah satu anda sedang tulis.

## Memindahkan data diantara aplikasi pihak-ketiga

Anda dapat menggunakan perpindahan data untuk memindahkan data dari satu aplikasi pihak-ketiga ke lainnya.

Jika anda berencana memindahkan aplikasi lama nanti, anda akan butuh menyetel properti `dependencies` berdasarkan pada apakah atau tidak aplikasi lama terpasang. Jika tidak, anda akan kehilangan ketergantungan sekali anda melepas aplikasi lama. Sama halnya, anda akan butuh menangkap `LookupError` di panggilan `apps.get_model()` yang mengambil model-model dari aplikasi lama. Pendekatan ini mengizinkan anda menyebarkan proyek anda dimanapun tanpa memasang dan melepas aplikasi lama terlebih dahulu.

Ini adalah contoh perpindahan:

`myapp/migrations/0124_move_old_app_to_new_app.py`

    from django.apps import apps as global_apps
    from django.db import migrations

    def forwards(apps, schema_editor):
        try:
            OldModel = apps.get_model("old_app", "OldModel")
        except LookupError:
            # The old app isn't installed.
            return

        NewModel = apps.get_model("new_app", "NewModel")
        NewModel.objects.bulk_create(
            NewModel(new_attribute=old_object.old_attribute)
            for old_object in OldModel.objects.all()
        )

    class Migration(migrations.Migration):
        operations = [
            migrations.RunPython(forwards, migrations.RunPython.noop),
        ]
        dependencies = [
            ("myapp", "0123_the_previous_migration"),
            ("new_app", "0001_initial"),
        ]

        if global_apps.is_installed("old_app"):
            dependencies.append(("old_app", "0001_initial"))

Juga pertimbangkan apa anda inginkan terjadi ketika perpindahan tidak diberlakukan. Anda dapat juga tidak melakukan apapun (seperti di contoh diatas) atau memindahkan beberapa atau semua data dari apliaksi baru. Sesuaikan argumen kedua dari operasi `RunPython` sesuai dengan itu.

## Mengubah `ManyToManyField` untuk menggunakan `through` model

Jika anda merubah `ManyToManyField` untuk menggunakan model `through`, perpindahan awalan akan menghapus tabel yang ada dan membuat satu yang baru, menghilangkan kaitan yang ada. Untuk menghindari ini, anda dapat menggunakan `SeparateDatabaseAndState` untuk menamai kembali tabel yang ada ke nama tabel baru selagi memberitahu perpindahan detektor otomatis dimana modek baru telah dibuat. Anda dapat memeriksa nama tabel yang ada dan membatasi nama melalui `sqlmigrate` atau `dbshell`. Anda dapat memeriksa nama tabel baru dengan properti `_meta.db_table` pada model tersebut.. Model baru anda `through` harus menggunakan nama sama untuk `ForeignKey` seperti Django lakukan. Juga jika dia membutuhkan bidang tambahan, mereka harus ditambahkan dalam tindakan setelah `SeparateDatabaseAndState`.

Sebagai contoh, jika kita memiliki model `Book` dengan `ManyToManyField` yang mengarah ke Author, kita bisa menambahkan model antara `AuthorBook` dengan field baru `is_primary`, seperti ini:

    from django.db import migrations, models
    import django.db.models.deletion

    class Migration(migrations.Migration):
        dependencies = [
            ("core", "0001_initial"),
        ]

        operations = [
            migrations.SeparateDatabaseAndState(
                database_operations=[
                    # Old table name from checking with sqlmigrate, new table
                    # name from AuthorBook._meta.db_table.
                    migrations.RunSQL(
                        sql="ALTER TABLE core_book_authors RENAME TO core_authorbook",
                        reverse_sql="ALTER TABLE core_authorbook RENAME TO core_book_authors",
                    ),
                ],
                state_operations=[
                    migrations.CreateModel(
                        name="AuthorBook",
                        fields=[
                            (
                                "id",
                                models.AutoField(
                                    auto_created=True,
                                    primary_key=True,
                                    serialize=False,
                                    verbose_name="ID",
                                ),
                            ),
                            (
                                "author",
                                models.ForeignKey(
                                    on_delete=django.db.models.deletion.DO_NOTHING,
                                    to="core.Author",
                                ),
                            ),
                            (
                                "book",
                                models.ForeignKey(
                                    on_delete=django.db.models.deletion.DO_NOTHING,
                                    to="core.Book",
                                ),
                            ),
                        ],
                        options={
                            "constraints": [
                                models.UniqueConstraint(
                                    fields=["author", "book"],
                                    name="unique_author_book",
                                )
                            ],
                        },
                    ),
                    migrations.AlterField(
                        model_name="book",
                        name="authors",
                        field=models.ManyToManyField(
                            to="core.Author",
                            through="core.AuthorBook",
                        ),
                    ),
                ],
            ),
            migrations.AddField(
                model_name="authorbook",
                name="is_primary",
                field=models.BooleanField(default=False),
            ),
        ]

## Merubah model tidak dikendalikan menjadi dikendalikan

Jika anda ingin merubah sebuah model tidak dikendalikan (`managed=False`) menjadi dikendalikan, anda harus memindahkan `managed=False` dan bangkitkan perpindahan sebelum membuat perubahan skema-terhubung lainnya ke model, sejak perubahan skema yang muncul di perpindahan yang mengandung tindakan untuk merubah `Meta.managed` mungkin tidak diberlakukan.