could not create unique index "credits_credit_pkey" - django

Initially, I had a model of Credit. But after adding models of Hypothec and AutoCredit with similar functionality, I realized that I needed to make a base model and inherit from it.
When I tried makemigrations, I received a question:
You are trying to add a non-nullable field 'abstractbaseproduct_ptr' to `credit` without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
*I entered 1; 1. Then I got this question:
It is not clear what the 'abstractbaseproduct_ptr' field is?
Then i got
You are trying to add a non-nullable field abstractbaseaction_ptr to creditaction without a default; we can't do that (the database needs something to populate existing rows).
Again introduced 1; 1.
When I try to migrate, I get
django.db.utils.IntegrityError: could not create unique index "credits_credit_pkey"
DETAIL: Key (abstractbaseproduct_ptr_id) = (1) is duplicated.
Such questions arose only with the Credit model. Apparently, because there is already data in this table...
How should I fix this?
class AbstractBaseProduct(models.Model):
bank = models.ForeignKey('banks.Bank', verbose_name=_('bank'))
#other fields
class AbstractBaseAction(models.Model):
name = models.CharField(_('name'), max_length=255)
short_description = models.CharField(_('short description'), max_length=255)
full_description = models.TextField(_('full description'), blank=True, null=True)
class Credit(AbstractBaseProduct):
class Meta:
verbose_name = _('credit')
verbose_name_plural = _('Credits')
class CreditAction(AbstractBaseAction):
credit = models.ForeignKey(Credit, verbose_name=_('credit'))
migration
migrations.AddField(
model_name='credit',
name='abstractbaseproduct_ptr',
field=models.OneToOneField(auto_created=True, default=1, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='credits.AbstractBaseProduct'),
preserve_default=False,
),
migrations.AddField(
model_name='creditaction',
name='abstractbaseaction_ptr',
field=models.OneToOneField(auto_created=True, default=1, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='credits.AbstractBaseAction'),
preserve_default=False,
),

You have named your model AbstractBaseProduct, but you haven't added abstract = True to the model's Meta class, so Django thinks you want multi-table inheritance instead of abstract base classes.
See the docs on model inheritance for more info.

Related

Django incomplete migration of table with multiple foreign keys

Django version: 4.1.2
After heaving the following table defined in the model:
class Tasks(models.Model):
name_text = models.CharField(max_length=200)
duration_int = models.IntegerField(default=1)
...
the next two tables have been defined:
class Metiers(models.Model):
name_text = models.CharField(max_length=50)
...
class TasksProperties(models.Model):
task = models.ForeignKey(Tasks, on_delete=models.CASCADE, related_name='task_relation')
metier = models.ForeignKey(Metiers, on_delete=models.CASCADE, related_name='metier_relation')
...
doing the migration, the metier is not created inside the SQL table, but the rest:
class Migration(migrations.Migration):
dependencies = [
('simo', '0009_alter_specialdays_day_date'),
]
operations = [
migrations.CreateModel(
name='Metiers',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_text', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='TasksProperties',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('workload_mh', models.IntegerField(default=8)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='simo.tasks')),
],
),
]
Is there any reason, why metier is not taken into account?
Update on 27/11/2022:
So, it still not clear why it was not working because I believe the structure is correct; therefore I did some try-on-errors and finally the migration is complete, although the reason is not obvious. See below the resolution:
Step 1) Remove the migrated TaskProperties class from models and do a migration
Step 2) models.py was updated as
class TasksProperties(models.Model):
workload_mh = models.IntegerField(default=8)
metier = models.ForeignKey('Metiers', on_delete=models.CASCADE, related_name='metier_relation')
It resulted that the "metier" as foreignkey was visible in SQL after the migration.
Step 3) Adding the "task" as well, the migration dropped the following question:
class TasksProperties(models.Model):
workload_mh = models.IntegerField(default=8)
metier = models.ForeignKey('Metiers', on_delete=models.CASCADE, related_name='metier_relation')
task = models.ForeignKey(Tasks, on_delete=models.CASCADE, related_name='task_relation')
*It is impossible to add a non-nullable field 'task' to tasksproperties without specifying a default. This is because 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 manually define a default value in models.py.
Select an option: 2*
Then after updating the model.py adding default value default=0 to "task" the migration was successful:
class TasksProperties(models.Model):
workload_mh = models.IntegerField(default=8)
metier = models.ForeignKey('Metiers', on_delete=models.CASCADE, related_name='metier_relation')
task = models.ForeignKey(Tasks, on_delete=models.CASCADE, related_name='task_relation', default=0)
Why is this "defualt" value is needed?
Try these three commands:
python manage.py makemigrations appname
python manage.py sqlmigrate appname 0001
python manage.py migrate
And see if it solves

How can I undo this (accidental) table inheritance in Django? [duplicate]

My current project uses multi-table inheritance models:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
class Cinema(Place):
sells_tickets = models.BooleanField(default=False)
sells_popcorn = models.BooleanField(default=False)
I want to switch to abstract base classes instead. Since my model is already deployed I need to write some custom migrations to convert the above schema to this one:
from django.db import models
class AbstractPlace(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Meta:
abstract = True
class Restaurant(AbstractPlace):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
class Cinema(AbstractPlace):
sells_tickets = models.BooleanField(default=False)
sells_popcorn = models.BooleanField(default=False)
Does anyone have any advice on the steps to take to achieve this?
I recently tackled this exact problem, which I solved by writing and running the migration in the code block below - loosely translated to fit the models in your case.
I'm pretty sure that it's not possible to alter the tables of the old Restaurant and Cinema models directly, as if you try to add fields to them, they will collide with the existing fields of the base model, and if you try to "decouple" the derived models from the base model by e.g. by manually setting abstract=True in the base model's options, Django reports that it's unable to find the base models of Restaurant and Cinema. (These issues might be caused by a bug, for all I know.) To circumvent this problem, I created new tables for the derived models, copied the data from the old tables to the new ones, deleted the old tables, and renamed the new tables to match the names of the old ones.
I got large parts of the code below from code generated by Django, which can be reproduced by creating a temporary migration (before creating one with the code below) which only deletes Restaurant, Cinema and Place, running makemigrations, and copying the CreateModel()s and AlterField()s (for related fields pointing to Restaurant or Cinema) from the generated migration.
For the record, I'm using Django 3.1.4.
from django.db import migrations, models
def copy_objects_from_restaurant_and_cinema_to_restaurant_tmp_and_cinema_tmp(apps, schema_editor):
Restaurant_Tmp = apps.get_model('<app name>', 'Restaurant_Tmp')
Cinema_Tmp = apps.get_model('<app name>', 'Cinema_Tmp')
Restaurant = apps.get_model('<app name>', 'Restaurant')
Cinema = apps.get_model('<app name>', 'Cinema')
# The `_meta.fields` list includes the PK
copy_objects_from_old_model_to_new_model(Restaurant, Restaurant_Tmp, Restaurant_Tmp._meta.fields)
copy_objects_from_old_model_to_new_model(Cinema, Cinema_Tmp, Cinema_Tmp._meta.fields)
def copy_objects_from_old_model_to_new_model(old_model, new_model, fields_to_copy):
field_names = [field.name for field in fields_to_copy]
for old_obj in old_model.objects.all():
old_obj_field_dict = {
field_name: getattr(old_obj, field_name)
for field_name in field_names
}
new_model.objects.create(**old_obj_field_dict)
def copy_objects_from_restaurant_tmp_and_cinema_tmp_to_restaurant_and_cinema(apps, schema_editor):
Restaurant_Tmp = apps.get_model('<app name>', 'Restaurant_Tmp')
Cinema_Tmp = apps.get_model('<app name>', 'Cinema_Tmp')
Restaurant = apps.get_model('<app name>', 'Restaurant')
Cinema = apps.get_model('<app name>', 'Cinema')
copy_objects_from_old_model_to_new_model(Restaurant_Tmp, Restaurant, Restaurant_Tmp._meta.fields)
copy_objects_from_old_model_to_new_model(Cinema_Tmp, Cinema, Cinema_Tmp._meta.fields)
class Migration(migrations.Migration):
dependencies = [
('<app name>', '<last migration>'),
]
operations = [
migrations.CreateModel(
name='Restaurant_Tmp',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('address', models.CharField(max_length=80)),
('serves_hot_dogs', models.BooleanField(default=False)),
('serves_pizza', models.BooleanField(default=False)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Cinema_Tmp',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('address', models.CharField(max_length=80)),
('sells_tickets', models.BooleanField(default=False)),
('sells_popcorn', models.BooleanField(default=False)),
],
options={
'abstract': False,
},
),
migrations.RunPython(copy_objects_from_restaurant_and_cinema_to_restaurant_tmp_and_cinema_tmp, migrations.RunPython.noop),
# Update foreign keys to reference the non-abstract models directly,
# instead of through the (automatically generated) `place_ptr` field of the old models
<
Run `migrations.AlterField()` here for each related field (like ForeignKey) of other models that point to Restaurant or Cinema,
but change their `to` argument from e.g. `<app name>.restaurant` to `<app name>.restaurant_tmp`
>
migrations.RunPython(migrations.RunPython.noop, copy_objects_from_restaurant_tmp_and_cinema_tmp_to_restaurant_and_cinema),
migrations.DeleteModel(
name='Restaurant',
),
migrations.DeleteModel(
name='Cinema',
),
migrations.DeleteModel(
name='Place',
),
migrations.RenameModel(
old_name='Restaurant_Tmp',
new_name='Restaurant',
),
migrations.RenameModel(
old_name='Cinema_Tmp',
new_name='Cinema',
),
]
Note that the migration I originally wrote was only tested to work using SQLite; other database management systems might not accept such a large variety of migration operations, and you might have to split it into multiple migrations. (I'm somewhat unsure what exactly could cause this problem, but I can recall that I've experienced it with PostgreSQL.)
Please let me know if this solves your problem! 😊

What does this Django migration error message mean?

When I try to create migrations for the Django models shown below, I'm getting an error message I don't understand. I am trying to model a website member who can add one or more other members as either friends or followers. In addition, a member can block any other member. Here are my models:
class Member(models.Model):
FRIEND = "friend_of"
FOLLOWS = "follows"
RELATION_TYPES = ((FRIEND, "friend"), (FOLLOWS, "follower"))
user = models.OneToOneField(User, on_delete=models.CASCADE)
relations = models.ManyToManyField(
"self", choices=RELATION_TYPES, through="MemberRelation"
)
blocks = models.ManyToManyField("self", through="MemberBlock")
def __str__(self):
return self.user.first_name
class MemberRelation(models.Model):
source = models.ForeignKey(
"Member", related_name="source_member", on_delete=models.CASCADE
)
target = models.ForeignKey(
"Member", related_name="target_member", on_delete=models.CASCADE
)
relation = models.CharField(max_length=8) # Contains Member.FRIEND or .FOLLOWER
def __str__(self):
return "Member {} {} member {}".format(self.source, self.relation, self.target)
class MemberBlock(models.Model):
source = models.ForeignKey(
"Member", related_name="blocker", on_delete=models.CASCADE,
)
target = models.ForeignKey(
"Member", related_name="blocked", on_delete=models.CASCADE,
)
def __str__(self):
return "Member {} is blocking Member {}".format(self.source, self.target)
I started out with the Member and MemberRelaton classes and my migrations ran without any errors. But after I add the MemberBlock class and a blocks ManyToMany field in my Member model, I'm getting the following error when I run the makemigrations command which I don't understand:
You are trying to change the nullable field 'source' on memberrelation to non-nullable without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now...
2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration)
3) Quit, and let me add a default in models.py
Select an option:
I don't understand this error because 1) it's talking about the MemberRelation class now that I've added a MemberBlock class. It didn't have a problem with this class previously; and 2) the error is saying that the 'source' field is nullable which I don't think it is.
Initially, I declared blocks without the through option and was getting the same error. I added the through option because I thought perhaps Django was getting confused by the fact that I have two recursive ManyToMany fields in one class.
What am I doing wrong?
Here is the solution:
blocked = models.ManyToManyField("self", symmetrical=False, related_name="members")

Django Admin - How to (automatically) adding data to related table that can then be used to filter results?

I have tables that share information in a single related table via foreign keys. The relationships work as expected, however, I'm trying to figure out how to automatically populate fields that are then used to filter the results. I hope the example below illustrates what I'm trying to do.
In the Models:
class UtilType(models.Model):
name = models.CharField()
description = models.CharField()
# following boolean fields used to filter table
is_address = models.BooleanField(default=False)
is_phone = models.BooleanField(default=False)
is_email = models.BooleanField(default=False)
is_domain = models.BooleanField(default=False)
class Address(models.Model):
address_type = models.ForeignKey(
UtilType,
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name="addresses",
limit_choices_to={'is_address': True}
)
class PhoneType(models.Model):
phone_type = models.ForeignKey(
UtilType,
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name="addresses",
limit_choices_to={'is_phone': True}
)
... more models with similar schema
In the Admin:
class ContactPhoneNumberInline(admin.StackedInline):
model = PhoneNumber
min_num = 0
max_num = 5
extra = 0
exclude = ["company"]
fields = (
("phone_type", "country", "phone_number"),
)
class ContactEmailAddressInline(admin.StackedInline):
model = EmailAddress
min_num = 0
max_num = 5
extra = 0
exclude = ["company"]
fields = (
("email_type", "email_address"),
)
.... more inlines w/ similar structure
#admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
fields = (
"company",
("name_first", "name_middle", "name_last",),
("name_salutation", "name_suffix", "title"),
)
inlines = [
ContactPhoneNumberInline,
ContactEmailAddressInline,
ContactDomainInline,
ContactAddressInline
]
When editing a contact, the action is as expected. I can add information to each type and the types show filtered as directed in the ForeignKeys.
However, the admin window for UtilType has the boolean selection fields: is_address, is_phone, is_email, is_domain so the user must select this to be filtered correctly. I can hide these fields, with the exclude method.
But how do I automatically populate the right boolean (=True) based on which inline is currently being used?
Would it be best to use a save override method in the models, in the admin, or is there a better way to do this?
I haven't found a way to do this in the Django admin. If someone knows how it would be good information. I'll deal with the action in the front end once it's developed. I'm not sure it's worth the effort in the admin.

How to add through option to existing ManyToManyField with migrations and data in django

I can't find reference to particular issue in docs or online.
I have an existing many to many relation.
class Books(models.Model):
name = models.CharField(max_length=100)
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books)
This has migrations and data. Now I need to use through option in order to add one extra field in table holding many to many relation.
class Authorship(models.Model):
book = models.ForeignKey(Books)
author = models.ForeignKey(Authors)
ordering = models.PositiveIntegerField(default=1)
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books, through=Authorship)
When I run migrations, django creates fresh migration for Authorship model. I tried to create migration file manually by adding ordering column in Authorship table and altering books column in Authors table but I get some migration issues.
operations = [
migrations.AddField(
model_name='authorship',
name='ordering',
field=models.PositiveIntegerField(default=1),
),
migrations.AlterField(
model_name='authors',
name='books',
field=models.ManyToManyField(to='app_name.Books', through='app_name.Authorship'),
),
]
When trying to migrate, it gives KeyError: ('app_name', u'authorship') I bet there are other things that are affected and thus errors.
What things am I missing? Is there any other approach to work with this?
There is a way to add "through" without data migrations.
I managed to do it based on this #MatthewWilkes' answer.
So, to translate it to your data model:
Create the Authorship model only with book and author fields. Specify the table name to use the same name as the auto-generated M2M table you already have. Add the 'through' parameter.
class Authorship(models.Model):
book = models.ForeignKey(Books)
author = models.ForeignKey(Authors)
class Meta:
db_table = 'app_name_authors_books'
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books, through=Authorship)
Generate a migration, but don't run it yet.
Edit the generated migration and wrap the migration operations into a migrations. SeparateDatabaseAndState operation with all the operations inside state_operations field (with database_operations left empty). You will end up with something like this:
operations = [
migrations.SeparateDatabaseAndState(state_operations=[
migrations.CreateModel(
name='Authorship',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('book', models.ForeignKey(to='app_name.Books')),
],
options={
'db_table': 'app_name_authors_books',
},
),
migrations.AlterField(
model_name='authors',
name='books',
field=models.ManyToManyField(through='app_name.Authorship', to='app_name.Books'),
),
migrations.AddField(
model_name='authorship',
name='author',
field=models.ForeignKey( to='app_name.Author'),
),
])
]
You can now run the migration and add the extra ordering field to your M2M table.
Edit:
Apparently, column names in the DB are generated slightly differently for automatic M2M tables as for models-defined tables. (I am using Django 1.9.3.)
After the described procedure, I also had to manually change the column names of a field with a 2-word name (two_words=models.ForeignKey(...)) from twowords_id to two_words_id.
Looks like there is no way to use through option without having to do data migrations. So had to resort to data migration approach, I took some ideas from #pista329's answer and solved the issue using following steps.
Create Authorship model
class Authorship(models.Model):
book = models.ForeignKey(Books)
author = models.ForeignKey(Authors)
ordering = models.PositiveIntegerField(default=1)
Keep original ManyToManyField relation, but add another field using above defined model as through model:
class Authors(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Books)
published_books = models.ManyToManyField(
to=Books,
through=Authorship,
related_name='authors_lst' # different related name is needed.
)
IMPORTANT: You must use a different related_name to the existing ManyToManyField. If you don't do this then Django may lose the data in the original field.
Add data migration to copy all data from old table to new Authorship table.
After this, books field on Authors model can be removed as we have new field named published_books.
Migrations can be messy sometimes.
If you want to alter m2m field with through, I would suggest to rename altered field Authors.books to Authors.book. When asked by makemigrations, if you changed name from books to book? [yN], choose "N" as No. Django will delete books and create book field instead of altering.
class Authorship(models.Model):
book = models.ForeignKey("Books")
author = models.ForeignKey("Authors")
ordering = models.PositiveIntegerField(default=1)
class Authors(models.Model):
name = models.CharField(max_length=100)
book = models.ManyToManyField("Books", through="Authorship")
If you want to use books anyway, change book to books and repeat migration process with y as answer to makemigrations question about renaming.