I've the following Django model:
class Apple(models.Model):
text = models.TextField()
I've already many records, and I'd like to add a subject field to the model, so it'll look like:
class Apple(models.Model):
text = models.TextField()
subject = models.CharField(max_length = 128)
. In this case I run a makemigrations, but since subject can be empty, I need to set a default value either in the model, or in the migration file.
What would be the correct procedure if I'd like to take the subject from the text for the already existing database lines (for instance: text[:64])?
My solution would be to create a migration with a default value, run a management command to update the values, and with a new migration remove the default value for the subject. Is there a better solution? What is it? Can I somehow combine / do this in the migration itself?
Python: 3.4.5
Django: 1.9.2
For some databases including postgresql, it can be quicker to add a nullable field, therefore I would change your approach to:
schema migration creates the field with null=True (no need to set a default)
data migration populates the field
schema migration removes null=True from field
You can combine the three operations in one migration file. However the Django docs for data migrations recommend that you keep them separate.
You can do it in migration itself, create a migration file with blank=True, null=True in subject field.
class Apple(models.Model):
text = models.TextField()
subject = models.CharField(max_length=128, blank=True, null=True)
Then create another empty migration file.
python manage.py makemigrations --empty yourappname
Paste below code in that file.
from django.db import migrations
def set_subject(apps, schema_editor):
Apple = apps.get_model('yourappname', 'Apple')
for a in Apple.objects.all():
a.subject = a.text
a.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', 'name of above migration file'),
]
operations = [
migrations.RunPython(set_subject),
]
Related
from django.db import models
class Town(models.Model):
name: models.CharField(max_length=70,unique=True)
country: models.CharField(max_length=30,unique=True)
class Meta:
pass
This is my model Town whith two attributes: name and country. When I create a migration in the initial_0001.py file only id column is shown
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Town',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
]
What kind of problem could it be?
The ID-Field is always automatically generated by Django when making migrations. You can specifiy your own ID field aswell, but using an auto-incremented like this is fine for your use case.
You also might want to get rid of the unique=True, as it would prevent adding multiple towns from the same country.
Create your model like this and redo your migration process:
class Town(models.Model):
name = models.CharField(max_length=70)
country = models.CharField(max_length=30)
When you have 'no changes detected' when making migrations, I usually follow these steps:
First, Delete migrations folder in project.
Then delete django-migration entries in your Database with this query:
DELETE from django_migrations WHERE app='yourAppName'
Then, create new folder ‘migrations” in app folder + init.py
Lastly, redo the migration process:
py manage.py makemigrations
py manage.py migrate --run-syncdb
I had the same problem, and this solved it. Hope it helps.
Instead of : it should be =
You also need to provide a default value for all the previous rows that were created
Then run python manage.py makemigrations
Run python manage.py migrate
class Town(models.Model):
name = models.CharField(max_length=70,unique=True, default='')
country = models.CharField(max_length=30,unique=True, default='')
class Meta:
pass
I've modified the foreign key calendar as nullable in my Django model CalendarAssign. \
# ---------------------------------------------------------------------------- #
class Calendars(models.Model):
id = models.CharField(primary_key=True, max_length=100)
cms_id = models.CharField(max_length=100)
default_program = models.ForeignKey(ControlPrograms, models.CASCADE, blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
managed = True
db_table = 'calendars'
# ---------------------------------------------------------------------------- #
class CalendarAssign(models.Model):
device_mac = models.ForeignKey(Device, models.CASCADE)
calendar = models.ForeignKey(Calendars, models.CASCADE, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
managed = True
db_table = 'calendar_assign'
When applying the migration generated by Django it gives me an error.
operations = [
migrations.AlterField(
model_name='calendarassign',
name='calendar',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='smartbridge.Calendars'),
)
Generated sql code uses unsupported feature 'WITH ORDINALITY'.
It's because Django doesn't support the Postrges version we are using.
WITH ORDINALITY appears in psql 9.4 but we use version 9.1.
Both Postgres and Django cannot be upgraded right now. So I need to write the migration manually (without 'WITH ORDINALITY' feature).
migrations.RunSQL("DO $$DECLARE r record;\
BEGIN\
FOR r IN SELECT table_name,constraint_name \
FROM information_schema.constraint_table_usage \
WHERE table_name IN ('calendars') AND constraint_name like '%calendar_assign_calendar_id%'\
LOOP\
EXECUTE 'ALTER TABLE calendar_assign DROP CONSTRAINT '|| quote_ident(r.constraint_name) || ';';\
END LOOP;\
ALTER TABLE calendar_assign ALTER COLUMN calendar_id DROP NOT NULL; \
ALTER TABLE calendar_assign \
ADD CONSTRAINT calendar_assign_calendar_id_fk_calendars_id FOREIGN KEY (calendar_id) REFERENCES calendars(id);\
END$$;")
Migration seems to work fine.
calendar is now nullable but Django still detect some difference.
If a ask Django to generate the migration corresponding to the difference it generates the same as before my manual migration.
I would like Django to see no difference after my migration.
Thanks
I think you will have to set managed = False for the time being, otherwise the makemigrations command will each time think it has not been made nullable yet.
The migration construction command looks to the previous migration files, and thus constructs a conceptual model how a table will look like in the database if all the previous migrations took place. Based on that model it will look for differences with your Django model that you constructed, and thus create a migration file for that.
As long as you thus do not migrate with the AlterField command, Django will think you did not make the field nullable. It can not parse SQL so even if you made it nullable over there, it will still assume that that the field is non-NULLable.
By setting it to managed=False [Django-doc], Django will no longer manage the migrations of that file. You can create an empty migration [Django-doc] with:
python3 manage.py makemigrations --empty
and use this to define SQL queries to perform on the table.
I have a database in microsoft sql server. I created tables and views in it.
I ran py manage.py inspetdb view_Name > Models.py and populated my models.py file with managed=false. I also dont want my model to alter my database. I just want it for data retrieval.
Should I definitely run migrate/makemigrations?
After inspectdb should i apply makemigrations on my app or is just migrate enough? And also what are the points to remember while using inspectdb on an existing database.
Also I have something like the below in my models.py file for all columns
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase
Is having the fieldname in lowercase safe ? Or should I change it as it is in my column? And what are those db_column='Created', blank=True, null=True fields. Not all my views have such fields. Only a few have such values.
Models.py contents
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# * Make sure each ForeignKey has `on_delete` set to the desired behavior.
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models
class test1table(models.Model):
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'Test1'
Migrations file 0001_intial.py
# Generated by Django 2.1.14 on 2019-11-28 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='test1table',
fields=[
('created', models.DateTimeField(blank=True, db_column='Created', null=True
],
options={
'db_table': 'Test1',
'managed': False,
},
),
]
This solution solved my problem: I created a notepad file, pasted the code, changed the extension to .py and replaced the models.py file.
Success in migrate!
I created a CustomUser model, inheriting from AbstractEmailUser.
I wanted to add an avatar field, after finishing it and making migrations but I'm getting the following error:
column account_customuser.avatar does not exist
LINE 1: ...user"."name", "account_customuser"."valid_email", "account_c...
models.py looks like this now
class CustomUser(AbstractEmailUser):
nickname = models.CharField('nickname', max_length=100, unique=True)
name = models.CharField(max_length=200, blank=True, null=True, default=None)
valid_email = models.BooleanField('valid email', default=False, blank=True)
avatar = models.ImageField(upload_to='profile/photo', blank=True, null=True, default=None)
What can I do to correctly add the avatar field?
As stated here: Django Programming error column does not exist even after running migrations
Something may have gone wrong in your migration process.
Go to your database and find a table named django_migrations where
all the migrations are listed.
Find the row with the migration in which you added the avatar column to your model and delete it from the database (only the row).
Migrate again: ./manage.py migrate
Another possibility is that you are using Django Toolbar like what happened here: Django Migration Error: Column does not exist, in which case you need to comment the toolbar in your installed apps and rerun migrations.
Did you apply a new migration with these changes?
You can check this using showmigrations or use makemigrations to create a migration and migrate to apply it.
It is stated in the Django documentation that each model requires a Primary Key. In my MySQL schema it is specified that for my table the primary key is made up of multiple columns. Using inspectdb to auto-generate the model creates the unique_together Meta information.
class Connections(models.Model):
router = models.ForeignKey('Routers', models.CASCADE)
src_mac = models.CharField(db_column='src_MAC', max_length=17) # Field name made lowercase.
src_ip = models.CharField(max_length=45, blank=True, null=True)
src_port = models.CharField(max_length=45)
dest_mac = models.CharField(db_column='dest_MAC', max_length=17, blank=True, null=True) # Field name made lowercase.
dest_ip = models.CharField(max_length=45)
dest_port = models.CharField(max_length=45)
first_activity = models.DateTimeField(blank=True, null=True)
last_activity = models.DateTimeField(blank=True, null=True)
hits_counter = models.IntegerField(blank=True, null=True)
def __unicode__(self):
return self.src_ip
class Meta:
db_table = 'Connections'
unique_together = (('router', 'src_mac', 'src_port', 'dest_ip', 'dest_port'),)
When I go to run
> python manage.py shell
>>> Connections.objects.all()
it gives me the OperationalError: (1054, "Unknown column 'Connections.id' in 'field list'") to my understanding this means that Django is trying to auto-create the primary key column for Connections.
How do I get around this?
tl;dr: Recreate the table or add the "id" field to the database manually.
I don't believe it's possible to create a primary key in all databases.
For example when using SQLite, if I create a simplified version of your table, altering it to add a PK returns an error:
> CREATE TABLE connections(src_MAC CHAR(17) NOT NULL, first_activity DATETIME NULL);
> INSERT INTO connections VALUES ('abcd1234',date('now'));
> ALTER TABLE connections add column id integer NOT NULL PRIMARY KEY AUTOINCREMENT;
Error: Cannot add a PRIMARY KEY column
Similarly, running a Django migration to create the field with SQLite simply fails to add the field silently (possibly a Django issue).
If you're unlucky and use a database that does not support adding a primary key (if you're not sure, check by running the ALTER command), your best bet would be to dump the table data, drop the table, create a new table with a primary key and finally reload the table data. This seems also to be the safest way to do it generally.
On the other hand if you do use a database that supports adding a primary key, you can either try to make migrations or alter the database manually.
Creating the id field manually in Postgres is as simple as:
ALTER TABLE connections ADD COLUMN id SERIAL PRIMARY KEY;
This here is sufficient to make the Model usable in Django. Now for the hard way.
It seems that makemigrations assumes an ID field already exists, and thus if you opt to use migrations, the initial migration would need a fix (remove the id field). You can create the CreateField migration manually thus:
migrations/0002_connections_id.py
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-08 08:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('testapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='connections',
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]
Unfortunately, this still doesn't seem to be enough, since the created column does not auto-increment (possibly another Django issue). The solution now would be to alter the created field like so:
> ALTER TABLE connections ALTER id SET default nextval('connections_id_seq');
Finally the values make sense to Django and you can now query and create "Connections" instances as usual.
Just in case anyone needs this later, I've pushed the code I used to test this to this GitHub repo.