Related
Suppose I have a reusable app, that defines a model Person and a model Invite.
A Person has a OneToOne field to AUTH_USER_MODEL and defines some basic fields (such as birthday). It is a swappable model, so that a project which uses this app can easily add other fields (such as gender, etc.)
In my reusable app, I define a setting that provides the swapping model (otherwise, a default one will be used, exactly as django.contrib.auth does it.
The Invite model has a OneToOneField to the swappable Person model and an email field. (I think, it's quite clear what this model is for). The model itself is swappable as well, but I don't think that this makes any difference for the kind of problem I am facing.
reusable app models:
class AbstractPerson(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='person')
birthdate = models.DateField()
class Meta:
abstract = True
class Person(AbstractPerson):
class Meta(AbstractPerson.Meta):
swappable = 'REUSABLEAPP_PERSON_MODEL'
class AbstractInvite(models.Model):
email = models.EmailField()
person = models.OneToOneField(settings.REUSABLEAPP_PERSON_MODEL, on_delete=models.CASCADE, null=False, related_name='+')
class Meta:
abstract = True
class Invite(AbstractInvite):
class Meta(AbstractInvite.Meta):
swappable = 'REUSABLEAPP_INVITE_MODEL'
If I create the initial migration for my reusable app (using a dummy project and not swapping out my models), I get the following migration for my reusable app:
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Person',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('birthdate', models.DateField()),
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='person', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
'swappable': 'REUSABLEAPP_PERSON_MODEL',
},
),
migrations.CreateModel(
name='Invite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('person', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.REUSABLEAPP_PERSON_MODEL)),
],
options={
'abstract': False,
'swappable': 'REUSABLEAPP_INVITE_MODEL',
},
),
]
If I then include my reusable app in another project, and swap out the Person and Invite model, I get an error when running makemigrations:
ValueError: The field myreusable_app.Invite.person was declared with a lazy reference to 'tester.myperson', but app 'tester' isn't installed.
(tester is the app that defines the swapped models, obviously)
If I delete the migration from my reusable app, and run makemigrations again, it works. the created migration is almost identical to the one above, with the exception of a new dependency:
migrations.swappable_dependency(settings.REUSABLEAPP_PERSON_MODEL),
The migration created in the tester app looks as follows:
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='MyPerson',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('birthdate', models.DateField()),
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='person', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='MyInvite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('person', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.REUSABLEAPP_PERSON_MODEL)),
],
options={
'abstract': False,
},
),
]
I have looked up what swappable_dependency actually does: It looks only which app name is defined (let's say, the setting is tester.mymodel), and creates a dependency to this apps initial migration.
Now, if I delete the created migration from my tester app, I cannot run makemigrations again, I get the same error as above.
To clarify up until this point: Everything works as desired, if I delete the initial migration (and therefore all future migrations!) from my reusable app.
The problem, as I understand it, is the following:
The reusable app has a dependency to the initial migration of the client application that defines the swapped models. But this migration does not exist yet (heck, I am trying to create it!), so makemigration failes. (running makemigrations tester does not help).
But this almost exact same thing works flawlessly when swapping out the standard User model for a custom one. Furthermore, I do not fully understand why the error message states that the tester app is not installed. It definitly is inside my INSTALLED_APPS and it is picked up by the django-ecosystem.
After a few hours, I came up with a possible (but hacky) workaround:
Remove my reusable app from INSTALLED_APPS
Create MyInvite and MyPerson in the tester app (they both inherit from django.models.Model
Create those models by running makemigrations tester
Add my reusable app to INSTALLED_APPS
Define the swap settings
Change the inheritance of my models to their respective abstract counterparts
Run makemigrations again.
This works, because the initial migration of my reusable app now can fullfill the dependency to the swapped models by looking at the initial migration of tester that defines the models with the same name that is defined in the swap variables.
But I am sure that there must be a better way to do that.
This leaves me with the following questions:
How can I handle foreign key relationships to swapped models?
Why can't I create migrations for one app without looking at the migrations of other apps?
Unfortunately, I have to answer my own question.
This package provides a public interface to the swapping API. I have learned, that looking at closed issues is sometimes more helpful than reading the open ones.
Especially #12, and the open #10 answer my question. To sum it up, what works for the AUTH_USER_MODEL setting, does not work for any other swappable model, because the lazy_reference error is ignored, when the target model is set as AUTH_USER_MODEL within django, here:
# There shouldn't be any operations pending at this point.
from django.core.checks.model_checks import _check_lazy_references
ignore = {make_model_tuple(settings.AUTH_USER_MODEL)} if ignore_swappable else set()
errors = _check_lazy_references(self, ignore=ignore)
if errors:
raise ValueError("\n".join(error.msg for error in errors))
The proposed solution would be a registry, where all swapped models are registered and then looked up, to ignore the lazy reference error. Unfortunately, there seems to be no plans from the django devs to support this, as I have not found any open issues or feature requests for that.
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',
},
),
]
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.
I have a problem with creating models in Django.
I want to create new model, so I write code:
class Image(models.Model):
title = models.CharField(max_length=100)
image = models.ImageField()
In the next step, I did python manage.py makemigrations , python manage.py migrate. I got this result:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myfirstapp', '0008_delete_image'),
]
operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('image', models.ImageField(upload_to='')),
],
),
]
but I don't have access to Image's table in Django administration.
Does anyone know what should I do? Or can see an error, know of tips, or other ways to help?
This question doesn't appear to have anything to do with migrations not working.
Migrations don't make a model appear in the admin. In order to do that, you need to register your model.
I am trying to migrate my Django 2.0.4 project from SQLite to PostgreSQL 10 following the steps described here, but I am having differents problems.
During the project I changed some Integer fields to UUID4 fields.
I managed to run python manage.py migrate --run-syncdb manually editing auto_increment migration file making changes of this type (see id field):
From
class Migration(migrations.Migration):
dependencies = [
('dumps', '0011_auto_20180608_1714'),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
],
),
...
...
...
To
class Migration(migrations.Migration):
dependencies = [
('dumps', '0011_auto_20180608_1714'),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='ID')),
('data', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
],
),
...
...
...
Next, I commented all auto_increment files in which there was an AlterTable on uuid fields, but when I run python manage.py loaddata datadump.json I obtain the following error:
django.db.utils.ProgrammingError: Problem installing fixture 'C:\Users\djangoproject\datadump.json': Could not load myApp.Reservation(pk=10d00b08-bf35-469f-b53f-ec28f8b6ecb3): ERROR: column "reservation_id" is integer type but the expression is uuid type
LINE 1: UPDATE "myApp_reservation" SET "reservation_id" = '066cff3c-4b...
I think the issue here is that you have old migrations which refer to the int PK field column as an AutoField() before you made the change to use a UUIDField().
You may need to leave the id field as it was (perhaps reverse back your migrations to the point at which the swithc was made), and include a new field (and thus column of type uuid) named uuid in your Report model:
class Report(models.Model)
id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
uuid = models.UUIDField(default=uuid.uuid4, editable=False, serialize=False, verbose_name='UUID')
data = models.DateTimeField(auto_now_add=True, verbose_name='Date')
...
Then re-run database migrations ... you'll likely hit some more migration errors but give me a shout and I can advise on where to go from there in the chat.
I understood where the error was.
The problem was in postgre table scheme: 'id' field had 'integer' type instead of 'uuid'. I converted it to 'uuid' and the import was successful.
Here is my Django Migration file.
When I run
python manage.py makemigrations/migrate
I get this error.
Error:-
django.db.utils.OperationalError: (1050, "Table 'tickets_duration' already exists")
I have dropped the database and running it but still get the same error.
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='Duration',
fields=[
('Id', models.UUIDField(primary_key=True, db_column=b'duration_id', default=uuid.uuid4, serialize=False, editable=False)),
('duration', models.CharField(max_length=200, db_column=b'duration')),
],
),
migrations.CreateModel(
name='ErrorCount',
fields=[
('Id', models.UUIDField(primary_key=True, db_column=b'error_id', default=uuid.uuid4, serialize=False, editable=False)),
('error', models.CharField(max_length=200, db_column=b'error')),
],
),
migrations.CreateModel(
name='OutageCaused',
fields=[
('Id', models.UUIDField(primary_key=True, db_column=b'error_id', default=uuid.uuid4, serialize=False, editable=False)),
('outage_caused', models.CharField(max_length=200, db_column=b'outage_caused')),
],
),
migrations.CreateModel(
name='Pg',
fields=[
('Id', models.UUIDField(primary_key=True, db_column=b'pg_id', default=uuid.uuid4, serialize=False, editable=False)),
('pg_cd', models.CharField(max_length=200, db_column=b'pg_cd')),
],
),
migrations.CreateModel(
name='SystemCaused',
fields=[
('Id', models.UUIDField(primary_key=True, db_column=b'error_id', default=uuid.uuid4, serialize=False, editable=False)),
('system_caused', models.CharField(max_length=200, db_column=b'system_caused')),
],
),
migrations.CreateModel(
name='Tickets',
fields=[
('ticket_num', models.CharField(max_length=100, serialize=False, primary_key=True, db_column=b'ticket_id')),
('created_dt', models.DateTimeField(db_column=b'created_dt')),
('ticket_type', models.CharField(max_length=20, db_column=b'ticket_type')),
('addt_notes', models.CharField(max_length=1000, db_column=b'addt_notes')),
('row_create_ts', models.DateTimeField(default=datetime.datetime(2016, 2, 29, 16, 58, 31, 584733))),
('row_end_ts', models.DateTimeField(default=b'9999-12-31 00:00:00.00000-00', db_column=b'row_end_ts')),
('duration', models.ManyToManyField(to='tickets.Duration')),
('error_count', models.ManyToManyField(to='tickets.ErrorCount')),
('outage_caused', models.ManyToManyField(to='tickets.OutageCaused')),
try python manage.py migrate your_app --fake. This post talks about it. Django South - table already exists.
python manage.py migrate --fake-initial should work for django 2.2
This question is already answered here
You should run this:
python manage.py migrate <appname> --fake
temporary solution may be to comment the creation of existing table(tickets_duration).
class Migration(migrations.Migration):
dependencies = [
]
operations = [
#migrations.CreateModel(
# name='Duration',
# fields=[
# ('Id', models.UUIDField(primary_key=True, db_column=b'duration_id', default=uuid.uuid4, serialize=False, editable=False)),
# ('duration', models.CharField(max_length=200, db_column=b'duration')),
# ],
#),
....
....
version:-Django 3.X
If above solution doesn't work :
python manage.py migrate <appname> --fake
If it doesn't work then have a look at the migrations folder you will find that there will be some missing changes which u have done in models.py but somehow Django is unable to capture, so find it there and again do some changes (even a small) to that model fields and then use ,
py manage.py makemigrations app_name
py manage.py migrate app_name
or
py manage.py makemigration <appname> --fake
It is an inconsistent situation of Database.
You may do the followings:
Comment out the code for your last added model.
Run makemigrations and then migrate --fake, to get the record sync at present situation.
Now, uncomment your last added model, and run makemigrations again;
I had the same problem when using sqlite3 because of the way Django creates the third join table. From the docs,
Behind the scenes, Django creates an intermediary join table to represent the many-to-many relationship. By default, this table name is generated using the name of the many-to-many field and the name of the table for the model that contains it.
So basically, the third table is created by concatenating the two table names. This doesn't work with sqlite3 because table names in sqlite3 are case insensitive.
The resolution is to add the db_table option when declaring your ManyToManyField.
models.ManyToManyField(to='tickets.Duration', db_table='_Duration')