Django: Quickly deal with adding non-nullable field - python

When developing models I quite often get the non-nullable field error when running makemigrations:
You are trying to add a non-nullable field 'user' to randommodel 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
Select an option:
Almost all the time when I get this error I'm quite happy to delete the data in that table (it's normally only a couple of test entries while developing) and it would be more efficient to just delete it rather than determine what a suitable default would be.
However currently I don't have a suitable method for doing this and end up flushing the database and/or deleting the migrations, which is pretty heavy handed but works.
What's the best way to delete the data just in that model/table to remove the error? (Would it be via shell/shell_plus?)
Model:
class RandomModel(models.Model):
user_details = JSONField(unique=True)
user = models.ForeignKey(User)

Even if you have deleted all the records in that table, when running makemigrations, you'll be asked to provide default values again. This is because you're making a new migration file for an existing table.
One solution I can think of is to tell Django that you're starting that app_name over again by running migrate app_name zero. This will unapply all migration files that have ever been applied to your database.
Then delete all the migration files in your app_name. And run makemigrations again. This would create a new initial migration file. Then you just apply it to your database with migrate.
As you've said you don't mind deleting your data. This is even better. You don't have to even delete any record. It will just create a new table with the same name with all the new fields and with 0 record.

Related

Django - delete M2M field but keep join table

is it possible to remove an M2M field from a model and keep the joining table?
context:
I am trying to add through model to existing M2M field like described in this post
But doing it simply like this will result in a production app crash when accessing the old table during deployment - short window between migration and code update, when old code will try to access a new database for a few moments - without the old table in it.
You can use --fake flag when running manage.py migrate. That will make a migration file that says the model field has been removed and mark it as applied in the database migration table, but not actually execute the SQL to remove the corresponding tables etc. Read more here

Django error when trying to migrate models with foreignkeys that don't have null=True or default set

I have this model that is a post like on Twitter, that has a creator. I'd ideally like the post to always require a creator_id and if the creator gets deleted then delete the post as well
class Post(AbstractBaseModel):
creator_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="post_creator_id")
body = models.CharField(max_length=511)
Whenever I try to run 'python manage.py migrate' I get this error
"You are trying to change the nullable field 'creator_id' on cheerpost to non-nullable without a default; we can't do that (the database needs something to populate existing rows)."
The options to solve this are are 1) provide a one off default or 2) ignore for now. Neither of these seem to fulfill my constraint that I want to enforce, which is creator_id must exist and is the person who created the post or the entity gets deleted.
I've tried deleting the DB and recreating it from scratch in postgres as well as deleting it using the following query:
TRUNCATE Post;
DELETE FROM Post;
If you've deleted the DB, just the data and tables from DB are deleted.
That doesn't reflect any changes in Django. All the changes you've made to the fields of your model still exist in migrations. You have to delete the old migrations too.
Delete those old migrations from your app, create new migrations from scratch and apply them.
python manage.py makemigrations
python manage.py migrate
Django is asking you to provide a one-off default for any rows you already have in your database, since the field was nullable before the migration. The issue is that Django doesn’t know if there are any rows in the existing database where that column is null, so it needs instructions on what to do if it finds any. You can just provide one and forget about it—it will never be used again after the migration is complete.
Also, you may want to review how the related_name parameter works; you’ve got it backwards.

Django migrations not getting applied on Postgres after changing the datatype of an attribute in models.py

I had this model where the price was an Integer Field. I did run migrations and all was fine.
from django.db import models
class Bill(models.Model):
price= models.IntegerField()
Then due to requirement changes, I had to make the price field as JSONField which would store the price value based on certain keys similar to this
price={"actual_price":100, "tax_price":20}
I made the changes in the model like below:
from django.db import models
class Bill(models.Model):
price= JSONField(blank=True, null=True)
I performed makemigrations and migrate operations, the migrations are not getting reflected in DB. There are no errors as well. I get the error "Column: price does not exist" when my code tries to read from the DB.
I tried the following things by referring to StackOverflow other questions:
Removed the field. Run migrations, applied it. Removing field works
fine. But when I add the field back instead of considering it as new
field to be added, it just doesn't get inserted.
Removing the migrations from the migrations.py folder, re-running it and re-applying it.
Please note:
I am using Postgres DB. The same thing works for SQLite DB which is in-house provided by Django but not for Postgres.
Losing the data is not a feasible option as the DB data is of production server.
I tried adding the column manually through PostGres using the query ALTER Table ADD Column which worked perfectly fine. This is a hook that I used which was used as a last resort.
The data in the initial integer field was present for some of the records before applying the updated migration. Strange thing is I also wasn't asked by Django to set the value in case I want to override the data.
I need the Django migrations for applying the changes automatically to work. Due to this issue, only the addition of new column and modifying the datatype of the column is not working at all (Rest operations like removing the column works).
Since there was no other option and no solutions provided, I had to point my application to new DB in PostGres and then it started working fine. Lesson Learnt for future:
Not to update directly the datatype of the model attribute and then apply migrations.
First Remove the field, apply migrations and then create the field with same name and required data type and then apply migrations.
Use the same Database that you are using for production in your local itself. In this way, you can avoid conflicts after deploying to production. I had used SQLite for local and PostGres for Production. From now on, I will use PostGres for Local also so that I can debug issues in my local itself.

ProgrammingError while opening admin table

I am using django with postgres database.
When loading the admin,clicking on Course Table, I get a classic error:
ProgrammingError at /admin/user_profile/course/
relation "user_profile_course" does not exist
LINE 1: SELECT COUNT(*) AS "__count" FROM "user_profile_course"
I tried makemigrations, and migrate it again but no success.
Would you please help me how to handle it?
My models.py looks like:
class Course(models.Model):
name=models.CharField(max_length=30)
number_of_sessions=models.IntegerField()
student=models.ManyToManyField(User, through='Registration')
Edit:
I removed the migration folder, and make migrations again.
in the 0001initial.py the dependencies looks like:
dependencies = [
('auth', '0007_alter_validators_add_error_messages'),
]
It looks like you've removed already applied migration from your migrations folder and re-created it with different model state (with extra model Course).
That will create situation when actual structure of your database is different than state that should be after applying your migration.
Now you can either fix your migration or fix your database structure.
To accomplish first possible fix, you need to check your current state of database and compare it with your migration file. If there is something different in your migration file, simply change it to reflect your database structure.
If you don't know how to edit migration file, you can change your models to reflect database structure, remove your migration and then create it again using makemigrations.
After fixing your migration file, generate second one, just running makemigrations with having desired models structure in your models files.
For second solution, check what's inside your migration file and edit your database structure to reflect that.
And remember, do not ever delete or change already applied migration, unless you really know what you're doing!

Is is possible to set m2m object not to have recursive relation to itself?

Suppose I have a User model that has a m2m subscribers relation with itself.
The idea is that user can't be subscribed on himself.
Is it possible to handle this at models level?
You could use a m2m_changed signal with a "pre_add" action to validate the adding object. See docs
Assuming this is a strong constraint on your design, I would let the database know about it and enforce it on the database itself. The corresponding SQL would look like this:
ALTER TABLE "myapp_subscribers" ADD CONSTRAINT "myapp_subscribers_not_self" CHECK (user1_id <> user2_id)
Replace user1_id and user2_id with the actual field names, myapp_ with the actual name of your app and not_self with whatever name you want to give the constraint, it does not matter as long as its purpose is obvious to you.
While Django does not provide automation for this kind of things, it's pretty easy to write a custom migration step yourself. If you are creating a new app, in the migration file generated by Django, add this at the end of the list of migration steps:
migrations.RunSQL(
[('ALTER TABLE "myapp_subscribers" ADD CONSTRAINT "myapp_subscribers_not_self" CHECK (user1_id <> user2_id);', None)],
[('ALTER TABLE "myapp_subscribers" DROP CONSTRAINT "myapp_subscribers_not_self";', None)]
)
The second SQL line allows the operation to be reversed. If you omit it, Django will refuse to run the migration backwards.
If you are changing an existing app, include that in the migration you're creating, or create an empty migration.
./manage.py makemigrations --empty myapp
Once you add the constraint, the database will enforce it, and prevent any attempt, be it from django or anything else, to subscribe a user to himself.
On the Django side, that means that any attempt to do so will raise an IntegrityError. If inside a transaction, this will also cause the transaction to rollback cleanly.

Categories

Resources