Django -- new field: How to set default callable for existing objects - python

I have a model:
class Model(models.Model):
price = models.DecimalField(...)
There are already Model objects in production database.
Now I add price_total field to this model which can't be null.
class Model(models.Model):
price = models.DecimalField(...)
price_total = models.DecimalField(...)
I want this price_total to be equal to price right after migration.
Something like:
price_total = models.DecimalField(default=this_object.price,...)
Is it possible to do it somehow?
The only thing I know about is:
make price_total nullable
makemigrations + migrate
set price_total equal to price for example through django shell
make price_total not nullable
makemigration + migrate
But this way has multiple disadvantages, you can forgot to do that in production, it has many steps etc...
Is there a better way?

you can do it by manual edit migration,
do makemigrations with null
do makemigrations with not null
Edit first make migration by add datamigration with update and move operations from second migrate file
remove the second migrations file,
for example:
from django.db import migrations, models
from django.db.models import F
def set_price_total(apps, schema_editor):
# Change the myapp on your
Model = apps.get_model('myapp', 'Model')
Model.objects.update(price_total=F('price'))
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='model',
name='price_total',
field=models.DecimalField(
decimal_places=2, max_digits=10, null=True),
),
migrations.RunPython(set_price_total),
migrations.AlterField(
model_name='model',
name='price_total',
field=models.DecimalField(
decimal_places=2, default=1, max_digits=10),
preserve_default=False,
),
]

You are on track to do it properly.
Just make sure step 3 in done in a datamigration (certainly not through django shell).
This way you won't forget to run it on production.
I'm pretty sure you can't do both add the column and setting the value to be the same as another column.
To convince yourself, you can search for vanilla SQL implementation like https://stackoverflow.com/a/13250005/1435156

Related

Why only id column is shown in migration file when I create model in Python?

from django.db import models
class Town(models.Model):
name: models.CharField(max_length=70,unique=True)
country: models.CharField(max_length=30,unique=True)
class Meta:
pass
This is my model Town whith two attributes: name and country. When I create a migration in the initial_0001.py file only id column is shown
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Town',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
),
]
What kind of problem could it be?
The ID-Field is always automatically generated by Django when making migrations. You can specifiy your own ID field aswell, but using an auto-incremented like this is fine for your use case.
You also might want to get rid of the unique=True, as it would prevent adding multiple towns from the same country.
Create your model like this and redo your migration process:
class Town(models.Model):
name = models.CharField(max_length=70)
country = models.CharField(max_length=30)
When you have 'no changes detected' when making migrations, I usually follow these steps:
First, Delete migrations folder in project.
Then delete django-migration entries in your Database with this query:
DELETE from django_migrations WHERE app='yourAppName'
Then, create new folder ‘migrations” in app folder + init.py
Lastly, redo the migration process:
py manage.py makemigrations
py manage.py migrate --run-syncdb
I had the same problem, and this solved it. Hope it helps.
Instead of : it should be =
You also need to provide a default value for all the previous rows that were created
Then run python manage.py makemigrations
Run python manage.py migrate
class Town(models.Model):
name = models.CharField(max_length=70,unique=True, default='')
country = models.CharField(max_length=30,unique=True, default='')
class Meta:
pass

Django - add required auto-generated `uuid4` to existing project

I'm trying to add internal_code to a Django Model in the existing project.
internal_code = models.CharField(max_length=128, default=uuid.uuid4, unique=True)
The problem is that when running migrate, Django raises IntegrityError:
DETAIL: Key (internal_code)=(b24f1ca6-bd90-4c91-87b0-5f246a4057e1) is duplicated.
I understand that this problem exists only during migrate as it is generated just once.
Is there a way to avoid this behavior without having to do this?:
set field to null=True
migrate
add RunPython that will populate all the existing objects internal_code fields
set field to null=False
EDIT: This is the final migration file. I want to know if I can avoid writing such migration to get the same result (automatic so not touching shell)
from django.db import migrations, models
import uuid
def gen_uuid(apps, schema_editor):
Product = apps.get_model('products', 'Product')
for product in Product.objects.all():
product.my_sku = uuid.uuid4()
product.save(update_fields=['my_sku'])
class Migration(migrations.Migration):
dependencies = [
('products', '0015_auto_20210827_1252'),
]
operations = [
migrations.AddField(
model_name='product',
name='my_sku',
field=models.CharField(max_length=128, null=True),
),
migrations.RunPython(gen_uuid),
migrations.AlterField(
model_name='product',
name='my_sku',
field=models.CharField(default=uuid.uuid4, max_length=128, unique=True),
),
]

makemigrations for a model that is created via inspectdb

I have a database in microsoft sql server. I created tables and views in it.
I ran py manage.py inspetdb view_Name > Models.py and populated my models.py file with managed=false. I also dont want my model to alter my database. I just want it for data retrieval.
Should I definitely run migrate/makemigrations?
After inspectdb should i apply makemigrations on my app or is just migrate enough? And also what are the points to remember while using inspectdb on an existing database.
Also I have something like the below in my models.py file for all columns
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase
Is having the fieldname in lowercase safe ? Or should I change it as it is in my column? And what are those db_column='Created', blank=True, null=True fields. Not all my views have such fields. Only a few have such values.
Models.py contents
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# * Make sure each ForeignKey has `on_delete` set to the desired behavior.
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models
class test1table(models.Model):
created = models.DateTimeField(db_column='Created', blank=True, null=True) # Field name made lowercase.
class Meta:
managed = False
db_table = 'Test1'
Migrations file 0001_intial.py
# Generated by Django 2.1.14 on 2019-11-28 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='test1table',
fields=[
('created', models.DateTimeField(blank=True, db_column='Created', null=True
],
options={
'db_table': 'Test1',
'managed': False,
},
),
]
This solution solved my problem: I created a notepad file, pasted the code, changed the extension to .py and replaced the models.py file.
Success in migrate!

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

Copy values from existing field when create new field of the same model

I have django 1.10 project. There are I have a model Feedback:
class Feedback(FirstMixin, SecondMixin, models.Model):
company = models.OneToOneField(
verbose_name='Company',
to=Company,
related_name='feedback'
)
This model exists and DB table's column Company is filled by keys to Company's items.
Now I need to add some new field to the model:
custom_name = models.CharField(
verbose_name='Company Custom Name',
null=False,
max_length=settings.DATABASE_STRING_LENGTH
)
This field should store custom names of Companies.
What should I do to make values of this field the same as relevant Companies names during migration?
Should I change migration's code or is there are some way to define it in model?
Yes you want to change a migration file that are created.
Try to use following solution
from django.db import migrations, models
from django.db.models import F
def migrate_custome_name(apps, schema_editor):
Feedback = apps.get_model("app_name","Feedback")
Feedback.objects.all().update(
custom_name=F('company'))
class Migration(migrations.Migration):
dependencies = [
------
]
operations = [
migrations.AddField(
model_name='feedback',
name='custom_name',
-- your code --
),
migrations.RunPython(migrate_custome_name), # Add this function to migrate data
]
Hope this will help you.
You can use a data migration, see the Django docs here: https://docs.djangoproject.com/en/1.10/topics/migrations/#data-migrations . You will need to run the operation that sets Feedback.custom_name = Feedback.company after applying the changes to the table.

Categories

Resources