# Τεκμηρίωση του Django - 1.10.7

# Γράφοντας migrations για τη βάση δεδομένων

Αυτό το άρθρο εξηγεί πως να δομήσετε και να γράψετε migrations για τη βάση δεδομένων (database migrations), βασιζόμενοι σε διαφορετικά σενάρια που μπορεί να συναντήσετε. Για μια εισαγωγή στα migrations, δείτε στο άρθρο migrations.

## Data migrations και πολλαπλές βάσεις δεδομένων

Όταν χρησιμοποιείτε πολλές βάσεις δεδομένων, συχνά θα βρίσκεστε σε δίλλημα να τρέξετε ένα migration σε κάποια συγκεκριμένη βάση δεδομένων. Για παράδειγμα, ίσως να θέλετε να τρέξετε ένα migration μόνο για μια συγκεκριμένη βάση δεδομένων.

Για να το κάνετε αυτό, ελέγξτε το alias της σύνδεσης της βάσης δεδομένων μέσα σε μια `RunPython` λειτουργία, κοιτώντας το attribute του `schema_editor.connection.alias`:

    from django.db import migrations

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

    class Migration(migrations.Migration):

        dependencies = [
            # Dependencies to other migrations
        ]

        operations = [
            migrations.RunPython(forwards),
        ]

Μπορείτε, επίσης, να δώσετε κάποια hints (πληροφορίες) οι οποίες θα περαστούν στη μέθοδο `allow_migrate()` των database routers ως `**hints`:

myapp/dbrouters.py

    class MyRouter(object):

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

Έπειτα, για να το αξιοποιήσετε αυτό στα migrations σας, κάντε τα ακόλουθα:

    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'}),
        ]

Αν η `RunPython` ή η `RunSQL` λειτουργία σας, επιδρά μόνο σε ένα μοντέλο, μια καλή πρακτική είναι να περάσετε το `model_name` ως hint για να το κάνετε όσο γίνεται πιο διαφανές στον router. Αυτό είναι ιδιαίτερα σημαντικό για επαναχρησιμοποιήσιμες και third-party εφαρμογές.

## Migrations τα οποία προσθέτουν unique πεδία

Εφαρμόζοντας ένα “απλό” migration το οποίο προσθέτει ένα μοναδικό μη-κενό πεδίο (unique non-nullable field) σε έναν πίνακα με ήδη υπάρχουσες γραμμές, θα κάνει raise κάποιο σφάλμα επειδή δεν είναι δυνατόν να αναπαραχθεί κάποια μοναδική τιμή για κάθε γραμμή της στήλης αυτής. Επίσης δεν γίνεται να ορίσουμε μια default τιμή καθώς δεν μπορεί κάθε γραμμή να έχει την ίδια τιμή για αυτή τη στήλη.

Για να λύσουμε αυτό το πρόβλημα, θα πρέπει να ακολουθήσουμε τα παρακάτω βήματα. Σε αυτό το παράδειγμα, θα προσθέσουμε ένα μη-κενό πεδίο του τύπου `UUIDField` με μια προεπιλεγμένη τιμή (default value). Τροποποιήστε το αντίστοιχο δικό σας πεδίο ανάλογα τις ανάγκες σας.

- Προσθέστε ένα πεδίο στο μοντέλο σας με τις παραμέτρους `default=uuid.uuid4` και `unique=True` (επιλέξτε μια κατάλληλη προεπιλεγμένη τιμή ανάλογα τον τύπο του πεδίου που πρόκειται να προσθέσετε).

- Τρέξτε την διαχειριστική εντολή `makemigrations`. Η εντολή αυτή θα δημιουργήσει ένα αρχείο migration με την λειτουργία του `AddField` μέσα του.

- Δημιουργήστε δύο κενά migration αρχεία για την ίδια εφαρμογή, τρέχοντας δύο φορές την εντολή `makemigrations myapp --empty`. Στο παράδειγμα μας, έχουμε μετονομάσει τα αρχεία των migrations για να τους δώσουμε πιο επεξηγηματικά ονόματα.

- Αντιγράψτε τη λειτουργία του `AddField` από το migration αρχείο που δημιουργήθηκε με την εντολή `makemigrations` (το πρώτο από τα τρία migration αρχεία) στο τελευταίο (το δεύτερο από τα δύο κενά) και αλλάξτε το `AddField` σε `AlterField`. Για παράδειγμα:

  0006_remove_uuid_null.py

      # -*- coding: utf-8 -*-
      # Generated by Django A.B on YYYY-MM-DD HH:MM
      from __future__ import unicode_literals

      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),
              ),
          ]

- Επεξεργαστείτε το πρώτο αρχείο του migration, το οποίο θα μοιάζει κάπως έτσι:

  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),
              ),
          ]

  Αλλάξτε το `unique=True` σε `null=True` – αυτό θα δημιουργήσει ένα μεσάζων κενό πεδίο που δεν περιέχει τον περιορισμό της μοναδικότητας (unique constraint) μέχρις ότου δημιουργήσουμε μοναδικές τιμές για κάθε γραμμή αυτής της στήλης.

- Στο πρώτο κενό migration αρχείο (το δεύτερο εκ των τριών), προσθέστε μια λειτουργία `RunPython` ή `RunSQL` για να δημιουργήσετε μια μοναδική τιμή (σε αυτό το παράδειγμα δημιουργούμε μια τιμή του τύπου UUID) για κάθε υπάρχουσα γραμμή. Για παράδειγμα:

  0005_populate_uuid_values.py

      # -*- coding: utf-8 -*-
      # Generated by Django A.B on YYYY-MM-DD HH:MM
      from __future__ import unicode_literals

      from django.db import migrations, models
      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()

      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),
          ]

- Τώρα μπορείτε να εφαρμόσετε τα migration αρχεία, ως συνήθως, με την διαχειριστική εντολή `migrate`.

  Σημειώστε ότι θα υπάρξει race condition αν επιτρέψετε να δημιουργηθούν objects όσο το migration τρέχει. Η τιμή της `uuid` των objects που θα δημιουργηθούν μεταξύ της λειτουργίας του `AddField` και της `RunPython` θα γίνει overwritten.

### Non-atomic migrations

Στις βάσεις δεδομένων που υποστηρίζουν DDL transactions (όπως η SQLite και η PostgreSQL), τα migrations θα τρέξουν μέσα σε ένα transaction από προεπιλογή. Για τις περιπτώσεις όπου θα πραγματοποιηθούν data migrations σε μεγάλους πίνακες, ίσως να θέλετε να αποτρέψετε το migration να τρέξει μέσα σε ένα transaction θέτοντας το attribute `atomic` σε `False`:

    from django.db import migrations

    class Migration(migrations.Migration):
        atomic = False

Μέσα σε ένα τέτοιο migration, όλες οι λειτουργίες θα τρέξουν εκτός κάποιου transaction. Μπορείτε, επίσης, να τρέξετε μερικά επιμέρους κομμάτια ενός migration μέσα σε ένα transaction χρησιμοποιώντας τη μέθοδο `atomic()` ή περνώντας την παράμετρο `atomic=True` στη λειτουργία του `RunPython`.

Παρακάτω φαίνεται ένα παράδειγμα ενός non-atomic data migration, το οποίο ενημερώνει έναν μεγάλο πίνακα ανά μικρότερα κομμάτια:

    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),
        ]

Το attribute `atomic` δεν επηρεάζει τις βάσεις δεδομένων οι οποίες δεν υποστηρίζουν τα DDL transactions (πχ MySQL, Oracle).

## Ελέγχοντας τη σειρά εκτέλεσης των migration αρχείων

Το Django δεν καθορίζει τη σειρά εκτέλεσης των migration αρχείων από το όνομα τους αλλά δημιουργώντας ένα γράφημα χρησιμοποιώντας δύο properties της κλάσης `Migration`: το `dependencies` και το `run_before`.

Αν έχετε χρησιμοποιήσει την διαχειριστική εντολή `makemigrations` θα έχετε δει, πιθανόν, το property `dependencies` που δημιουργείται αυτόματα μέσα σε κάθε migration αρχείο.

Το property `dependencies` ορίζεται ως εξής:

    from django.db import migrations

    class Migration(migrations.Migration):

        dependencies = [
            ('myapp', '0123_the_previous_migration'),
        ]

Συνήθως αυτό θα είναι αρκετό, αλλά μερικές φορές ίσως χρειαστεί να βεβαιωθείτε ότι το migration αρχείο σας τρέξει πριν τρέξουν άλλα migrations. Αυτό είναι χρήσιμο, για παράδειγμα, να κάνετε τα migration αρχεία τρίτων εφαρμογών να τρέξουν μετά την αντικατάσταση της δική σας ρύθμισης `AUTH_USER_MODEL`.

Για να το επιτύχετε αυτό, τοποθετήστε όλα τα migrations τα οποία εξαρτώνται από τα δικά σας, στο attribute `run_before` της κλάσης `Migration`:

    class Migration(migrations.Migration):
        ...

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

Προτιμήστε να χρησιμοποιείτε το `dependencies` αντί του `run_before` όπου αυτό είναι δυνατόν. Θα πρέπει να χρησιμοποιείτε το `run_before` μόνο όταν είναι μη επιθυμητό ή μη πρακτικό να προσδιορίσετε το `dependencies` σε ένα migration αρχείο το οποίο θέλετε να τρέξει μετά από αυτό που γράφετε.

## Κάνοντας migration των data μεταξύ third-party εφαρμογών

Μπορείτε να χρησιμοποιήσετε το data migration για να μετακινήσετε δεδομένα (data) από μια εφαρμογή τρίτου σε μια άλλη.

Αν σκοπεύετε να αφαιρέσετε αργότερα την παλιά εφαρμογή σας, θα χρειαστεί να ορίσετε το property `dependencies` βασιζόμενοι στο αν η παλιά σας εφαρμογή είναι εγκατεστημένη ή όχι. Σε διαφορετική περίπτωση, θα έχετε απόντα dependencies μόλις απεγκαταστήσετε την παλιά εφαρμογή. Ομοίως, θα χρειαστεί να κάνετε catch το exception `LookupError` μέσα στην κλήση της μεθόδου ``apps.get_model()``η οποία ανακτά τα μοντέλα από την παλιά εφαρμογή. Αυτή η μέθοδος σας επιτρέπει να ανεβάσετε το project σας σε κάποιον server (deployment) χωρίς να χρειάζεται, πρώτα, να εγκαταστήσετε και μετά να απεγκαταστήσετε την παλιά σας εφαρμογή.

Παρακάτω φαίνεται ένα δείγμα ενός migration αρχείου:

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'))

Επίσης, σκεφτείτε τι θα θέλατε να γίνει όταν το migration δεν εφαρμοστεί. Θα μπορούσατε να μην κάνετε τίποτα (όπως στο παραπάνω παράδειγμα) ή να αφαιρέσετε κάποια ή όλα τα data από την καινούργια εφαρμογή. Προσαρμόστε το δεύτερο argument της λειτουργίας του `RunPython` καταλλήλως.

## Αλλαγή ενός unmanaged μοντέλου σε managed

Αν θέλετε να αλλάξετε ένα unmanaged μοντέλο (`managed=False`) σε managed, θα πρέπει να αφαιρέσετε το `managed=False` και να δημιουργήσετε ένα migration προτού κάνετε άλλες αλλαγές στο μοντέλο σχετικά με το schema, αφού οι αλλαγές στο schema οι οποίες εμφανίζονται στο migration που περιέχει την αλλαγή του `Meta.managed` ίσως να μην εφαρμοστούν. Με άλλα λόγια, δημιουργήστε και τρέξτε ένα migration μετά την αφαίρεση του `managed=False` και κατόπιν μπορείτε ελεύθερα να κάνετε τις schema αλλαγές στο (managed) μοντέλο σας.