Upgrade Django with incompatible migrations - python

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.

Related

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 model change OneToOneField to ForeignKey with no DownTime

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

django giving cryptic error for foreignkey

I am getting this error for a django model:
ProgrammingError at /admin/notifications/eventtoken/
column notifications_eventtoken.user_id does not exist
LINE 1: ...ications_eventtoken" INNER JOIN "users_user" ON ( "notificat...
^
HINT: Perhaps you meant to reference the column "notifications_eventtoken.used_at".
I added a foreign key for user like this:
class EventToken(models.Model):
token = models.CharField(max_length=255, db_index=True)
user = models.ForeignKey(User, null=False, blank=False)
used_at = models.DateTimeField(null=True, blank=True)
event = models.ForeignKey(Event)
it works when I remove the user field, but breaks otherwise and I can't tell much from the error message.
It works when I remove the user field.
This is because your database is missing a column that you have programmed in your EventToken class which I'm guessing is inside of notifications/models.py. This is why it is called a ProgrammingError.
There are several ways to solve this:
1. Delete your entire database and sync it again! Not recommended for anything running on production, but for test projects really doesn't hurt much.
2. Use a tool like south or the django migrate app (depending on which django version you're using) to automate adding a table to your database using the manage.py script. Edit: this solution is the most viable as it's quick and adheres to DRY principles when working across multiple development environments.
3a. Manually go into your database using a command line tool and add the missing column. If I haven't made it clear... you're saying "I want to save the user_id" foreign key in this table" while your table is saying back "There is nowhere to save this ID." ...)
3b. Use a database GUI (like PGadmin for postgresql) and add the user_id column to your notifications_eventtoken database table.
4. Delete the user field in the EventToken class! Don't do this, but understand it works because it clears the logical discrepancy.

Many to Many and Foreign Key relations in django admin

First my django knowledge is beginner level, so please be patient with me.
I am faced with a model relationship that I do not know how to handle. I have 3 models: Project, Location and SubLocation.
A project can have multiple locations and each location can have many sublocations. I have a many to many field for location in the Project model and a foreignkey field for location in the Sublocation model.
class Project(models.Model):
...
locations = models.ManyToManyField(Location)
class Location(models.Model):
name = models.CharField(max_length=250, unique=True)
class SubLocation(models.Model):
location = models.ForeignKey(Location)
name = models.CharField(max_length=100, unique=True)
In django admin, I am able to add multiple locations when creating a project(Using filter_horizontal). However, I also need the option to select sublocations based on an added location for the project being created. I did not know how to do it with the above approach.
I then removed the locations many to many field from the project model tried the approach below.
I created a ProjectLocation model and added it as an inline to the Project ModelAdmin to be able to add locations and sublocations when creating a project. The model that looks as follows:
class ProjectLocation(models.Model):
project = models.ForeignKey(Project)
location = models.ManyToManyField(Location)
sublocations = models.ManyToManyField(SubLocation)
However, the approach does not work as desired since you can add any sublocations irregardless of the locations added. What I would like is to be able to add locations and their relevant sublocations when creating a project.
I read through generic relations as another possible approach but still I did not know how to pull it off.
With my model structure, is that possible?
If True, what should I do to get the desired result?
If False, please recommend how I could change it.
I think if you use foreign key it will be easier for your case, and it will easy to use with the _set option from django.
Yes it is possible but it will not be dynamic (meaning that changing the value of location will not magically update themselves without first saving the change) which might be very unpractical.
You should use formfield_for_manytomany in your admin -> https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_manytomany
The easiest way to implement this would be to add custom javascript filtering on that particular admin form
Leave for models as it was and try to use inlines in admin page.
So, your admins.py would look something like this:
class SubLocationInline(admin.StackedInline):
model = SubLocation
#admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
.....
inlines = [SubLocationInline]

Change Django model column default using South

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.

Categories

Resources