Wat is de beste aanpak om primaire sleutels in een bestaande Django-app te wijzigen?

Ik heb een applicatie die zich in de BETA-modus bevindt. Het model van deze app heeft enkele klassen met een expliciete primaire_sleutel. Als gevolg hiervan gebruikt Django de velden en maakt hij niet automatisch een id aan.

class Something(models.Model):
    name = models.CharField(max_length=64, primary_key=True)

Ik denk dat het een slecht idee was (zie unicode fout bij het opslaan van een object in django admin) en ik wil teruggaan en een id hebben voor elke klasse van mijn model.

class Something(models.Model):
    name = models.CharField(max_length=64, db_index=True)

Ik heb de wijzigingen in mijn model aangebracht (vervang elke primary_key=True door db_index=True) en ik wil de database migreren met zuiden.

Helaas mislukt de migratie met het volgende bericht:
ValueError: You cannot add a null=False column without a default value.

Ik evalueer de verschillende oplossingen voor dit probleem. Suggesties?

Bedankt voor je hulp


Antwoord 1, autoriteit 100%

Akkoord, uw model is waarschijnlijk verkeerd.

De formele primaire sleutel moet altijd een surrogaatsleutel zijn. Nooit iets anders. [Sterke woorden. Sinds de jaren 80 databaseontwerper. Een belangrijke geleerde les is deze: alles is veranderlijk, zelfs als de gebruikers op het graf van hun moeder zweren dat de waarde niet kan worden veranderd, is het echt een natuurlijke sleutel die als primaire kan worden beschouwd. Het is niet primair. Alleen surrogaten kunnen primair zijn.]

Je doet een openhartoperatie. Knoei niet met schemamigratie. Je vervangthet schema.

  1. Verwijder uw gegevens in JSON-bestanden. Gebruik hiervoor Django’s eigen interne django-admin.py tools. U moet één unload-bestand maken voor elk bestand dat wordt gewijzigd en voor elke tabel die afhankelijk is van een sleutel die wordt gemaakt. Aparte bestanden maken dit iets gemakkelijker om te doen.

  2. Laat de tabellen vallen die u van het oude schema gaat wijzigen.

    Voor tabellen die afhankelijk zijn van deze tabellen, wordt de FK gewijzigd; je kan of
    update de rijen op hun plaats of — het is misschien eenvoudiger — om te verwijderen en opnieuw in te voegen
    deze rijen ook.

  3. Maak het nieuwe schema. Dit zal alleen de tabellen maken die veranderen.

  4. Schrijf scripts om de gegevens te lezen en opnieuw te laden met de nieuwe sleutels. Deze zijn kort en lijken erg op elkaar. Elk script gebruikt json.load()om objecten uit het bronbestand te lezen; u maakt vervolgens uw schema-objecten van de JSON-tuple-line-objecten die voor u zijn gebouwd. U kunt ze vervolgens in de database invoegen.

    Je hebt twee gevallen.

    • Tabellen met gewijzigde PK’s worden ingevoegd en krijgen nieuwe PK’s. Deze moeten worden “gecascadeerd” naar andere tafels om ervoor te zorgen dat de FK’s van de andere tafels ook worden gewijzigd.

    • Tabellen met FK’s die veranderen, moeten de rij in de vreemde tabel zoeken en hun FK-referentie bijwerken.

Alternatief.

  1. Hernoem al je oude tabellen.

  2. Maak het geheel nieuwe schema.

  3. Schrijf SQL om alle gegevens van het oude schema naar het nieuwe schema te migreren. Dit zal op een slimme manier toetsen opnieuw moeten toewijzen.

  4. Laat de hernoemde oude tabellen vallen.

 


Antwoord 2, autoriteit 12%

Als u de primaire sleutel wilt wijzigen in zuid, kunt u de opdracht south.db.create_primary_key gebruiken in datamigratie.
Als u uw aangepaste CharField pk wilt wijzigen in standaard AutoField, moet u het volgende doen:

1) maak een nieuw veld in uw model

class MyModel(Model):
    id = models.AutoField(null=True)

1.1) als je een refererende sleutel hebt in een ander model voor dit model, maak dan ook een nieuw nep fk-veld op dit model aan (gebruik IntegerField, het wordt dan geconverteerd)

class MyRelatedModel(Model):
    fake_fk = models.IntegerField(null=True)

2) automatische migratie naar het zuiden maken en migreren:

./manage.py schemamigration --auto
./manage.py migrate

3) nieuwe datamigratie maken

./manage.py datamigration <your_appname> fill_id

vul in deze datamigratie deze nieuwe id- en fk-velden met getallen (noem ze gewoon op)

   for n, obj in enumerate(orm.MyModel.objects.all()):
        obj.id = n
        # update objects with foreign keys
        obj.myrelatedmodel_set.all().update(fake_fk = n)
        obj.save()
    db.delete_primary_key('my_app_mymodel')
    db.create_primary_key('my_app_mymodel', ['id'])

4) in uw modellen set primary_key=True op uw nieuwe pk-veld

id = models.AutoField(primary_key=True)

5) verwijder het oude primaire sleutelveld (als het niet nodig is) maak automatische migratie en migreer.

5.1) als u externe sleutels heeft – verwijder ook oude velden met externe sleutels (migreren)

6) Laatste stap – herstel fireign key-relaties. Maak opnieuw een echt fk-veld en verwijder uw fake_fk-veld, maak automatische migratie MAAR MIGREER NIET(!) – u moet de gemaakte automatische migratie wijzigen: in plaats van een nieuwe fk te maken en fake_fk te verwijderen – hernoem de kolom fake_fk

# in your models
class MyRelatedModel(Model):
    # delete fake_fk
    # fake_fk = models.InegerField(null=True)
    # create real fk
    mymodel = models.FoeignKey('MyModel', null=True)
# in migration
    def forwards(self, orm):
        # left this without change - create fk field
        db.add_column('my_app_myrelatedmodel', 'mymodel',
                  self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='lots', to=orm['my_app.MyModel']),keep_default=False)
        # remove fk column and rename fake_fk
        db.delete_column('my_app_myrelatedmodel', 'mymodel_id')
        db.rename_column('my_app_myrelatedmodel', 'fake_fk', 'mymodel_id')

zodat eerder ingevulde fake_fk een kolom wordt, die actuele relatiegegevens bevat, en deze gaat niet verloren na alle bovenstaande stappen.


Antwoord 3, autoriteit 10%

Het is me gelukt met django 1.10.4-migraties en mysql 5.5, maar het was niet gemakkelijk.

Ik had een primaire varchar-sleutel met verschillende externe sleutels. Ik heb een veld idtoegevoegd, gemigreerde gegevens en externe sleutels. Dit is hoe:

  1. Toekomstig veld voor primaire sleutel toevoegen. Ik heb een veld id = models.IntegerField(default=0)toegevoegd aan mijn hoofdmodel en een automatische migratie gegenereerd.
  2. Eenvoudige gegevensmigratie om nieuwe primaire sleutels te genereren:

    def fill_ids(apps, schema_editor):
       Model = apps.get_model('<module>', '<model>')
       for id, code in enumerate(Model.objects.all()):
           code.id = id + 1
           code.save()
    class Migration(migrations.Migration):
        dependencies = […]
        operations = [migrations.RunPython(fill_ids)]
    
  3. Bestaande externe sleutels migreren. Ik heb een gecombineerde migratie geschreven:

    def change_model_fks(apps, schema_editor):
        Model = apps.get_model('<module>', '<model>')  # Our model we want to change primary key for
        FkModel = apps.get_model('<module>', '<fk_model>')  # Other model that references first one via foreign key
        mapping = {}
        for model in Model.objects.all():
            mapping[model.old_pk_field] = model.id  # map old primary keys to new
        for fk_model in FkModel.objects.all():
            if fk_model.model_id:
                fk_model.model_id = mapping[fk_model.model_id]  # change the reference
                fk_model.save()
    class Migration(migrations.Migration):
        dependencies = […]
        operations = [
            # drop foreign key constraint
            migrations.AlterField(
                model_name='<FkModel>',
                name='model',
                field=models.ForeignKey('<Model>', blank=True, null=True, db_constraint=False)
            ),
            # change references
            migrations.RunPython(change_model_fks),
            # change field from varchar to integer, drop index
            migrations.AlterField(
                model_name='<FkModel>',
                name='model',
                field=models.IntegerField('<Model>', blank=True, null=True)
            ),
        ]
    
  4. Primaire sleutels verwisselen en externe sleutels herstellen. Nogmaals, een aangepaste migratie. Ik heb automatisch de basis voor deze migratie gegenereerd toen ik a) primary_key=Trueverwijderde van de oude primaire sleutel en b) het veld id

    verwijderde

    class Migration(migrations.Migration):
        dependencies = […]
        operations = [
            # Drop old primary key
            migrations.AlterField(
                model_name='<Model>',
                name='<old_pk_field>',
                field=models.CharField(max_length=100),
            ),
            # Create new primary key
            migrations.RunSQL(
                ['ALTER TABLE <table> CHANGE id id INT (11) NOT NULL PRIMARY KEY AUTO_INCREMENT'],
                ['ALTER TABLE <table> CHANGE id id INT (11) NULL',
                 'ALTER TABLE <table> DROP PRIMARY KEY'],
                state_operations=[migrations.AlterField(
                    model_name='<Model>',
                    name='id',
                    field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
                )]
            ),
            # Recreate foreign key constraints
            migrations.AlterField(
                model_name='<FkModel>',
                name='model',
                field=models.ForeignKey(blank=True, null=True, to='<module>.<Model>'),
        ]
    

Antwoord 4, autoriteit 6%

Momenteel faal je omdat je een pk-kolom toevoegt die niet voldoet aan de NOT NULL- en UNIQUE-vereisten.

Je moet de migratie opsplitsen in verschillende stappen, scheiding van schemamigraties en gegevensmigraties:

  • voeg de nieuwe kolom toe, geïndexeerd maar niet de primaire sleutel, met een standaardwaarde (ddl-migratie)
  • de gegevens migreren: vul de nieuwe kolom met de juiste waarde (gegevensmigratie)
  • markeer de primaire sleutel van de nieuwe kolom en verwijder de voormalige pk-kolom als deze overbodig is geworden (ddl-migratie)

Antwoord 5, autoriteit 6%

Ik had vandaag hetzelfde probleem en kwam tot een oplossing geïnspireerd door de bovenstaande antwoorden.

Mijn model heeft een tabel ‘Locatie’. Het heeft een CharField genaamd “unique_id” en ik heb er vorig jaar dwaas een primaire sleutel van gemaakt. Natuurlijk bleken ze niet zo uniek te zijn als destijds verwacht. Er is ook een “ScheduledMeasurement”-model met een externe sleutel voor “Locatie”.

Nu wil ik die fout corrigeren en Locatie een gewone, automatisch oplopende primaire sleutel geven.

Genomen stappen:

  1. Maak een CharField ScheduledMeasurement.temp_location_unique_id en een model TempLocation, en migraties om ze te maken. TempLocation heeft de structuur die ik wil dat Location heeft.

  2. Maak een gegevensmigratie die alle temp_location_unique_id’s instelt met behulp van de externe sleutel, en die alle gegevens kopieert van Location naar TempLocation

  3. Verwijder de refererende sleutel en de locatietabel met een migratie

  4. Maak het locatiemodel opnieuw zoals ik het wil, maak de externe sleutel opnieuw met null=True. Hernoemd ‘unique_id’ naar ‘location_code’…

  5. Maak een gegevensmigratie die de gegevens in Locatie invult met behulp van TempLocation en de externe sleutels in ScheduledMeasurement invult met behulp van temp_location

  6. Verwijder temp_location, TempLocation en null=True in de refererende sleutel

en bewerk alle code die aangenomen dat unique_id uniek was (alle objecten.get (unique_id = …) dingen), en dat gebruikte unique_id anders …


6

Ik heb deze aanpak gewoon geprobeerd en het lijkt te werken, voor Django 2.2.2, maar werk alleen voor Sqlite. Deze methode proberen in andere database zoals PostGres SQL, maar werkt niet.

  1. Voeg id=models.IntegerField()toe om te modelleren, migreren en migreren, geef een eenmalige standaard zoals 1

  2. Gebruik de python-shell om een ​​ID te genereren voor alle objecten in het model van 1 tot N

  3. verwijder primary_key=Trueuit het primaire sleutelmodel en verwijder id=models.IntegerField(). Maak migratie en controleer de migratie en u zou moeten zien dat het id-veld wordt gemigreerd naar automatisch veld.

Het zou moeten werken.

Ik wist niet wat ik deed met het plaatsen van de primaire sleutel in een van de velden, maar als ik niet zeker weet hoe ik de primaire sleutel moet verwerken, denk ik dat het beter is om Django dit voor je te laten regelen.


Antwoord 7

Ik wil graag mijn case delen: de kolom emailwas de primaire sleutel, maar dat is nu verkeerd. Ik moet de primaire sleutel in een andere kolom veranderen. Na wat suggesties geprobeerd te hebben, kwam ik uiteindelijk op de meest eenvoudige oplossing:

  1. Laat eerst de oude primaire sleutel vallen. Voor deze stap zijn de migraties een beetje aangepast:
  • bewerk het model om primary_key=Truein de e-mailkolom te vervangen door blank=True, null=True
  • voer makemigrationsuit om een ​​nieuw migratiebestand te maken en bewerk het als volgt:
class Migration(migrations.Migration):
    dependencies = [
        ('api', '0026_auto_20200619_0808'),
    ]
    operations = [
        migrations.RunSQL("ALTER TABLE api_youth DROP CONSTRAINT api_youth_pkey"),
        migrations.AlterField(
            model_name='youth', name='email',
            field=models.CharField(blank=True, max_length=200, null=True))
    ]
  • voer migreren uit
  1. Je tabel heeft nu geen primaire sleutel, je kunt een nieuwe kolom toevoegen of een oude kolom gebruiken als primaire sleutel. Verander gewoon het model en migreer vervolgens. Gebruik wat extra script als je een nieuwe kolom nodig hebt om te vullen en zorg ervoor dat deze alleen unieke waarden bevat.

Other episodes