Cannot create an instance of a model with GenericForeignKey in migration - django

IMPORTANT: This question is no longer relevant.
In a Django 1.7 migration I try to create Comment entries programatically with the following code:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
def create_genericcomment_from_bookingcomment(apps, schema_editor):
BookingComment = apps.get_model('booking', 'BookingComment')
Comment = apps.get_model('django_comments', 'Comment')
for comment in BookingComment.objects.all():
new = Comment(content_object=comment.booking)
new.save()
dependencies = [
('comments', '0001_initial'),
('django_comments', '__first__'),
]
operations = [
migrations.RunPython(create_genericcomment_from_bookingcomment),
]
And it produces an error:
TypeError: 'content_object' is an invalid keyword argument for this function
However, the same code (i.e. Comment(content_object=comment.booking)) works when executed in the shell.
I tried to create a blank model with new = Comment() and then set all the necessary fields manually but even though I set content_type and object_pk fields accordingly, they content_type was not actually saved and I received django.db.utils.IntegrityError: null value in column "content_type_id" violates not-null constraint
Any idea how to properly create a model with a generic foreign key in a migration? Or any workaround?

This is an issue of migrations' model loader. You load your models using default
Comment = apps.get_model('django_comments', 'Comment')
It loads the Comment model in some special way, so some features like generic relations don't work.
There is a bit hacky solution: load your models as usual:
from django_comments import Comment

Related

Conditional Django migration based on a field only present in new version

My app that currently depends on Postgres and Django's Postgres-only JSONField. The field works well and I've no interest in another project, but I have prospective-users who want to use my app, but can't while it relies on Postgres.
Django 3.1 has a cross-platform version of this field —which will work for my needs— but I don't want to force everybody up to Django 3.1; I would like to offer people a choice between Postgres or Django 3.1+. On paper, this is simple enough with a conditional import...
try:
from django.db.models import JSONField
except ImportError:
from django.contrib.postgres.fields import JSONField
And if I installed Django 3.1 and generated a migration, it could take me from django.contrib.postgres.fields.JSONField to django.db.models.JSONField. But...
New users will still execute the initial migration. I will still have a dependency on Postgres.
Sub-Django 3.1 users won't be able to execute the new migration. I now have a dependency on Django 3.1.
This is worse than when I started. How do I do this sort of field migration in a way that will work for everybody?
Migrations are just code. Just because they're auto-generated doesn't mean you shouldn't change them. You're encouraged to, at least to check they're generated correctly, but also there's no harm in writing them yourself.
This works for me:
Model:
from django.db import models
try:
from django.db.models import JSONField
except ImportError:
from django.contrib.postgres.fields import JSONField
class MyModel(models.Model):
stuff = JSONField()
Migration:
from django.db import migrations, models
try:
from django.db.models import JSONField
except ImportError:
from django.contrib.postgres.fields import JSONField
class Migration(migrations.Migration):
dependencies = [('testapp', '0001_initial')]
operations = [
migrations.CreateModel(
name='MyModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stuff', JSONField()),
],
),
]
Keep in mind that if you need to change this field in the future, you will need to go through this process again.
I have got this from Django source code
from django.db.models import JSONField as BuiltinJSONField
class JSONField(BuiltinJSONField):
system_check_deprecated_details = {
'msg': (
'django.contrib.postgres.fields.JSONField is deprecated. Support '
'for it (except in historical migrations) will be removed in '
'Django 4.0.'
),
'hint': 'Use django.db.models.JSONField instead.',
'id': 'fields.W904',
}
This indicates that, django.contrib.postgres.fields.JSONField is going to be deprecated. Also, Django uses the django.db.models.JSONField as postgres special JSONField.
Apart from that, I have generated the SQL command using sqlmigrate command and it was like,
BEGIN;
--
-- Create model MyModel
--
CREATE TABLE "myapp_mymodel" ("id" serial NOT NULL PRIMARY KEY, "stuff" jsonb NOT NULL);
COMMIT;
Surprisingly, I have got same SQL command using Django==3.0 and Django==3.1 and in the database, the field is a jsonb type
These pieces of information conclude that you don't have to worry about this new JSONField upgrade.
What changes need to be done?
You don't need to generate a new migration file, but edit existing migration files which have django.contrib.postgres.fields.JSONField reference with the try...except block.
That's it!!!
All new migrations will be correct automatically if this solution with a deconstruct() method will be used.
You can create a compatible custom JSONField that encapsulates both variants.
Create a small file fields.py in your application:
try:
from django.db.models import JSONField as OrigJSONField
except ImportError:
from django.contrib.postgres.fields import JSONField as OrigJSONField
class JSONField(OrigJSONField):
def deconstruct(self):
# the original path was 'django.db.models.JSONField' or 'django.contrib.postgres.fields....'
name, path, args, kwargs = super().deconstruct()
# Substitute 'my_app' by your application name everywhere.
path = 'my_app.fields.JSONField'
return name, path, args, kwargs
Change a line in your models.py:
from my_app.fields import JSONField
Edit all existing migrations that use a JSONField:
import my_app.fields
...
('stuff', my_app.fields.JSONField()),
  (or equivalently)
from my_app.fields import JSONField
...
('stuff', JSONField()).
No migration is created after that because no difference is found by makemigrations.
All future makemigrations after a changed model will be created automatically with my_app.fields.JSONField(). Compatibility is a benefit of this solution.
Reflection about Django Release Notes 3.1 and future:
They describe a plain upgrade that requires to create a new migration that only upgrades the import path, but it generates no SQL e.g. by sqlmigrate. It is easier than to edit old migrations.
Maybe you you also upgrade in two years to Django >= 3.1 only and you will have two alternatives:
A) Only the import in models.py will be edited and you create a new nearly empty formal migration similarly to release notes. You will be not able to remove the import path my_app.fields.JSONField because it must be importable from old migrations similarly that Django can never remove the path django.contrib.postgres.fields.JSONField. The file my_app/fields.py could be simplified to one line from django.db.models import JSONField as JSONField after an unconditional upgrade of requirements.
B) Editing old migrations again to only the new JSONField is possible, but unreasonable.
(It is every user's responsibility that an edit in a migrations file doesn't require a changed database state and all migrations remain consistent each other. That is why not much about it can be found, except such clear cases.)
I gave point to Tom Carrick for his answer but I think it should also include the usage of Meta.required_db_vendor = 'postgres' on Django < 3.1
# models.py
from django.db import models
try:
from django.db.models import JSONField
postgres_only = False
except ImportError:
from django.contrib.postgres.fields import JSONField
postgres_only = True
class MyModel(models.Model):
stuff = JSONField()
class Meta:
if postgres_only:
required_db_vendor = 'postgres'
# migrations/0001_initial.py
from django.db import migrations, models
try:
from django.db.models import JSONField
postgres_only = False
except ImportError:
from django.contrib.postgres.fields import JSONField
postgres_only = True
class Migration(migrations.Migration):
dependencies = [('testapp', '0001_initial')]
operations = [
migrations.CreateModel(
name='MyModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stuff', JSONField()),
],
options={'required_db_vendor': 'postgres'} if postgres_only else None,
),
]
This will ensure that users of your reusable app cannot use it on a different database than PostgreSQL on Django < 3.1 and seamlessly enable support on 3.1 without requiring a migration on PostgreSQL users using your library and updating to Django 3.1.

Copy a database column into another in Django

I am writing a migration that requires me to fill a field with existing data from another field (with same type and constraints). Is it possible in Django to copy the data in a single operation? The destination column already exists when I need to copy the data.
In SQL, I would have written something like that:
UPDATE my_table SET column_b = column_a;
Edit
The current answer proposes to loop over the model instances, but that is what I want to avoid. Can it be done without a loop?
As the comment mentioned, you can simply write a migration for this. I think the below should work though I haven't tested it. It uses the queryset update API and F to avoid looping
from __future__ import unicode_literals
from django.apps import apps
from django.db import migrations, models
from django.db.models import F
def copy_field(apps, schema):
MyModel = apps.get_model('<your app>', 'MyModel')
MyModel.objects.all().update(column_a=F('column_b'))
class Migration(migrations.Migration):
dependencies = [
('<your app>', '<previous migration>'),
]
operations = [
migrations.RunPython(code=copy_field),
]

How to define default data for Django Models?

I want my application to have default data such as user types.
What's the most efficient way to manage default data after migrations?
It needs to handle situations such as, after I add a new table, it adds the default data for it.
You need to create an empty migration file and Do your stuff in operations block, as explained in docs.
Data Migrations
As well as changing the database schema, you can also use migrations to change the data in the database itself, in conjunction with the schema if you want.
Now, all you need to do is create a new function and have RunPython use it
Docs explains this with an example to show ,how to communicate with your models.
From Docs
To create an empty migration file,
python manage.py makemigrations --empty yourappname
And this is the example how to update a newly added field.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = "%s %s" % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
initial = True
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
The accepted answer is fine. But, since OP asked the question in the context of adding new rows and not updating existing entries. Here is the code snippet for adding new entries :
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('loginmodule', '0002_login_avatar'),
]
def insertData(apps, schema_editor):
Login = apps.get_model('loginmodule', 'Login')
user = Login(name = "admin", login_id = "admin", password = "password", email = "admin#pychat.com", type = "Admin", avatar="admin.jpg")
user.save()
operations = [
migrations.RunPython(insertData),
]
Update:
most users are looking for data migration as suggested by #durdenk in https://stackoverflow.com/a/39742847/3627387. But what OP was asking is about a way to add data after migrations, that is why this is accepted answer.
Original answer:
I think what you are looking for is fixtures https://docs.djangoproject.com/en/1.10/howto/initial-data/
From docs
It’s sometimes useful to pre-populate your database with hard-coded data when you’re first setting up an app. You can provide initial data via fixtures.
Also read this https://code.djangoproject.com/wiki/Fixtures
Answer is given above just to show how to insert new rows to the table.
from django.db import migrations, models
from yourapp.models import <yourmodel>
def combine_names(apps, schema_editor):
obj = <yourmodel>(arrib=value)
obj.save()
For example let's say you have model Person
person = Person(first_name='raj', last_name='shah')
person.save()

how to add db_index=True to email field of django auth_user

suddenly, my page got so many users in db that the a filter for email over the auth_user table almost failing because of the extremely big number of users.
Since the table comes built-in, I need to add db_index=True to columns in this table, any idea how to do this?
One quick and easy way would be to manually add the index using RunSQL in a migration.
operations = [
migrations.RunSQL("CREATE INDEX..."),
]
It's not very elegant. For one thing, the migration will be for a different app (since you don't control the auth migrations). For another, the schema will technically be out of sync with the database. However, I don't think there are any negative consequences in this case, since Django doesn't do anything with db_index other than create the index.
One possibility is to substitute the user model with a custom one, which will have proper indices and any other field that you require. There is extensive documentation on Django docs: Substituting a custom User model on how to achieve this. This is how I did it on a particular case with a similar issue.
Another possibility is to extend the user model, which could have a particular field repeated from the original model, on which there is an index. Disclaimer: I am genuinely against that for obvious reasons, but I have seen this happening, as this approach is easier to code than the first. This would be very bad though if there are many fields.
This is a good question imo. I would love to know if there is another possibility which I miss.
I had the same problem, but with an additional twist--- I already had an index created by South in my table. So if I added a RunSQL("Create index") to my migration, it would add a second index to my database. But at the same time, if I don't include some create index action in the migrations, then I won't be able to properly spin up new databases.
Here's my solution, some python code to check for the existence of an index using some of the private-ish methods in schema_editor
project/appname/migrations/0002.py:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db.migrations import RunPython
def forward__auth_user__email__index(apps, schema_editor):
auth_user = apps.get_model("auth", "User")
target_column_to_index = 'email'
se = schema_editor
# if there aren't any indexes already, create one.
index_names = se._constraint_names(auth_user, [target_column_to_index], index=True)
if len(index_names) == 0:
se.execute(se._create_index_sql(auth_user, [auth_user._meta.get_field('email')]))
def reverse__auth_user__email__index(apps, schema_editor):
auth_user = apps.get_model("auth", "User")
target_column_to_index = 'email'
se = schema_editor
# delete any indexes for this table / column.
index_names = se._constraint_names(model, [target_column_to_index], index=True)
for index_name in index_names:
se.execute(se._delete_constraint_sql(se.sql_delete_index, auth_user, index_name))
class Migration(migrations.Migration):
dependencies = [
('frontend', '0001_initial'),
]
operations = [
RunPython(forward__auth_user__email__index, reverse_code=reverse__auth_user__email__index)
]

Cannot create form field for 'created_by' yet, because its related model 'users.User' has not been loaded yet

I recently installed Blogango, where I had the following error:
CommandError: One or more models did not validate:
blogango.blogentry: 'created_by' defines a relation with the model 'auth.User', which has been swapped out. Update the relation to point at settings.AUTH_USER_MODEL.
So I added settings.AUTH_USER_MODEL and now I get the following message:
ValueError: Cannot create form field for 'created_by' yet, because its related model 'users.User' has not been loaded yet
I went through my settings.py where it calls AUTH_USER_MODEL = 'users.User', and moved it higher up on the settings.py to try and get it load sooner.
As requested:
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, unique=False)
What can I do to fix this?
It seems Blogango (is it https://github.com/agiliq/django-blogango?) does not support the custom user models introduced in Django 1.5.
The patch in Blogango should be pretty simple, just replace:
from django.contrib.auth.models import User
with:
from django.contrib.auth import get_user_model
User = get_user_model()
in django-blogango/blogango/models.py.