Django: outsource model properties with inheritanc

2019-08-24 10:20发布

问题:

I have noticed, that I need a generalized model based on a specified model, following example should show what I mean:

before:

class TextResult(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
    text = models.ForeignKey(Text)
    wpm = models.FloatField(default=0.0)
    accuracy = models.FloatField(default=1.0)

after:

class TypingResult(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
    wpm = models.FloatField(default=0.0)
    accuracy = models.FloatField(default=1.0)


class TextResult(TypingResult):
    text = models.ForeignKey(Text)

Though there is already some data in the original model, so it is necessary to migrate the data to the new modelstructure

回答1:

The following answer is based on this answer(https://stackoverflow.com/a/44148102/4129587)

In order to achieve that it is necessary to do a manual Data Migration

The following 5 basic migration steps lead to the desired result:

  1. create the new model TypingResult
  2. create a new foreign key that is nullable to the new model TypingResult in the old model TextResult
  3. copy all the old attributes to a new instance of the new model TypingResult
  4. remove the old attributes including the id from the original model
  5. alter the foreign key as the new primary key

It might be possible to start the migration with an autogenerated migration of the new models

The following code is based on an automatically generated migration and already tested

from __future__ import unicode_literals

from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion

def copy_text_results_to_typing_results(apps, schema_editor):
    TypingResult = apps.get_model('testapp', 'TypingResult')
    TextResult = apps.get_model('testapp', 'TextResult')
    for text_result in TextResult.objects.all():
        copied_result = TypingResult()
        copied_result.user = text_result.user
        copied_result.wpm = text_result.wpm
        copied_result.accuracy = text_result.accuracy
        copied_result.save()
        text_result.typingresult_ptr = copied_result
        text_result.save()

class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('testapp', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='TypingResult',
            fields=[
                ('id', models.AutoField(auto_created=True, 
                                        primary_key=True,
                                        serialize=False,
                                        verbose_name='ID')),
                ('wpm', models.FloatField(default=0.0)),
                ('accuracy', models.FloatField(default=1.0)),
                ('user', models.ForeignKey(default=1, 
                                           on_delete=django.db.models.deletion.CASCADE,
                                           to=settings.AUTH_USER_MODEL)),
            ],
        ),
        # add the foreign key for the new inherited model,
        # it is allowed to have null values since the actual values have to be
        # copied first to this, it will be changed later
        migrations.AddField(
            model_name='textresult',
            name='typingresult_ptr',
            field=models.OneToOneField(blank=True, null=True, to='testapp.TypingResult'),
        ),
        # copy the old values to the new inherited model
        migrations.RunPython(copy_text_results_to_typing_results),
        # remove the old id and the copied fields from the TextResult model
        migrations.RemoveField(
            model_name='textresult',
            name='accuracy',
        ),
        migrations.RemoveField(
            model_name='textresult',
            name='id',
        ),
        migrations.RemoveField(
            model_name='textresult',
            name='user',
        ),
        migrations.RemoveField(
            model_name='textresult',
            name='wpm',
        ),
        # alter the id of the inherited model to be the new primary key
        migrations.AlterField(
            model_name='textresult',
            name='typingresult_ptr',
            field=models.OneToOneField(auto_created=True,
                                       on_delete=django.db.models.deletion.CASCADE,
                                       parent_link=True,
                                       primary_key=True,
                                       serialize=False,
                                       to='testapp.TypingResult'),
        ),
    ]