ValueError: Cannot assign error in Django foreign key - python

I have three different Django apps in my project: questions, answers, and students. I created demo data for both Question and Student, and I want their objects to be foreign keys of Answer.
Below is the migration file of Answer.
from django.db import migrations
from django.apps import apps
questions = apps.get_model('questions', 'Question')
students = apps.get_model('students', 'Student')
def create_data(apps, schema_editor):
Answer = apps.get_model('answers', 'Answer')
Answer(question=questions.objects.get(pk="1"),
student=students.objects.get(pk="2"), answer="Main function").save()
class Migration(migrations.Migration):
dependencies = [
('answers', '0001_initial'),
]
operations = [
migrations.RunPython(create_data)
]
When I run python manage.py migrate, I get the error below.
ValueError: Cannot assign "<Question: What is this?>": "Answer.question" must be a "Question" instance.
Can please someone help me with this?

Check if there are questions and student rows with the ids that you need and then set the foreign keys using ids. Example:
answer = Answer(answer="Main function")
answer.question_id = 1
answer.student_id = 2
answer.save()
P.S. no need to import apps from django.apps, it's the first argument of create_data function. I suggest to use that instead.

Related

Django migration with uuid field generates duplicated values

I have a uuid field (not a primary key). The generated migration is:
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
....
]
operations = [
...
migrations.AddField(
model_name='device',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
...
]
But when doing python manage.py migrate it is crashing with:
django.db.utils.IntegrityError: could not create unique index
"restaurants_device_uuid_key" DETAIL: Key
(uuid)=(f3858ded-b8e0-4ac0-8436-8a61b10efc73) is duplicated.
Strangely enough, the problem does not seem to occur with primary keys (which are maybe created by the database, and not internally by django?)
How can I add a uuid field, and make sure that migrations work?
Here is an example doing everything in one single migration thanks to a RunPython call.
# -*- coding: utf-8 -*
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
def create_uuid(apps, schema_editor):
Device = apps.get_model('device_app', 'Device')
for device in Device.objects.all():
device.uuid = uuid.uuid4()
device.save()
class Migration(migrations.Migration):
dependencies = [
('device_app', 'XXXX'),
]
operations = [
migrations.AddField(
model_name='device',
name='uuid',
field=models.UUIDField(blank=True, null=True),
),
migrations.RunPython(create_uuid),
migrations.AlterField(
model_name='device',
name='uuid',
field=models.UUIDField(unique=True)
)
]
(Answer taken from the first comment)
See the django docs - Migrations that add unique fields
They recommend changing your single migration into three separate migrations:
Create field, set to null but not unique
Generate unique UUIDs
Alter the field to be unique
In the mode, you have configured, that you want unique values for the uuid fields, but with default values(the same for all). So if you have two 'device' objects in the database, the migrations add 'uuid' field to them with the default 'uuid.uuid4' value and when it tries to set it to the second one, it crashes because of the unique constrains.
If you drop your db and create new objects probably there will be not problems but thats not a solution for production db obviously :D.
A better solution is to create a data migration which sets different uuid value (generated by the default 'uuid' library) to every existing object in the database. You can read more about data migrations here:
https://docs.djangoproject.com/en/1.10/topics/migrations/#data-migrations
Then, when you create new objects, django will generate different uuid automatically. ;)
For the primary keys: Django adds it to the model by default.
You can provide a management command to populate the uuid field after uu_id column is created in the model but this has to be done after migrating the model and setting the field default as None:
from django.core.management.base import BaseCommand
from django.apps import apps
import uuid
class Command(BaseCommand):
def handle(self, *args, **options):
classes()
def classes():
app_models = apps.get_app_config('appname').get_models()
for model in app_models:
field = None
try:
field = model._meta.get_field('uu_id')
except:
pass
if field:
uu_id_list = list(model.objects.all().values_list('uu_id',flat=True))
if None in uu_id_list:
for row in model.objects.all():
row.uu_id = uuid.uuid4()
row.save()

Data migrations for OneToOneField in django

I have a Product model and I want to extend by using OneToOneField.
For example
class Product:
name = models.CharField(..)
price = models.FloatField(...)
I want to do like this
class MyProduct:
product = models.OneToOneField(myapp.Product, on_delete=models.CASCADE)
location = models.CharField(...)
and using signal
def create_myproduct(sender, instance, created, **kwargs):
"""Create MyProduct class for every new Product"""
if created:
MyProduct.objects.create(product=instance)
signals.post_save.connect(create_myproduct, sender=Product, weak=False,
dispatch_uid='models.create_myproduct')
This works for newly created Product, so I can do like this in template.
{{ product.myproduct.location }}
But Old products that created before adding this OneToOneRelation,has no field 'myproduct' and that template code didn't work.
I heard I need a data migrations for old product using RunPython or manage.py shell. Can you teach me how to do? I read a documentation from django, but still don't fully understand.
you can add new migration. and apply it.
something like this code:
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-22 06:04
from __future__ import unicode_literals
from django.db import migrations, models
def create_myproducts(apps, schema_editor):
Product = apps.get_model('myapp', 'Product')
MyProduct = apps.get_model('myapp', 'MyProduct')
for prod in Product.objects.all():
MyProduct.objects.create(product=prod)
class Migration(migrations.Migration):
dependencies = [
('myapp', 'your last migration'),
]
operations = [
migrations.RunPython(create_myproducts)
]
I just found out.
Like Rohit Jain said
product.myproduct is None.
When I tried to access product.myproduct, I got an exception that object does not exist. It has a relation to myproduct but the actual object doesn't exist.
What I really want was creating MyProduct object and add it to Product class.
So I did it in python manage.py shell
products = Product.objects.all()
for prod in products:
if not hasattr(prod, 'myproduct'):
prod.myproduct = MyProduct.objects.create(product=prod)
prod.save()
I think it works for me now.
Thank you guys
You should just migrate your models in a normal way
python manage.py makemigrations
python manage.py migrate
During making migrations you will be asked how to fill new fields for existing data
Please notice that when you are using Django under 1.7 version you do not have migrations (and syncdb will not do the job for existing tables) - consider using the 3rd part tool like south

Django 1.8 'default' option only executes external function once

I am trying to add a UUID field to an existing table. I specified that default = uuid.uuid4 however, it Django doesn't seem to call uuid.uuid4 function for every row. So when I migrate I keep getting duplicated uuid error.
My Django version is 1.8.2.
from django.db import models, migrations
import uuid
class Migration(migrations.Migration):
dependencies = [
('conv', '0008_video_video_uri'),
]
operations = [
migrations.AddField(
model_name='conversation',
name='channel_id',
field=models.UUIDField(unique=True, default=uuid.uuid4, editable=False),
),
]
Below the error:
> > File "/home/yonk/projects/trailerapp/venv/local/lib/python2.7/site-packages/django/db/backends/utils.py",
> line 64, in execute
> return self.cursor.execute(sql, params) django.db.utils.IntegrityError: could not create unique index
> "conv_conversation_channel_id_68f7d58df7c78d61_uniq" DETAIL: Key
> (channel_id)=(5f512cbe-e514-4bf5-bf5a-3efd1a94e401) is duplicated.
Here you have django docs describing exactly what you want:
https://docs.djangoproject.com/en/1.8/howto/writing-migrations/#migrations-that-add-unique-fields
You will need two migration files.
First one Adds fields, also change unique=True to null=True so django won't try to use default value...
Second migration populates the field.
So second migration should look like this:
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
for row in MyModel.objects.all():
row.uuid = uuid.uuid4()
row.save()
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),
]
In order to get a new value each time, you will need to set the default to a callable, otherwise as you have observed, uuid.uuid4 will be calculated once and then that single value will be used each time.
This other StackOverflow question and answer shows how to pass a callable as the default value.
EDIT: This answer only applies to versions of Django 1.7 and lower.

ValueError: Cannot assign User: issue on my OneToOneField relationship

I'm facing a really odd problem with OneToOneField. I've a really simple model like
class Doctor(models.Model):
user = models.OneToOneField(User)
The problem is with my method RunPython in the migration. I've written a 0002_addusers migration that depends on 0001_initial and the code is the following:
class Migration(migrations.Migration):
def create_users(apps, schema_editor):
u = User.objects.create_superuser('admin', 'admin#aaa.com', 'admin')
u.save()
du = User.objects.create_user(username='doc01', password='doc01')
du.save()
def create_doctors(apps, schema_editor):
Doctor = apps.get_model('custom_user', 'Doctor')
du = User.objects.get(username='doc01')
d = Doctor(user=du)
d.save()
dependencies = [
('custom_user', '0001_initial')
]
operations = [
migrations.RunPython(create_users),
migrations.RunPython(create_doctors),
]
What is really weird for me is that this really simple code works in views, works in shell, works everywhere except in the migration :)
The traceback is the follow:
line 23, in create_doctors
d = Doctor(user=du)
...
ValueError: Cannot assign "<User: doc01>": "Doctor.user" must be a "User" instance.
Thank you a lot for any support!
EDIT:
I found out the solution. I just had to call the RunPython
migrations.RunPython(create_users, create_doctor)
as Avinash suggested even without moving the functions outside the class.
It seems that subsequent functions have to be called as arguments of a single RunPython call.
The suggested answer to run migrations.RunPython(create_users, create_doctor) doesn't solve your issue, it just makes it invisible.
The second argument of RunPython is the function that will be called during a rollback, this is why it did not raise any exception when migrating upwards. You never called the function create_doctors.
Your issue is caused by du not being a User instance. This can be caused in migrations when not using apps.get_model to get the model class. You should use the following code instead:
class Migration(migrations.Migration):
def create_users(apps, schema_editor):
User = apps.get_model('auth', 'User') # Here you get the user programatically, it is a good practise in migrations
u = User.objects.create_superuser('admin', 'admin#aaa.com', 'admin')
u.save()
du = User.objects.create_user(username='doc01', password='doc01')
du.save()
def create_doctors(apps, schema_editor):
Doctor = apps.get_model('custom_user', 'Doctor')
User = apps.get_model('auth', 'User') # Here you get the user programatically, it is a good practise in migrations
du = User.objects.get(username='doc01')
d = Doctor(user=du)
d.save()
dependencies = [
('custom_user', '0001_initial')
]
operations = [
migrations.RunPython(create_users),
migrations.RunPython(create_doctors),
]
I think the problem is in your migration code. Define your methods outside the Migration class then call it from migration's RunPython command.
Try below code in your migration file. This will work.
def create_users(apps, schema_editor):
u = User.objects.create_superuser('admin', 'admin#aaa.com', 'admin')
u.save()
du = User.objects.create_user(username='doc01', password='doc01')
du.save()
def create_doctors(apps, schema_editor):
Doctor = apps.get_model('custom_user', 'Doctor')
du = User.objects.get(username='doc01')
# We can't import the Doctor model directly, But we can create it. Try this -
Doctor.objects.create(user=du)
class Migration(migrations.Migration):
dependencies = [
('custom_user', '0001_initial')
]
operations = [
migrations.RunPython(create_users, create_doctors),
]
Importing models in a "migration" file will not work if done the "traditional" way. Check this https://www.spheron1.uk/2016/05/15/valueerror-in-django-migration/ to import User model and the documentation here https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations for an example.

Django 1.7 datamigration and user groups

I'm trying to implement a datamigration using django 1.7 native migration system. Here is what I've done.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def create_basic_user_group(apps, schema_editor):
"""Forward data migration that create the basic_user group
"""
Group = apps.get_model('auth', 'Group')
Permission = apps.get_model('auth', 'Permission')
group = Group(name='basic_user')
group.save()
perm_codenames = (
'add_stuff',
'...',
)
# we prefere looping over all these in order to be sure to fetch them all
perms = [Permission.objects.get(codename=codename)
for codename in perm_codenames]
group.permissions.add(*perms)
group.save()
def remove_basic_user_group(apps, schema_editor):
"""Backward data migration that remove the basic_user group
"""
group = Group.objects.get(name='basic_user')
group.delete()
class Migration(migrations.Migration):
"""This migrations automatically create the basic_user group.
"""
dependencies = [
]
operations = [
migrations.RunPython(create_basic_user_group, remove_basic_user_group),
]
But when I try to run the migration, I got a LookupError exception telling me that no app with label 'auth' could be found.
How can I create my groups in a clean way that could also be used in unit tests ?
I've done what you are trying to do. The problems are:
The documentation for 1.7 and 1.8 is quite clear: If you want to access a model from another app, you must list this app as a dependency:
When writing a RunPython function that uses models from apps other than the one in which the migration is located, the migration’s dependencies attribute should include the latest migration of each app that is involved, otherwise you may get an error similar to: LookupError: No installed app with label 'myappname' when you try to retrieve the model in the RunPython function using apps.get_model().
So you should have a dependency on the latest migration in auth.
As you mentioned in a comment you will run into an issue whereby the permissions you want to use are not created yet. The problem is that the permissions are created by signal handler attached to the post_migrate signal. So the permissions associated with any new model created in a migration are not available until the migration is finished.
You can fix this by doing this at the start of create_basic_user_group:
from django.contrib.contenttypes.management import update_contenttypes
from django.apps import apps as configured_apps
from django.contrib.auth.management import create_permissions
for app in configured_apps.get_app_configs():
update_contenttypes(app, interactive=True, verbosity=0)
for app in configured_apps.get_app_configs():
create_permissions(app, verbosity=0)
This will also create the content types for each model (which are also created after the migration), see below as to why you should care about that.
Perhaps you could be more selective than I am in the code above: update just some key apps rather than update all apps. I've not tried to be selective. Also, it is possible that both loop could be merged into one. I've not tried it with a single loop.
You get your Permission objects by searching by codename but codename is not guaranteed to be unique. Two apps can have models called Stuff and so you could have an add_stuff permission associated with two different apps. If this happens, your code will fail. What you should do is search by codename and content_type, which are guaranteed to be unique together. A unique content_type is associated with each model in the project: two models with the same name but in different apps will get two different content types.
This means adding a dependency on the contenttypes app, and using the ContentType model: ContentType = apps.get_model("contenttypes", "ContentType").
As said in https://code.djangoproject.com/ticket/23422, the signal post_migrate should be sent before dealing with Permission objects.
But there is a helper function already on Django to sent the needed signal: django.core.management.sql.emit_post_migrate_signal
Here, it worked this way:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.core.management.sql import emit_post_migrate_signal
PERMISSIONS_TO_ADD = [
'view_my_stuff',
...
]
def create_group(apps, schema_editor):
# Workarounds a Django bug: https://code.djangoproject.com/ticket/23422
db_alias = schema_editor.connection.alias
try:
emit_post_migrate_signal(2, False, 'default', db_alias)
except TypeError: # Django < 1.8
emit_post_migrate_signal([], 2, False, 'default', db_alias)
Group = apps.get_model('auth', 'Group')
Permission = apps.get_model('auth', 'Permission')
group, created = Group.objects.get_or_create(name='MyGroup')
permissions = [Permission.objects.get(codename=i) for i in PERMISSIONS_TO_ADD]
group.permissions.add(*permissions)
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
('myapp', '0002_mymigration'),
]
operations = [
migrations.RunPython(create_group),
]
So, I figure out how to solve this problem and I get the following exit: get_model will only fetch Your model apps. I don't have sure about if this would be a good pratice, but it worked for me.
I just invoked the model Directly and made the changes.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.contrib.auth.models import Group
def create_groups(apps, schema_editor):
g = Group(name='My New Group')
g.save()
class Migration(migrations.Migration):
operations = [
migrations.RunPython(create_groups)
]
And then, just apply a /manage.py migrate to finish.
I hope it helps.

Categories