Vaughan
Vaughan

Reputation: 411

Django trying to add an existing field when making a model managed

How can I stop Django 2.2.4 from trying to create a database column that already exists when making a model managed?

I have 2 models, ticket and message, which were connected to tables in a third-party database so the models were created with managed=False. I'm moving away from the third-party tool. The ticket model was change to managed=True a while ago by somebody else, and now I'm trying to do the same with the message model.

These are the relevant parts of the model:

from django.db import models

class Message(models.Model):
    mid = models.BigAutoField(db_column='MID', primary_key=True)
    ticket = models.ForeignKey('Ticket', on_delete=models.CASCADE, db_column='TID')
    author = models.CharField(db_column='AUTHOR', max_length=32)
    date = models.DateTimeField(db_column='DATE')
    internal = models.CharField(db_column='INTERNAL', max_length=1)
    isoper = models.CharField(db_column='ISOPER', max_length=1)
    headers = models.TextField(db_column='HEADERS')
    msg = models.TextField(db_column='MSG')

    class Meta:
        # managed = False
        db_table = 'messages'
        permissions = (
            ("can_change_own_worked_time", "Can change own worked time"),
            ("can_change_own_recently_worked_time", "Can change own recently worked time"),
            ("can_change_subordinate_worked_time", "Can change subordinate worked time"),
        )

This are the migrations that get generated by commenting out managed=False:

# Generated by Django 2.2.4 on 2020-06-18 20:56 (0017_auto_20200618_1656)

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('troubleticket', '0016_auto_20200511_1644'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='message',
            options={'permissions': (('can_change_own_worked_time', 'Can change own worked time'), ('can_change_own_recently_worked_time', 'Can change own recently worked time'), ('can_change_subordinate_worked_time', 'Can change subordinate worked time'))},
        ),
    ]
# Generated by Django 2.2.4 on 2020-06-18 21:14 (0018_message_ticket)

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


class Migration(migrations.Migration):

    dependencies = [
        ('troubleticket', '0017_auto_20200618_1656'),
    ]

    operations = [
        migrations.AddField(
            model_name='message',
            name='ticket',
            field=models.ForeignKey(db_column='TID', default=1, on_delete=django.db.models.deletion.CASCADE, to='troubleticket.Ticket'),
            preserve_default=False,
        ),
    ]

When I try to apply those migrations I get this error:

django.db.utils.OperationalError: (1060, "Duplicate column name 'TID'")

The initial migration didn't include the TID column and neither do any of the subsequent migrations so I understand why Django thinks it's a new column. But it isn't a new column (the model has had it since the first time it was committed to the git repo) so I also understand why MySQL is throwing an error.

This is the initial migration:

# Generated by Django 2.0.8 on 2018-08-20 14:43 (0001_initial)

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Message',
            fields=[
                ('mid', models.BigAutoField(db_column='MID', primary_key=True, serialize=False)),
                ('author', models.CharField(db_column='AUTHOR', max_length=32)),
                ('date', models.DateTimeField(db_column='DATE')),
                ('internal', models.CharField(db_column='INTERNAL', max_length=1)),
                ('isoper', models.CharField(db_column='ISOPER', max_length=1)),
                ('headers', models.TextField(db_column='HEADERS')),
                ('msg', models.TextField(db_column='MSG')),
            ],
            options={
                'managed': False,
                'db_table': 'messages',
            },
        ),
        migrations.CreateModel(
            name='Ticket',
            fields=[
                ('id', models.BigIntegerField(db_column='ID', primary_key=True, serialize=False)),
                ('accesskey', models.CharField(db_column='ACCESSKEY', max_length=64)),
                ('open', models.DateTimeField(db_column='OPEN')),
                ('updated', models.DateTimeField(db_column='UPDATED')),
                ('closed', models.DateTimeField(db_column='CLOSED', null=True)),
                ('status', models.CharField(db_column='STATUS', max_length=3)),
                ('oper', models.CharField(db_column='OPER', max_length=32)),
                ('email', models.CharField(db_column='EMAIL', max_length=128)),
                ('name', models.CharField(db_column='NAME', max_length=128)),
                ('subject', models.CharField(db_column='SUBJECT', max_length=255)),
                ('lname', models.CharField(db_column='LNAME', max_length=50)),
                ('company', models.CharField(db_column='C0', max_length=255)),
                ('type', models.CharField(db_column='C1', max_length=255)),
                ('c2', models.CharField(db_column='C2', max_length=255)),
                ('c3', models.DecimalField(db_column='C3', decimal_places=2, max_digits=6)),
                ('c4', models.CharField(db_column='C4', max_length=255)),
                ('pending', models.CharField(db_column='C5', max_length=255)),
                ('c6', models.CharField(db_column='C6', max_length=255)),
                ('c7', models.CharField(db_column='C7', max_length=255)),
                ('c8', models.CharField(db_column='C8', max_length=255)),
                ('cc', models.CharField(db_column='C9', max_length=255)),
                ('grp', models.CharField(db_column='GRP', max_length=10)),
                ('item', models.CharField(db_column='ITEM', max_length=255)),
            ],
            options={
                'managed': False,
                'db_table': 'tickets',
            },
        ),
    ]

Upvotes: 0

Views: 889

Answers (1)

1D0BE
1D0BE

Reputation: 191

2 Seconds after writing my comment I figured out how to solve the problem.

When 'migrating' from another ORM to Django always consider the following aspects. The following is my recommendation of order, but I'm still learning how to use django migrations, so keep that in mind.

1. Consider which fields of the model are currently known to django

It is important to note, that the actual content of the database does not matter to django when calculating which columns to add or alter. This means that, in the initial migration of the django models, where managed is still =False, the columns are "created" and registered by django. When makemigrations calculates which columns to add, it only takes the columns mentioned in the initial migration as given. Not the actual contents of the db.

Knowing that, we can now go field for field and decide for each one according to point 2.

2. Consider which fields of the model should or should not be created by django

Now, generally, when setting a Model managed=True fields will fall into 3 categories.

2.1 The field is currently in the model definition, the database, and is also in the initial commit.

For this case, no actions need to be taken.

2.2 The field is currently in the model definition, the database, but is not in the initial commit.

For this case, the field definition must be added to the original initial migration script. As such, the migrations.CreateModel call might look like this:

...
('field_already_present', models.FloatField(blank=True, null=True)),
...

2.3 The field is currently in the database, but not in the model definition or the initial commit

If the field is not needed in the django application it can be left out. If it is needed at some point it will have to be added to the model as well as the first initial migration. That way, django makemigrations will not attempt to create the field.

3. Set managed=True

Now set managed=True and make your migrations. Do so twice! The first time, makemigrations will set the model to managed, then it will add fields not in the initial commit.

After this, the model can be treated same as a normally created and managed-from-the-start django model.

Upvotes: 1

Related Questions