Converting Django BooleanField to NullBooleanField and changing default - django

I need to change a field from
my_boolean = models.BooleanField(verbose_name="Safe Visiting Space", default=False)
to
my_boolean = models.NullBooleanField(verbose_name="Safe Visiting Space", default=None, blank=True, null=True)
So I've made the above change within the model and run makemigrations to create
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('my_app', '0164_auto_20201027_0820'),
]
operations = [
migrations.AlterField(
model_name='mymodel',
name='my_boolean',
field=models.NullBooleanField(default=None, verbose_name='Safe Visiting Space'),
),
]
But this will not set the default of the current 70k records to None but will leave them as False, so I amended the migrations file to
from django.db import migrations, models
from my_app.models import MyModel
def set_my_boolean_default(apps, schema_editor):
objects= MyModel.objects.active().filter(my_boolean=False)
for object in objectss:
object.my_boolean = None
object.save()
class Migration(migrations.Migration):
dependencies = [
('providers', '0164_auto_20201027_0820'),
]
operations = [
migrations.AlterField(
model_name='organisation',
name='infection_control_safe_visiting_space',
field=models.NullBooleanField(default=None, verbose_name='Safe Visiting Space'),
),
migrations.RunPython(set_my_boolean_default),
]
This will take hours to run. Also, a random check of the database and it doesn't seem to be updating any of the records.
What is the right / better way to do this?

It's taking you so long because of this for loop:
objects= MyModel.objects.active().filter(my_boolean=False)
for object in objects:
object.my_boolean = None
object.save() # Database roundtrip
Namely, for each object, you are hitting the database. The better way is to bulk_update the fields all at once:
objects= MyModel.objects.active().filter(my_boolean=False).update(my_boolean=None)
You can perform the query above inside your shell. It's better not to include it in your migration files for it may be executed each time you run migrations.
Enter you Django shell by python manage.py shell, import your model, and execute the query above.

Related

How to set default value for a model field based on enum in Django?

I'm using Django 2.2.5 and have multiple choice fields based on enums in my models. For an unknown reason I now get a migration error when using an enum for choice field during migration:
django.db.utils.OperationalError: (1067, "Invalid default value for 'protocol'")
model.py
from django.db import models
# See class above
from .utils import NetworkProtocolList
class Networks(models.Model):
ipv4 = models.GenericIPAddressField(blank=False, null=False)
protocol = models.CharField(choices=NetworkProtocolList.choices(), max_length=20,default=NetworkProtocolList.ETH)
class Meta:
managed = True
db_table = 'networks'
utils.py
from enum import Enum
class NetworkProtocolList(Enum):
ETH = 'Ethernet'
MPLS = 'MPLS'
#classmethod
def choices(cls):
return [(key.name, key.value) for key in cls]
I issued
manage.py makemigrations
and subsequent
manage.py migrate
generated the following error:
django.db.utils.OperationalError: (1067, "Invalid default value for
'protocol'")
xxxx_auto_xxxxxxxx_xxxx.py
# Auto generated migration file
import my_.utils
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('my_app', 'yyyy_auto_yyyyyyyy_yyyy'),
]
operations = [
migrations.AddField(
model_name='networks',
name='protocol',
# Field definition here, pay attention to the default value
field=models.CharField(choices=[('ETH', 'Ethernet'), ('MPLS', 'MPLS')], default=my_app.utils.NetworkProtocolList('Ethernet'), max_length=20),
),
]
Than I edited migration file to manually set the default to a string instead of calling enum class:
xxxx_auto_xxxxxxxx_xxxx.py
# Edited migration file
import my_.utils
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('my_app', 'yyyy_auto_yyyyyyyy_yyyy'),
]
operations = [
migrations.AddField(
model_name='networks',
name='protocol',
# Field definition here, pay attention to the modified default value
field=models.CharField(choices=[('ETH', 'Ethernet'), ('MPLS', 'MPLS')], default='Ethernet', max_length=20),
),
]
Now migration works properly but I wonder why I can't define default value like before using the enum instead of a litteral string, because I have other model field for whom this has worked properly.
Is that a bug, what am I lissing here, How to set default value for a model field based on enum in Django?
Since you are using Django<3.X, Django isn't able to identify the enum value. So, use the .value property of enum class as
protocol = models.CharField(
choices=NetworkProtocolList.choices(),
max_length=20,
default=NetworkProtocolList.ETH.value # &lt--- change is here
)

how to add another 0000n_initial.py file from makemigrations command

i want to add another initial.py file from makemigrations file
when i try to py manage.py makemigrations it show error
you are trying to add a non-nullable field 'role' to user without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix :
Provide a one-off default now(will be set on all existing rows with a null value for this column)
Quit, and let me add a default in models.py
i want to make another init to make a dependent dropdown
this is what i write in models.py
import datetime
from django.db import models
from django.utils import timezone
class TableAll(models.Model):
table_name = models.CharField(max_length=250)
and this is the code that 0001_initial.py has
# Generated by Django 2.2.2 on 2019-10-29 03:52
from django.db import migrations, models
from django.contrib.auth.models import User
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
role = models.CharField(max_length=250)
role.contribute_to_class(User,'role')
operations = [
migrations.CreateModel(
name='TableAll',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('table_name', models.CharField(max_length=250)),
],
),
]
i want to add a foreign key to another model and want to makemigrations to add a new 00002_initial.py(for example) and i want to have something like this(here is the code i found from github source)
new initial file (0002_add_initial_data.py) //this is just an example from github source code where it has 0001_initial.py and 0002_add_initial_data.py to make the dependent dropdown works
class Migration(migrations.Migration):
Country = apps.get_model('hr', 'Country')
City = apps.get_model('hr', 'City')
india = Country.objects.create(name='India')
City.objects.create(name='Bengaluru', country=india)
City.objects.create(name='Mumbai', country=india)
City.objects.create(name='Chennai', country=india)
City.objects.create(name='Hyderabad', country=india)
City.objects.create(name='New Delhi', country=india)
usa = Country.objects.create(name='United States')
City.objects.create(name='New York', country=usa)
City.objects.create(name='San Francisco', country=usa)
City.objects.create(name='Los Angeles', country=usa)
City.objects.create(name='Chicago', country=usa)
City.objects.create(name='Seattle', country=usa)
russia = Country.objects.create(name='Russia')
City.objects.create(name='Moscow', country=russia)
City.objects.create(name='Saint Petersburg', country=russia)
City.objects.create(name='Yekaterinburg', country=russia)
City.objects.create(name='Kazan', country=russia)
City.objects.create(name='Krasnodar', country=russia)
brazil = Country.objects.create(name='Brazil')
City.objects.create(name='Sao Paulo', country=brazil)
City.objects.create(name='Rio de Janeiro', country=brazil)
City.objects.create(name='Belo Horizonte', country=brazil)
City.objects.create(name='Curitiba', country=brazil)
City.objects.create(name='Recife', country=brazil)
uk = Country.objects.create(name='United Kingdom')
City.objects.create(name='London', country=uk)
City.objects.create(name='Huddersfield', country=uk)
City.objects.create(name='Glasgow', country=uk)
City.objects.create(name='Edinburgh', country=uk)
City.objects.create(name='Cambridge', country=uk)
dependencies = [
('hr', '0001_initial'),
]
operations = [
migrations.RunPython(add_initial_data),
]
Thankyou for the help, im new to this django framework , and i need to use it for my thesis ..
There is a pattern that can be followed when a non-nullable field is being added to a model and the value of that field is the result of a script.
Add the field to your model but make it nullable (null=True)
Run makemigrations to create a migration that adds the nullable field
Create another migration (makemigrations --empty) that fills in this column using migrations.RunPython
Remove null=True from the field and run makemigrations again. When prompted with the selection choose 1 "Provide a one-off default now" and enter any integer you like as all rows should already have a value it will not matter
migrate

django.db.utils.NotSupportedError in sqlite why not supported in sqlite

class M_Post(models.Model):
''''
CODE
''''
class M_File(models.Model):
....
CODE
....
class M_Post_File(models.Model):
post = models.ForeignKey(M_Post,on_delete=models.CASCADE)
file = models.ForeignKey(M_File,on_delete=models.CASCADE,null=True)
error:
django.db.utils.NotSupportedError: Renaming the 'posts_file' table while in a transaction is not supported on SQLite because it would break referential integrity. Try adding atomic = False to the Migration class.
how to solve this error
Go to related migration file(automatically created in migrations directory after makemigrations command) and add atomic = False to the Migration class. Migration(migrations.Migration):. Then you can migrate the changes.
example code:
# Generated by Django 2.1.14 on 2019-12-02 07:07
from django.db import migrations, models
class Migration(migrations.Migration):
atomic = False # **<<< HERE**
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ebayLog',
fields=[
If you still have the problem here is a example:
# Generated by Django 2.1 on 2018-08-16 21:22
from django.db import migrations
class Migration(migrations.Migration):
atomic = False # <<<< THIS LINE
dependencies = [
('shop', '0004_product_imgfeat'),
]
operations = [
migrations.RenameModel(
old_name='Category',
new_name='CategoryShop',
),
]
I migrated many times after I got this error.
Then I did what Selim said above and I also added atomic = False after class Migration(migrations.Migration): in every migration file, which was a little silly because I didn't know which file was THE related migration file...
Then I searched the "atomic=False" in Django documentation and I found these:1
2
As the error "Renaming the 'posts_file' table while in a transaction is not supported on SQLite" described we know that renaming while in a transaction is not supported on SQLite, so adding atomic=False is needed. But I don't know about DDL transactions so that's too much for me...
If you don't want to touch anything and you've just started your project (well not necessarily), you can just delete the migration files inside the migrations directory and migrate again.
Otherwise, change the atomic variable in the migrations file to False then you can migrate your changes.
Another way, if atomic=false method didn't work, is you can delete generated files in the migration folder and start again by make migrations and migrate
Iam also getting the same error at categories_coupon model.
Go to app which contains categories_coupon model.
Rename the error raising model. (i renamed the 'coupon' model to 'coupons')
open the migrations folder.
and add 'atomic=False' line to all migrations files whose names related to post_file.
then run makemigrations command.
then run migrate command.
then runserver.
For reference look at my models
0003_coupons.py
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
atomic = False #this is the line i added.
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('categories', '0002_auto_20211008_1305'),
]
operations = [
migrations.CreateModel(
name='Coupons',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(blank=True, max_length=200, null=True)),
('code', models.CharField(blank=True, max_length=200, null=True)),
('description', models.TextField()),
('coupon_type', models.IntegerField(choices=[('%', '%'), ('RS', 'RS')])),
('discount', models.DecimalField(decimal_places=2, default=0, max_digits=20)),
('max_users', models.IntegerField(default=0)),
('used_count', models.IntegerField(default=0)),
('startd_date', models.DateField()),
('end_date', models.DateField()),
('icon', models.FileField(upload_to='uploads/banners')),
('is_active', models.IntegerField(choices=[(1, 'Active'), (2, 'Inactive'), (3, 'Deleted')])),
('created_date', models.DateTimeField(auto_now_add=True)),
('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
],
),
]
0004_rename_coupons_coupon.py
-----------------------------------
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
atomic=False #here is the line i added.
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('categories', '0003_coupons'),
]
operations = [
migrations.RenameModel(
old_name='Coupons',
new_name='Coupon',
),
]

Default value for foreign key in Django migrations.AddField

Using migrations, I need to add a new field (a foreign key) to a model. I know it can be done with:
migrations.AddField(
model_name='MyModel',
name='state',
field=models.ForeignKey(null=True, related_name='mymodel_state', to='msqa_common.MyModelState'),
),
However, I don't want my field to be nullable. Instead, I want to use a default value for it, corresponding to the id of MyModelState whose name is "available" (id value might change in different machines). This "available" value of table MyModelState is inserted into the database in a previous migration script, so it does exist.
I guess I should do something like:
migrations.AddField(
model_name='MyModel',
name='state',
field=models.ForeignKey(null=False, default=available_state_id, related_name='mymodel_state', to='msqa_common.MyModelState'),
),
My question: How can I get the available_state_id within my migration script?
You can't do it directly. The recommended way of doing this is to create a migration to add it with null=True, then add a data migration that uses either Python or SQL to update all the existing ones to point to available_state_id, then a third migration that changes it to null=False.
I just had the same issue and stumbled upon this answer, so here is how I did it:
operations = [
# We are forced to create the field as non-nullable before
# assigning each Car to a Brand
migrations.AddField(
model_name="car",
name="brand",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="model.Brand",
),
),
# assign_car_to_brand loops over all my Car objects and sets their
# "brand" field
migrations.RunPython(add_category_to_tags, do_nothing),
# Make the field non-nullable to force all future Car to have a Brand
migrations.AlterField(
model_name="car",
name="brand",
field=models.ForeignKey(
null=False,
on_delete=django.db.models.deletion.PROTECT,
to="model.Brand",
),
preserve_default=False
),
]
Here is a relatively complete example:
Step One
python manage.py makemigrations, set the temporary default value to None
Step Two
Change the genrated migration code to below style
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
def set_default_author_to_blog(apps, schema_editor):
User = apps.get_model("auth", "User")
Blog = apps.get_model("blog", "Blog")
Blog.objects.update(author=User.objects.first())
def revert_set_default_autor_to_blog(apps, schema_editor):
Blog = apps.get_model("blog", "Blog")
Blog.objects.update(author=None)
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('blog', '0001_auto_20220425_1017'),
]
operations = [
migrations.AddField(
model_name='blog',
name='author',
field=models.ForeignKey(null=True, db_constraint=False, on_delete=django.db.models.deletion.PROTECT,
to='auth.user', verbose_name='Author')
),
migrations.RunPython(set_default_author_to_blog, reverse_code=revert_set_default_autor_to_blog),
migrations.AlterField(
model_name='blog',
name='author',
field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.PROTECT,
to='auth.user', verbose_name='Author')
),
]
Step Three
python manage.py migrate

Pass South random unique default values when migrating

I am trying to forward migrate a model with existing data. The model has a new field with constraints unique=True and null=False.
When I do
./manage.py schemamigration myapp --auto
South lets me specify a default value for the new field by asking:
Specify a one-off value to use for existing columns now
Usually I set this to None but since this field needs to be unique I was wondering if it is possible to pass South a unique value via:
>>> import uuid; uuid.uuid1().hex[0:35]
This gives me an error message
! Invalid input: invalid syntax
Any ideas if it is possible to pass South random unique default values when migrating via the commandline?
Thanks.
Unfortunately only the datetime module is available for use as a one-off value in a schemamigration.
However, you can achieve the same effect by splitting this up into three migrations:
add new field to the model without constraints (with null=True, unique=False)
use a datamigration to add the UUID to the new field
add the constraint on the new field (with null=False, unique=True)
Tutorial on data migrations: http://south.readthedocs.org/en/0.7.6/tutorial/part3.html#data-migrations
In django 1.7+ you can do the following. It first adds the field with no indexing and no unique. It then assigns the unique values (I based them on the name and used slugify method which you need to create) and finally alters the field again to add index and unique attributes.
from django.db import migrations
import re
import django.contrib.postgres.fields
from common.utils import slugify
import django.core.validators
def set_slugs(apps, schema_editor):
categories = apps.get_model("myapp", "Category").objects.all()
for category in categories:
category.slug = slugify(category.name)
category.save()
class Migration(migrations.Migration):
dependencies = [
('myapp', '0034_auto_20150906_1936'),
]
operations = [
migrations.AddField(
model_name='category',
name='slug',
field=models.CharField(max_length=30, validators=[django.core.validators.MinLengthValidator(2), django.core.validators.RegexValidator(re.compile('^[0-9a-z-]+$'), 'Enter a valid slug.', 'invalid')], help_text='Required. 2 to 30 characters and can only contain a-z, 0-9, and the dash (-)', unique=False, db_index=False, null=True),
preserve_default=False,
),
migrations.RunPython(set_slugs),
migrations.AlterField(
model_name='category',
name='slug',
field=models.CharField(help_text='Required. 2 to 30 characters and can only contain a-z, 0-9, and the dash (-)', unique=True, max_length=30, db_index=True, validators=[django.core.validators.MinLengthValidator(2), django.core.validators.RegexValidator(re.compile('^[0-9a-z-]+$'), 'Enter a valid slug.', 'invalid')]),
),
]
Here is the Django's official how-to on migrating unique fields.
Migrations that add unique fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Applying a "plain" migration that adds a unique non-nullable field to a table
with existing rows will raise an error because the value used to populate
existing rows is generated only once, thus breaking the unique constraint.
Therefore, the following steps should be taken. In this example, we'll add a
non-nullable :class:`~django.db.models.UUIDField` with a default value. Modify
the respective field according to your needs.
* Add the field on your model with ``default=...`` and ``unique=True``
arguments. In the example, we use ``uuid.uuid4`` for the default.
* Run the :djadmin:`makemigrations` command.
* Edit the created migration file.
The generated migration class should look similar to this::
class Migration(migrations.Migration):
dependencies = [
('myapp', '0003_auto_20150129_1705'),
]
operations = [
migrations.AddField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(max_length=32, unique=True, default=uuid.uuid4),
),
]
You will need to make three changes:
* Add a second :class:`~django.db.migrations.operations.AddField` operation
copied from the generated one and change it to
:class:`~django.db.migrations.operations.AlterField`.
* On the first operation (``AddField``), change ``unique=True`` to
``null=True`` -- this will create the intermediary null field.
* Between the two operations, add a
:class:`~django.db.migrations.operations.RunPython` or
:class:`~django.db.migrations.operations.RunSQL` operation to generate a
unique value (UUID in the example) for each existing row.
The resulting migration should look similar to this::
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
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()
class Migration(migrations.Migration):
dependencies = [
('myapp', '0003_auto_20150129_1705'),
]
operations = [
migrations.AddField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, null=True),
),
# omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
* Now you can apply the migration as usual with the :djadmin:`migrate` command.
Note there is a race condition if you allow objects to be created while this
migration is running. Objects created after the ``AddField`` and before
``RunPython`` will have their original ``uuid``’s overwritten.
You can manually edit your migration file:
I needed to add random character to some field so I have imported random and randint
import random
import string
and changed the value of default to
default=random.choice(string.lowercase)
It worked.
There is way to do unique value for each row with South.
Define slug in models.py as:
class Foo(models.Model):
slug = models.SlugField(unique=True, default='')
....
Create new migration
run python manage.py schemamigration --auto foo
Open new migration file, and edit it:
# Change add_column to this:
db.add_column(u'account_funnel', 'slug',
self.gf('django.db.models.foo.Foo')(default='',
unique=False,
max_length=50),
keep_default=False)
# right above this add such python code:
foos = orm['foo.Foo'].objects.all()
for foo in foos:
foo.slug = slugify(funnel.name)
foo.save()
# Modify slug as unique field
db.create_unique(u'foo_foo', ['slug'])
ps mark this migration as no_dry_run = True
pss do not forget to import slugify function from django.template.defaultfilters import slugify