I'm using South with a Postgresql DB for a Django project.
There is a model field that I'd like to change the default value for going forward. I dont need previous records effected. Just new records.
Do I need to do a migration for this, or just change the model?
OLD FIELD DETAIL:
background_style = models.CharField(max_length=1, choices=BACKGROUND_STYLE, default=BackgroundStyleCode.CENTERED)
NEW FIELD DETAIL:
background_style = models.CharField(max_length=1, choices=BACKGROUND_STYLE, default=BackgroundStyleCode.STRETCHED)
(model name is "Page")
You should run a migration. Any time you make a change to a model, no matter how insignificant, you should create a schema migration so that you can move backwards and forwards to any point in time without any "magic" edits.
Related
I'm tasked with upgrading the Django version for a project that currently uses Django 2.2.24.
It contains a model (with existing migrations) that looks roughly like this:
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
type = models.ForeignKey(MembershipType, on_delete=None)
Starting with Django 3.0, on_delete=None causes an error since on_delete is supposed to be a callable. In order to avoid the error, both the model and the existing migrations have to be changed.
By itself, it's not an issue to change the model like this:
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
type = models.ForeignKey(MembershipType, on_delete=models.SET_NULL, null=True)
But existing databases are not yet aware that the corresponding field can be nullable, so a new migration is required for that.
The best way I currently see to do this is the following:
change the model
create&apply a migration using Django 2.2.24
change the old migrations manually
Is there a more elegant way to solve this issue?
I'm not sure this is the optimal solution, but maybe it will help you find a similar solution at least.
If you can reset the database, then you can find the migration file where the field was first created and change on_delete to SET_NULL and set null=True. Then remove the database and run migrations from scratch.
If you can't remove the database, then you could:
Change the model as your code.
Edit migration file where the field was created. (Same as above).
Manually in the database run the SQL to alter the field to make it nullable.
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.
I made one field of my django model as OneToOneField. So, It's not possible to store duplicate FK value. I think changing OneToOneField to ForeignKey is the solution.
Current
class MyModel(models.Model):
...
abc = models.OneToOneField(YourModel, related_name='my_model', blank=True, null=True, on_delete=models.CASCADE)
...
Future
class MyModel(models.Model):
...
abc = models.ForeignKey(YourModel, related_name='my_model', blank=True, null=True, on_delete=models.CASCADE)
...
The problem is downtime when migrating. This model is an important model in my service, many requests come in even in a moment. It also has many data.
Is there a way to fix this without downtime?
And my service is using mysql 5.6 and django 2.2.
Option a)
Hmm so DB relation wise I don't see a difference, so what if you just adjust the field in the Model definition and modify the old migration that initially created the field? That way Django should think that there is nothing new to apply and treat the original OneToOne Field as a ForeignKey Field
Please try that on an a backup first to see if there are maybe additional unique constraints or so that you might have to remove in a custom sql command before you have a real ForeignKey Field.
Option b)
Use multiple migrations and deployments.
First add a new field (e.g. abc_new that is nullable)
Adjust your logic so always both fields are filled for new data and changes
Deploy this in a new release
Copy the "old" data from abc to abc_new
At this point you have two rows that contain the exact same data
Create a new release that drops the old abc column and renames abc_new to abc and remove the logic that contained this "sync" logic for the abc_new field
With Django 1.5 and the introduction of custom user models the AUTH_PROFILE_MODULE became deprecated. In my existing Django application I use the User model and I also have a Profile model with a foreign key to the User and store other stuff about the user in the profile. Currently using AUTH_PROFILE_MODULE and this is set to 'app.profile'.
So obviously, my code tends to do lots of user.get_profile() and this now needs to go away.
Now, I could create a new custom user model (by just having my profile model extend User) but then in all other places where I currently have a foreign key to a user will need to be changed also... so this would be a large migration in my live service.
Is there any way - and with no model migration - and only by creating/overriding the get_profile() function with something like my_user.userprofile_set.all()[0]) somewhere?
Anyone out there that has gone down this path and can share ideas or experiences?
If I where to do this service again now - would obviously not go this way but with a semi-large live production system I am open for short-cuts :-)
Using a profile model with a relation to the built-in User is still a totally legitimate construct for storing additional user information (and recommended in many cases). The AUTH_PROFILE_MODULE and get_profile() stuff that is now deprecated just ended up being unnecessary, given that built-in Django 1-to-1 syntax works cleanly and elegantly here.
The transition from the old usage is actually easy if you're already using a OneToOneField to User on your profile model, which is how the profile module was recommended to be set up before get_profile was deprecated.
class UserProfile(models.Model):
user = OneToOneField(User, related_name="profile")
# add profile fields here, e.g.,
nickname = CharField(...)
# usage: no get_profile() needed. Just standard 1-to-1 reverse syntax!
nickname = request.user.profile.nickname
See here if you're not familiar with the syntactic magic for OneToOneField's that makes this possible. It ends up being a simple search and replace of get_profile() for profile or whatever your related_name is (auto related name in the above case would be user_profile). Standard django reverse 1-1 syntax is actually nicer than get_profile()!
Change a ForeignKey to a OneToOneField
However, I realize this doesn't answer your question entirely. You indicate that you used a ForeignKey to User in your profile module rather than a OneToOne, which is fine, but the syntax isn't as simple if you leave it as a ForeignKey, as you note in your follow up comment.
Assuming you were using your ForeignKey in practice as an unique foreign key (essentially a 1-to-1), given that in the DB a OneToOneField is just a ForeignKey field with a unique=True constraint, you should be able to change the ForeignKey field to a OneToOneField in your code without actually having to make a significant database migration or incurring any data loss.
Dealing with South migration
If you're using South for migrations, the code change from the previous section may confuse South into deleting the old field and creating a new one if you do a schemamigration --auto, so you may need to manually edit the migration to do things right. One approach would be to create the schemamigration and then blank out the forwards and backwards methods so it doesn't actually try to do anything, but so it still freezes the model properly as a OneToOneField going forward. Then, if you want to do things perfectly, you should add the unique constraint to the corresponding database foreign key column as well. You can either do this manually with SQL, or via South (by either editing the migration methods manually, or by setting unique=True on the ForeignKey and creating a first South migration before you switch it to a OneToOneField and do a second migration and blank out the forwards/backwards methods).
I use south to migrate my django models. There is however a nasty bug in south. It doesn't set default values in Postgres Databases. Example:
created_at = models.DateTimeField(default = datetime.now)
tag_id = models.PositiveIntegerField(default = 0)
South will add these 2 fields to database, but fail to set their default values, which needs to be done manually.
Is there any patch for this bug?
UPDATE
I had already tried setting default date with auto_now_add=True, but that is also not setting defaults. Adding null=True in field adds a db.alter_column in migration script produced by south. But that only removes NOT NULL constraint, doesnt add a default. Same for integer field
If you are auto-generating your migrations using:
./manage.py schemamigration app_name --auto
Then you need to make a small edit to the migration before you actually apply it. Go into the generated migration (should be called something like app_name/migrations/000X__auto_add_field_foo.py) and look for the argument:
keep_default=False
in the db.add_column call. Simply change this to:
keep_default=True
And Django will now apply your default value to the actual schema, in addition to any existing rows. Would be great if South had some kind of setting to generate this parameter as True by default, but no such luck. You will need to make this edit every time.
This is not a bug, in South or elsewhere.
I think you are confused about how default values work in Django generally. Django does not set default values in the database schema. It applies them directly in Python, when a new instance is created. You can verify this by doing manage.py sqlall and see that the generated SQL does not contain default attributes.
As mentioned in earlier answers, the default mechanism in django is implemented in the model class, and is not relevant to south migrations.
Also, since south 0.8, the keep_default flag is deprecated, and won't add the default value to your model.
What I do to solve this is writing a custom migration to add the default value. You can do that by creating a separate data migration:
./manage.py datamigration your_app_name migration_name
and add the following line to the forwards function:
orm.YourModel.objects.update(field_name = DEFAULT_VALUE)
Alternatively, instead of creating a new migration, you can modify your original migration:
add no_dry_run = True to the class itself (so you will have access to the ORM).
add orm.YourModel.objects.update(field_name = DEFAULT_VALUE) to the end of the forwards function.
This way you don't have to write a backwards migration, because you already have the original delete-column one.