Django migrations - python

I am trying to build a blog on django. I have gone as far as creating models. Here they are:
from django.db import models
import uuid
class Users(models.Model):
username = models.CharField(max_length = 32, primary_key = True)
password = models.CharField(max_length = 32)
email = models.EmailField()
registration_date = models.DateTimeField(auto_now_add = True)
class Posts(models.Model):
author = models.ForeignKey("Users")
header = models.CharField(max_length=100)
body = models.TextField()
pub_date = models.DateTimeField(auto_now_add = True)
mod_date = models.DateTimeField(auto_now = True)
upvotes = models.PositiveIntegerField()
views = models.PositiveIntegerField()
post_id = models.AutoField(primary_key = True)
class Answers(models.Model):
body = models.TextField()
author = models.ForeignKey("Users")
pub_date = models.DateTimeField(auto_now_add = True)
mod_date = models.DateTimeField(auto_now = True)
post = models.ForeignKey("Posts")
answer_id = models.UUIDField(primary_key = True, default=uuid.uuid4)
After running python manage.py migrate I get the following:
You are trying to add a non-nullable field 'post_id' to posts without
a default; we can't do that (the database need mething to populate existing
rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Even if I press 1 and try to set a random one-off value, it migrates successfully but later on the website crashes with "no such table: blog_posts". But I think it should work without such workarounds as setting the default value manually anyway.
I tried playing with primary keys for Posts and Answers. I tried completely removing them so that django automatically sets them itself and tried changing it from AutoField to UUIDField and vice versa but it didn't help. What am I doing wrong?

You're seeing this error message because django is trying to build a consistent history of migrations, and it complains that if there was a database that held data with your old migrations, and you'd try to add a non-nullable field, it wouldn't know what to do.
Migrations are supposed to be put into version control and used across different development/production environments. If you add a field that must not be null, existing data of other environments (for example a production database that held models that did not have the field post_id) then django will warn you about this, with the error message that you got, and offer two solutions:
This field should always be prepoulated with a default value( you have to modify the models.py)
This is a one time migration and you supply a one-off value, for example "LEGACY" to mark pre-migration data.
If you're not in production and there is no valuable data on your development server, an easy way to fix this error message is just to delete the existing migration files and run python manage.py makemigrations && python manage.py migrate again.

Related

You are trying to add a non-nullable field 'id' to contact_info without a defaultt

I am using the command python manage.py makemigrations
However, I get this error:
You are trying to add a non-nullable field 'id' to contact_info
without a default; we can't do that (the database needs something to
populate existing rows). Please select a fix: 1) Provide a one-off
default now (will be set on all existing rows) 2) Quit, and let me
add a default in models.py
Here is models.py:
class Posts(models.Model):
id = models.AutoField(primary_key=True)
post_uesr = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,related_name='create_user')
post_title = models.CharField(max_length=200)
post_description = models.TextField(max_length=800, null=True, blank=True)
post_likes = models.IntegerField(default=0)
post_date = models.DateTimeField(default=datetime.now)
def __str__(self):
return f'{self.post_uesr}'
It's not a bug, it's documented and logical. You add a new field, which is (by best practice, as you noticed) not NULLable so django has to put something into it for the existing records .
You can press 1 so this option applies
1) Provide a one-off default now (will be set on all existing rows)
so just press 1, if thats your case as value.
else if you want to abort this task and provide manually press 2 and this option applies.
2) Quit, and let me add a default in models.py
BUT here in your case id is a default field already included in every table you create and that will be default to primary key.
And if you are looking to add another AutoField probably this will help
order = models.AutoField(primary_key=False)
And if you are trying to make primary key as uuid4 format
import uuid
id = models.AutoField(default=uuid.uuid4, primary_key=True)
it's telling you should specified a default value for your id field.
ask you for adding manually a default value to this field or make a field without any default value in database.
for example for using UUID as your post id you should do like this:
import uuid
from django.db import models
class Posts(models.Model):
id = models.AutoField(default=uuid.uuid4, primary_key=True)
# other lines of codes
and this is because you are using an AutoField.
you can change it like this:
import uuid
from django.db import models
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, auto_created=True)
# other lines of codes

Cannot alter tables after Django migration in posgres DB

I really need to know why in the world we cannot change the column types after we do the migrations to Postgres DB.
I already created my models by python manage.py makemigrations then do migrate. Everything looks fine and tables are created on the postgres DB.
class test_API(models.Model):
IDnumber = models.IntegerField(null=False, blank=False)
State = models.CharField(max_length = 256, null = True)
Exlcludmethod = models.CharField(max_length=256, null=True)
class test_API_2(models.Model):
Idnumber = models.Foreignkey(test_API, max_length = 256, blank=False, null = False)
value = models.CharField(max_length=128, default="")
last_updated = models.DateTimeField(auto_now=True)
Lets say we want to make change to IDnumber column from Integerfield to Charfield.
class test_API(models.Model):
IDnumber = models.CharField(null=False, blank=False)
State = models.CharField(max_length = 256, null = True)
Exlcludmethod = models.CharField(max_length=256, null=True)
and run the python manage.py makemigrations again
return self.cursor.execute(sql)
psycopg2.errors.DuplicateTable: relation "test_API" already exists
How can we modify/change column types and do migrations. What is the proper way of doing this ?
Using
Django:Django==4.0.3
Python = 3.9.1
Posgresql = 2.9.3
Django migrations using RunPython to commit changes
django 1.7 migrate gets error "table already exists"
It seems like you want to drop the link to test_API in test_API_2. If you want to be able store 'abcd' type strings, and not try to keep anything from the relation previously created by the ForeignKey field, then I believe you need to do this in a two step process.
First step would be to remove (or comment) the IDNnumber field, make migrations, then add it back with a new type of field.
The IDNumber field was more than an integer field, it has an indexed relation to test_API, so you need to sever that first by removing the field, then create a new field with identical name.

You are trying to add a non-nullable field 'post' to stream without a default; we can't do that

I have models.py
class Stream(models.Model):
following = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='stream_following')
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
date = models.DateTimeField()
and function
def add_post(sender, instance, *args, **kwargs):
post = instance
user = post.user
followers = Follow.objects.all().filter(following=user)
for follower in followers:
stream = Stream(post=post, user=follower.follower, date=post.posted, following=user)
stream.save()
when I'm trying command py manage.py makemigrations
I have an issue.
You are trying to add a non-nullable field 'post' to stream without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
Provide a one-off default now (will be set on all existing rows with a null value for this column)
Quit, and let me add a default in models.py
Select an option:
How to solve that? I put on default smth. However in function add_post I have added date=post.posted
Thanks!
you probably changed your models while it already have data in it
you could choose either:
a. delete data from that model and re-do migrations,or
b. delete migrations and sqlite files and re-do migrations,or
c. choose "Provide a one-off default now " by typing 1 and just add a random existing id number(but it could mess with your data),or
d. add blank=True,null=True to all fields in your models and re-do migrations,

Adding db_table to model added a last_login field

Background: I started a project with a custom User model. However, noob that I am, I was unaware of the AbstractBaseUser class. So I just wrote my own. The app has been deployed to prod and working fine. But now I want to switch to using AbstractBaseUser so I can take advantage of some of the built-in Django utilities (like the pre-made password resetting process). I had done this with a different app and it worked fine. But that one wasn't in prod while I made the change. Because this one is, I needed to keep the old user table while I made the changes with a copy of it. So my first step was to add db_table = test_users to my old user model, so as to keep the prod app running with an unchanged table. I ran the migration, and two unexpected things happened (I'm a noob, and that's why they were unexpected):
The old user table was renamed. I thought a new table would be created. No problem, I quickly copied the new table and named the copy with the old table's name so the prod app could still find its users
A column last_login was added. Why??
Here's my model, with the added db_table
class User(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email = models.CharField(max_length=255)
password = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
client_id = models.IntegerField(blank=True, null=True)
is_admin = models.BooleanField(default=False)
is_super = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
class Meta:
db_table = "test_users"
The big problem with this is that when I change to AbstractBaseUser and run the migration, I get an error. Looking at the migration file I see that this change creates a migration that all it tries to do is to add last_login to the table. So, of course, the error I get is "Duplicate column name 'last_login'"
So, my question is two-fold:
Why was that column added in the first migration?
If I just run migrate --fake and keep going, will it have unintended consequences? I thought this could be a good solution, given that the migration file shows nothing else is being done, and if the field already exists, then no harm done?
Maybe because you've changed the parent class django automatically change all the migrations that connected to your user class

Django migration from dynamic fields

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),
]

Categories

Resources