I want to add a new HASH column to my existing django table. I referred How to generate HASH for Django model. Here is how I am doing it:
def _create_uuid():
return uuid.uuid4().hex[:20]
class users(models.Model):
user_id = models.CharField(max_length=100, primary_key=True)
uuid= models.CharField(max_length=20, null=False, default=_create_uuid)
While this works completely fine for all the new users created after the addition of the uuid column. But all the existing users get assigned with the same uuid in the uuid column when I migrate. I want the existing users to have unique uuid as well. How can I do this?
You could run a Python script during the migration to generate the hashes. Generate a migrations file first with an empty default value. Then add a RunPython function and alter the field to use your _create_uuid default function for new values.
The migration script could look like this:
from django.db import migrations, models
import YOURPROJECT.YOURAPP.models
import uuid
def generate_hash(apps, schema_editor):
User = apps.get_model("YOURAPP", "users")
users = User.objects.all()
for u in users:
u.uuid = uuid.uuid4().hex[:20]
u.save()
def reverse_generate_hash(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('YOUTAPP', 'PREVIOUS_MIGRATIONS_FILE'),
]
operations = [
migrations.AddField(
model_name='users',
name='uuid',
field=models.CharField(default='', max_length=20),
),
migrations.RunPython(generate_hash, reverse_generate_hash),
migrations.AlterField(
model_name='users',
name='uuid',
field=models.CharField(default=jYOURPROJECT.YOURAPP.models._create_uuid, max_length=20),
),
]
Related
I have a django model to which I would like to add a UUIDField :
uuid = models.UUIDField(
default = uuid.uuid4,
unique = True,
editable = False
)
I'd like this uuid to be unique, however at migration of an existing database, I have the following error :
django.db.utils.IntegrityError: (1062, "Duplicate entry '3ba46cb5f7f340ffa43e348cb789901a' for key 'carbon.uuid'")
How can I provide to default an other uuid if this one is already used ?
You have lot of problems in here, there are 3 steps to achieve your goal.
If you have data already in your model you have to define it as null=True and remove the default=uuid.uuid4. Why? Because if you put a default value, it will try to add the same default address to all the models when you run your migration so it will of course give you the error django.db.utils.IntegrityError: (1062, "Duplicate entry '3ba46cb5f7f340ffa43e348cb789901a' for key 'carbon.uuid'") because use the same uuid for each row.
Create a migration to update your existing rows in the model and put them a uuid as following:
phython src/manage.py makemigrations app --name set_uuid_mymodel --empty
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
import uuid
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
for row in MyModel.objects.all():
row.uuid = uuid.uuid4()
row.save(update_fields=['uuid'])
class Migration(migrations.Migration):
dependencies = [
('myapp', '0004_add_uuid_field'),
]
operations = [
# omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
]
Look at the official documentation: https://docs.djangoproject.com/en/3.0/howto/writing-migrations/#migrations-that-add-unique-fields
Then you can manage new rows generation uuid in the save method of the model:
def save(self, *args, **kwargs):
''' On save, update uuid and ovoid clash with existing code '''
if not self.uuid:
self.uuid = uuid.uuid4()
return super(YourModel, self).save(*args, **kwargs)
To add a new required field (null=False) for a model, you should include a default value. This value will be applied to all existing records.
For UUIDField, during migrations, Django does not create a new UUID value for each record. A single value is generated through uuid.uuid4() and applied to all records using a single SQL. So, knowing that unique=True is applied at database-level, if you have existing records on your database, during migration an IntegrityError will be raised for this reason.
If you need the existing records with a valid uuid value (you cannot recreate the entire model migration with the uuid field or just drop the existing records before run the migration) is possible force a new value for each record using a RunPython operation inside your migration.
Edit your migration to do the following steps:
Add a nullable uuid field, without unique and default settings, so Django can create the column without integrity errors.
Add a custom Python code to force the uuid for existing records
Alter the uuid field, recovering the original settings
import uuid
from django.db import migrations
def force_uuid(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version
MyModel = apps.get_model('myapp', 'MyModel')
db_alias = schema_editor.connection.alias
for instance in MyModel.objects.using(db_alias).all():
instance.uuid = uuid.uuid4()
instance.save()
class Migration(migrations.Migration):
dependencies = []
operations = [
# Pre generate the uuid field (nullable)
migrations.AddField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(null=True, unique=False, editable=False),
),
# Force uuid on existing records
migrations.RunPython(force_uuid),
# Recovering original settings
migrations.AlterField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(
default=uuid.uuid4, null=False, unique=True, editable=False
),
),
]
Notes
If you have a lot of records to generate a unique uuid, you can benefit from bulk_create() instead loop over all records and adds a lot of single inserts
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),
),
]
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!
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),
]
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.