Django: How do I update previously created database entries? - python

I've got a bunch of previously created database entries of a model class I created, called Equations. I created a new type of model, EquationGroup, and I want to be able to link the existing Equations in the database to newly created EquationGroups. How would I do that?
Update:
I forgot to mention that I've got a ForeignKey relationship in Equation to EquationGroup.
Here is short version of my models.py
class EquationGroup(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Equation(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
group = models.ForeignKey(EquationGroup)

You can create a script that goes through all the equations and attaches them to equations groups. Using Equations.objects.all() you can get an iterable of all of the equations objects.
You can then go through in a for loop and assign each one to the specified Equations group.
Example:
for equation in Using Equations.objects.all():
equation.equationGroup = some_group #based on however you specify what goes in a group.
equation.save()

Do you want to make associations Equation <-> EquationGroup? If so, you should add OneToOne relation field. Look here for examples.
There is many other ways to add relations, like ManyToMany and ForeighKey.
Do you need to associate instances? In this way, you should have data to find related data and save it as OneToOne/ForeignKey.

How do you plan to update the database with the new change to your equation model? If you are using django 1.7+ you can use the built-in migrations. If not you will need to look to something like South. Thirdly you can alter the database by hand.
Anyway, the reason i bring up migrations is because you cant add a null foreign key field. When adding the field to the database table you will need to specify a default FK value, that you can go back and update using #user3154952 suggestion.
Depending on the database you are using, you may be able to remove the foreign-key constraint check temporarily. Allowing you to add the new field to the table without a dummy value, by allowing null.

Related

Django ORM : model with a list of items from another model

In my Django models, I have two models : one called Site and the other SiteFeature.
Object-wise, it is very clear how this should work : every instance of the Site class should have as property a list containing instances of the SiteFeature class, simply because the SiteFeature objects should only exist in relation to a Site object.
Database-wise, it is also very clear how it should work : the SiteFeature table should contain a not-nullable column referencing the primary key id column of the Site table, with a foreign key.
But in terms of Django ORM, I don't know how to code this.
Based on this question, and this other example, it seems the classical way to proceed works the other way round :
The Site model class contains no ORM model field referencing the SiteFeature list.
Instead, the SiteFeature ORM model class has a ForeignKey field referencing the Site class.
I see there is a way to code this out : by adding a function to the Site model class that searches all the related SiteFeature, and make this function a property (decorator #property):
#property
def site_features(self):
return SiteFeature.objects.filter(site_id=site_id)
But that leaves me doubts :
The proper logic for me would also be that when I save, update or create an instance of the Site class, it would also automatically save / update / create the instances of SiteFeature that are related to it. (same thing for deleting the object, but that can be covered by the on_delete=models.CASCADE parameter of the ForeignKey field).
I could add my own save_with_features / update_with_features / create_with_features methods that cascade all but I am not sure what would happen in case of calls made automatically by Django to the standard save / update / create such as in bulk operations.
This problem seems to basic that I suppose there is already a proper way to do it. How would that be ?
Eventually, I solved the problem with the sitefeature_set Manager.
Reference: https://docs.djangoproject.com/en/3.0/topics/db/queries/#following-relationships-backward

django asking to add default values

I am new to django and I am facing some problem.
This was my previous model.
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
I added some data to this model.
After that I added one more field to it.
Now my model looks like this..
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
age = models.IntegerField()
When I ran the command python manage.py makemigrations I got the following error which is obvious.
You are trying to add a non-nullable field 'age' to blog 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 with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option:
I don't want to add default so I deleted the table contents by accessing db.sqlite3
But again after running python manage.py makemigrations I am getting the same error.why?
Even though you have deleted the table, any changes to your Django model will always refer to the previous migration of that model, hence it still requires you to set a default value. If you're still in development, then you could delete the migrations for that model and then makemigrations again. But, this is terrible practice and you should never do this unless you are consciously squashing your models, the better way is to do what Django is telling you and set a default. This'll help in the long run anyways with error handling.
The other answers address how to set defaults quite well.
When you add a new field (i.e., column) to a database table, that field applies to all rows in the table...the existing ones and any new ones. If the field cannot be null, then some value must be specified for all of the existing rows.
By default, IntegerField() is non-nullable. This explains why you are getting the warning. You have a few options:
You can choose a default "one-off" initial value like Django suggests. All existing rows will use this value for the new field.
Assign your own values to each existing row for the new field.
Use the blank and null arguments in your model field to allow the field to accept null values.
For the last option, you'd do this like so:
models.IntegerField(blank=True, null=True)
But that might not be the best choice depending on your use case. If nullable values don't make sense for your field, I would avoid making the field support nulls.
age = models.IntegerField(null=True)
Django models fields are null set to true by default.
The way you can solve this:
Delete the Blog table from the database.
Delete the migrations folder from the app that contains Blog model.
Remove all data from django_migrations table.
Now run makemigrations and then migrate command.
Hope this will solve your problem.
Do what Django is telling you, then you can remove the default attribute if you don't need it. But it's a good practice to keep it anyway

Handling Django/Postgres Natural Foreign Key Cascading Updates

In my application and database, I have Compound and Name models.
# possible names of compound
class Name(models.Model):
compound_name = models.TextField(primary_key=True)
# compound
class Compound(models.Model):
# unique id for the compound
compound_id = models.AutoField(primary_key=True)
# name representing the compound
primary_name = models.ForeignKey(Name, db_column='compound_name', null=True, db_index=True, on_delete=models.PROTECT)
What I'd like to have happen is when I change a name, say 'apsirin' to 'aspirin', this change would cascade to my Compound.
Currently, because of the way Django handles these FK relationships, this doesn't work. If I update a name entry in django, it actually just creates a new entry, and the old compound retains the linkage to the compound.
I've thought of 3 possible solutions, but I'm not quite sure what all the drawbacks are of each.
make a custom 'update' function for my names, that first gets/creates the new name entry, finds all the compounds/other link tables with references to the old name, and then replaces all of the old name values with the new name values, before finally deleting the old name. Possible drawback is that I don't know how this would work if someone was using django admin to update names.
Use signals to intercept an update before it attempted to make the update in the database, and perform something similar to (1)
Create a trigger in postgres to perform something similar to (1) before updates occur. I'm not quite sure if Django would throw an error on this though, since it would be unaware of the trigger.
Its possible I'm just overlooking some basic functionality of models in models.py, and if I am, then hopefully that can solve this issue. But I don't see any kinds of on_update params for the FKs, and I've read that the Django team has labelled this a 'wont fix' issue.
I don't get it yet but:
FK is FK, we just care about FK_id to link it (1)
You can change name of the models and makemigrations (2)
# in SQL, i saw it create model_old, then create a new model and copy (for the function about add and remove field)
# in makemigrations, when change name: it just run the Update query.
When change name, nothing happen to data, it just update the name (3)
When you change the name of a FK (it still just change the name again) (4)
From (1), we can access the data of FK ~> Nothing about change name or something like this can make problem.
So:
Just make sure you create a good models.
To logic should start from model, and fit with your idea.

Django Custom primary key

When I inspect the Trac database I get:
class TicketChange(models.Model):
ticket = models.IntegerField()
time = models.BigIntegerField()
author = models.TextField(blank=True)
field = models.TextField()
oldvalue = models.TextField(blank=True)
newvalue = models.TextField(blank=True)
class Meta:
managed = False
db_table = 'ticket_change'
With not primary key:
>>> TicketChange.objects.all()
DatabaseError: column ticket_change.id does not exist
LINE 1: SELECT "ticket_change"."id", "ticket_change"."ticket", "tick...
Because I need to specify a pk but the original primary key of ticket_change in Trac is:
Primary key (ticket, time, field)
But It's not possible in Django: Django Multi-Column Primary Key Discussion.
If I define time like pk I can't add two tickets changes in the same time.
What can I do?
You're right. It's a known problem. So the only solutions are hacks (sort of).
Your best option is to use django-compositepks. The drawbacks are that it doesn't really support model relationships, so you will not be able to navigate to any relationship from your composite-pk model. However, looking at your TicketChange model this doesn't seem like an issue (unless you have more models with relationships to this one).
Another option would be to manually add the id column (and make sure to apply any additional changes to the db), thereby creating the new one-column primary key. A third option (and what I would probably do), pretty similar to the last one but cleaner, would be to create a new database from scratch and then populate it with a script by fetching the data from your existing legacy db.
Sorry I don't have any better solution for you, that's the way it is. Legacy dbs and django are always a headache, have gone through some similar processes myself. Hope this helps!
This is a use case the Django ORM simply does not support. If you're able to modify the database schema, add an additional column that will serve as the primary key for Django: An integer field with AUTO_INCREMENT (MySQL) or a SERIAL (PostgreSQL). It shouldn't desturb your other applications using the table since it will be managed by your database when new records are inserted. You can still use the actual primary key when making queries through the ORM:
ticket_change = TicketChange.objects.get(ticket=1234, time=5678, field='myfield')
If you’d like to specify a custom primary key, specify primary_key=True on one of your fields. If Django sees you’ve explicitly set Field.primary_key, it won’t add the automatic id column.
Each model requires exactly one field to have primary_key=True (either explicitly declared or automatically added).
https://docs.djangoproject.com/en/3.1/topics/db/models/#automatic-primary-key-fields

How to maintain model fields order structure when using south

Suppose I have this model which is associated with south:
class MyModel(models.Model):
field_a = models.CharField(max_length=30)
field_b = models.CharField(max_length=30)
Now later on I want to add one more field so I created that field between field_a and field_b now my model looks like:
class MyModel(models.Model):
field_a = models.CharField(max_length=30)
field_c = models.CharField(max_length=30)
field_b = models.CharField(max_length=30)
Then I migrated the changes, when checking the table structure in MySQL the field_c is created at the end of all fields. How can I tell south to maintain the fields order e.g. insert after field_a.
In MySQL we can insert the new field before or after any existing field. Is it possible to do that in south?
I do not believe that it is not possible to force the order of field creation in MySQL when using Django (or South). New fields will always be appended to the end of the table definition.
However: The order of the columns should be irrelevant to you. Read on...
If you're writing SQL queries against your database, it is bad form to do select * and rely on the field ordering of the table. Instead you should specify which fields you want to select, and the order in which you want them reported. This makes the underlying tables' field-ordering unimportant.
Additionally, if anyone in your organisation is currently writing reports which use select *: by inserting new columns between others you could easily break reports that use field position (rather than field name), as the indexing of the columns will change.
As Steve Mayne said, there is no direct way to do it if you keep updating your models. There is a hack to do it though.
The first time you create your model, all definitions go one after another, as you have already observed.
If you want to be absolutely sure of the order of your models after you add a new column, log into the MySQL shell. This is usually done with mysql -u<user> -p. Tell it to use your database with USE db.
Then, manually insert the new column with ALTER TABLE table ADD field_c VARCHAR(30) AFTER field_a.
Now you can tell Django about it by editing your MyModel class.
class MyModel(models.Model):
field_a = models.CharField(max_length=30)
field_c = models.CharField(max_length=30)
field_b = models.CharField(max_length=30)
Obviously, this is really annoying if your model changes a lot. Hopefully that doesn't happen.
Here is a work-around that has worked for me:
1) Create your model in your model.py file as you would, but add only the first field.
2) Make the migration, but do not migrate yet.
3) Add each field one by one and make migration after each.
4) After having them all, migrate.
This has worked for me. If you have your model designed already, then you can:
0) Delete your model, make migration, migrate, and then undo deleting (you will lose your data, dump it if needed).
1) cut/delete each field from the bottom one by one but the first one.
2) make migration
3) undo to have the 2nd field back and make migration. Repeat for the others.
4) finally, migrate.

Categories