ValueError: Cannot assign User: issue on my OneToOneField relationship - python

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.

Related

Django models default function run for every current object

models.py
class Subscription(models.Model):
#... many fields ...
# I added this field when I already had many objects
uniqueSubscriptionId = models.CharField(default=generateUniqueSubscription, max_length=30)
generateUniqueSubscription
from django.utils.crypto import get_random_string
def generateUniqueSubscription():
return get_random_string(20)
The Problem is that, when I run migrations, all of my old objects get the same uniqueSubscriptionId. I want each and every single old object to get a unique uniqueSubscriptionId.
How can I do that?
Here's what I did:
models.py
def updateOldSubscriptionObjs(apps, schema_editor):
old_subscription_model = apps.get_model("app_label", "Profile")
for obj in old_subscription_model.objects.all():
obj.uniqueSubscriptionId = generateUniqueSubscription()
obj.save()
class Subscription(models.Model):
#... many fields ...
# I added this field when I already had many objects
uniqueSubscriptionId = models.CharField(default=generateUniqueSubscription, max_length=30)
Then I ran makemigrations:
python manage.py makemigrations
Then edited the latest migration file:
class Migration(migrations.Migration):
dependencies = [
# forget this
]
operations = [
# .... many things ...
migrations.RunPython(updateOldProfileObjs)
]
Then ran migrate:
python manage.py migrate
And voila, all old objects got updated, and also, any new object will also get updated as I specified default.
If you are lazy like me, and don't want to do these things, then open django python shell:
python manage.py shell
and then execute this function in shell:
def updateOldSubscriptionObjs():
for obj in Subscription.objects.all():
obj.uniqueSubscriptionId = generateUniqueSubscription()
obj.save()
I wish if there was some built-in django feature for this.

Django Custom Migration Not Executing

So I added a new "status" field to a django database table. This field needed a default value, so I defaulted it to "New", but I then added a custom migration file that calls the save() method on all of the objects in that table, as I have the save() overridden to check a different table and pull the correct status from that. However, after running this migration, all of the statuses are still set to "New", so it looks like the save isn't getting executed. I tested this by manually calling the save on all the objects after running the migration, and the statuses are updated as expected.
Here's the table model in models.py:
class SOS(models.Model):
number = models.CharField(max_length=20, unique=True)
...
# the default="New" portion is missing here because I have a migration to remove it after the custom migration (shown below) that saves the models
status = models.CharField(max_length=20)
def save(self, *args, **kwargs):
self.status = self.history_set.get(version=self.latest_version).status if self.history_set.count() != 0 else "New"
super(SOS, self).save(*args, **kwargs)
And here is the migration:
# Generated by Django 2.0.5 on 2018-05-23 13:50
from django.db import migrations, models
def set_status(apps, schema_editor):
SOS = apps.get_model('sos', 'SOS')
for sos in SOS.objects.all():
sos.save()
class Migration(migrations.Migration):
dependencies = [
('sos', '0033_auto_20180523_0950'),
]
operations = [
migrations.RunPython(set_status),
]
So it seems pretty clear to me that I'm doing something wrong with the migration, but I matched it exactly to what I see in the Django Documentation and I also compared it to this StackOverflow answer, and I can't see what I'm doing wrong. There are no errors when I run the migrations, but the custom one I wrote does run pretty much instanteously, which seems strange, as when I do the save manually, it takes about 5 seconds to save all 300+ entries.
Any suggestions?
P.S. Please let me know if there are any relevant details I neglected to include.
When you run migrations and get Model from apps you can not use custom managers or custom save or create or something like that. This model only have the fields and that's all. If you want to achieve what you want you should add your logic into you migrations like this:
# comment to be more than 6 chars...
def set_status(apps, schema_editor):
SOS = apps.get_model('sos', 'SOS')
for sos in SOS.objects.all():
if sos.history_set.exists():
sos.status = sos.history_set.get(version=sos.latest_version).status
else:
sos.status = "New"
sos.save()

Querying model data in form for cleaned DB

So I have forms.py which is as below:
from django import forms
from .models import SipExtension
from xmpp.models import xmpp_buddy_groups
class ExtensionForm(forms.Form):
xmpp_buddy_groups_choices = xmpp_buddy_groups.objects.values_list('group_name',flat=True)
boolean_choices=(('Yes','Yes'),('No','No'))
sip_extension = forms.IntegerField(min_value=0,max_value=100000)
sip_secret = forms.CharField(required=True,max_length=32)
commlink_push = forms.ChoiceField(choices=boolean_choices,widget=forms.CheckboxSelectMultiple,required=True)
real_name = forms.CharField(required=True,max_length=32)
xmpp = forms.ChoiceField(choices=boolean_choices,widget=forms.CheckboxSelectMultiple,required=True)
xmpp_username = forms.CharField(required = True,min_length=5)
xmpp_password = forms.CharField(max_length=32, widget=forms.PasswordInput)
xmpp_buddy_groups_names = forms.MultipleChoiceField(choices=xmpp_buddy_groups_choices,widget=forms.CheckboxSelectMultiple,required=False)
It works fine if my DB is already created by previous migrations. But I faced problem when my DB is blank. To test,I dropped all the tables and then run make migrations and got below error:
django.db.utils.ProgrammingError: relation "extensions_sipextension" does not exist
LINE 1: ...p_buddy_groups_names"."xmpp_buddy_groups_id" FROM "extension...
I am getting problems in handling this on blank database when I need to deploy on entirely new system. I could handle that by commenting the urls which are executing views which needs this form but that is a bad and temporary work around. How to fix this?
I think the issue is with xmpp_buddy_groups_choices attribute in the form. The queryset is evaluated at time of project load. So when you are using ./manage.py makemigrations, the xmpp_buddy_groups_choices trying to evaluate the queryset, but failed as there is no table in database. Try to wrap the choices in a function and pass that function in the choices parameter.
And your choices is required to be a list of tuples (with 2 elements), but the xmpp_buddy_groups_choices is a list of strings.
Sample Code:
class ExtensionForm(forms.Form):
#staticmethod
def get_group_choices():
xmpp_buddy_groups_choices = xmpp_buddy_groups.objects.values_list('group_name',flat=True)
return xmpp_buddy_groups_choices
boolean_choices=(('Yes','Yes'),('No','No'))
sip_extension = forms.IntegerField(min_value=0,max_value=100000)
sip_secret = forms.CharField(required=True,max_length=32)
commlink_push = forms.ChoiceField(choices=boolean_choices,widget=forms.CheckboxSelectMultiple,required=True)
real_name = forms.CharField(required=True,max_length=32)
xmpp = forms.ChoiceField(choices=boolean_choices,widget=forms.CheckboxSelectMultiple,required=True)
xmpp_username = forms.CharField(required = True,min_length=5)
xmpp_password = forms.CharField(max_length=32, widget=forms.PasswordInput)
xmpp_buddy_groups_names = forms.MultipleChoiceField(choices=ExtensionForm.get_group_choices,widget=forms.CheckboxSelectMultiple,required=False)

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

Testing with a custom user model as a ForeignKey in Django 1.5

I'm using Django 1.5 and I'm trying to make an application work with any custom user model. I've changed the app to use get_user_model everywhere and the app itself is not showing any problems so far.
The issue is that I want to be able to test the app as well, but I can't find a way to make ForeignKey model fields to test correctly using custom user models. When I run the test case attached below, I get this error:
ValueError: Cannot assign "<NewCustomUser: alice#bob.net>": "ModelWithForeign.user" must be a "User" instance.
This is the file I'm using for testing:
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.tests.custom_user import CustomUser, CustomUserManager
from django.db import models
from django.test import TestCase
from django.test.utils import override_settings
class NewCustomUser(CustomUser):
objects = CustomUserManager()
class Meta:
app_label = 'myapp'
class ModelWithForeign(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
#override_settings(
AUTH_USER_MODEL = 'myapp.NewCustomUser'
)
class MyTest(TestCase):
user_info = {
'email': 'alice#bob.net',
'date_of_birth': '2013-03-12',
'password': 'password1'
}
def test_failing(self):
u = get_user_model()(**self.user_info)
m = ModelWithForeign(user=u)
m.save()
I'm referencing the user model in the ForeignKey argument list as described here, but using get_user_model there doesn't change anything, as the user attribute is evaluated before the setting change takes place. Is there a way to make this ForeignKey play nice with testing when I'm using custom user models?
I asked about this on the Django mailing list as well but it seems that, at least currently, there is no way to change the settings.AUTH_USER_MODEL and have it work nicely with a ForeignKey.
So far, in order to test my app, I've created a runtests.py file from this answer:
import os, sys
from django.conf import settings
if len(sys.argv) >= 2:
user_model = sys.argv[1]
else:
user_model = 'auth.User'
settings.configure(
...
AUTH_USER_MODEL=user_model,
...
)
...
And added a bash script to actually run the tests using different user models:
for i in "auth.User" "myapp.NewCustomUser"; do
echo "Running with AUTH_USER_MODEL=$i"
python runtests.py $i
if [ $? -ne 0 ]; then
break
fi
done
The last bit is to use a function to actually retrieve the right user model info instead of just using a "static" variable:
def get_user_info():
if settings.AUTH_USER_MODEL == 'auth.User':
return {default user info}
if settings.AUTH_USER_MODEL == 'myapp.NewCustomUser':
return {my custom user info}
raise NotImplementedError
I'm not claiming this to be a correct answer for the problem, but so far... It works.

Categories