Migrations can't run due to evaluation of QuerySet in Serializer? - django

Trying to solve this chicken-and-egg issue. I've defined a new model which an existing model foreign keys into, and made the new migrations, including a data migration that provisions some defaults for the new model.
In the serializer for the existing model, I wanted to define a default like so:
new_thing = serializers.PrimaryKeyRelatedField(
queryset=NewThing.objects.all(),
default=NewThing.objects.get(id=DEFAULT_NEW_THING_ID),
)
I don't understand django's specific mechanism, but this seems to cause problems while running the migrations (although the makemigrations seems fine). It seems the migration runs a series of app checks on the views and eventually the serializer, where it evaluates the NewThing QuerySet and returns an error that the relation for NewThing does not exist (since the migration hasn't been run yet).

You should never run actual queries in class-level definitions; this is generally true in both Django generally as well as DRF. get is a query and will hit the database at definition time, although the queryset argument will not and is OK.
If you want to set a default that is based on an actual object, you should do it at create time by defining the create() method, in which you would check that the value is not supplied.

Related

Django won't migrate because one model used to be a subclass

I'm using Django 3.0.4 with MySQL 5.7 on Ubuntu 18.04.
I have a model Apples. I created a second model BadApples as a subclass of it:
# models.py
class Apples(models.Model):
# fields
class BadApples(Apples):
pass
I ran the migration, which completed successfully.
Then, I decided that the 'subclass' approach wasn't working and that BadApples should be its own model. I rewrote the models like this:
# models.py
class Apples(models.Model):
# fields
class BadApples(models.Model):
# fields
When I tried to run the migration, I ran into the following error:
MySQLdb._exceptions.OperationalError: (1090, "You can't delete all columns with ALTER TABLE; use DROP TABLE instead")
As best I can tell, migrating BadApples from one form to the other involves changing all of its columns. Instead of dropping the table and recreating it, Django uses ALTER TABLE commands only, which throws the MySQL error when it attempts to remove the last column of the original BadApples. This seems related to this bug, purportedly fixed over two years ago, but evidently not fully.
To work around this bug, my idea was to remove BadApples from models.py and all references to BadApples from the rest of the code (in this case, views.py and admin.py). Then I'd run a migration, which would drop the BadApples table entirely because it no longer exists, and then I could recreate it as a separate migration.
Instead, after confirming that there is no trace of the BadApples model anywhere in my code, running makemigrations throws this error:
django.core.exceptions.FieldError: Local field 'id' in class 'BadApples' clashes with field of the same name from base class 'Apples'.
When I originally created BadApples as a child of Apples, its table had no id field because its data was stored in the Apples table. After I changed it to be an independent model, its table now gets assigned an id field. Django retains some memory of BadApples being a child of Apples, however, and it sees this as a conflict.
To try to fix this, I reverted to the migration immediately before I ever created BadApples in the first place. This migration completed successfully:
$ python3 manage.py migrate AppName 0012_auto_some_number
Operations to perform:
Target specific migration: 0012_auto_some_number, from AppName
Running migrations:
Rendering model states... DONE
Unapplying AppName.0013_badapples... OK
At this point, there is NO reference to BadApples anywhere in my code, and the models are at a point where BadApples has never even existed. Yet, when I try to run a migration (with no further changes to models.py), I still get the error
django.core.exceptions.FieldError: Local field 'id' in class 'BadApples' clashes with field of the same name from base class 'Apples'.
Django is fixated on the idea that BadApples used to exist and be a subclass of Apples. How do I un-fixate it?
This type of operation you're trying to perform is sufficiently complicated that Django Migrate can't infer what you're trying to do. In situations like these you should write a custom migration to handle how it can migrate the database forward. In this migration you can tell it to properly drop the table and create a new one.
In the Migration Files topic there is an example that shows the migrations.DeleteModel() method.
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [('migrations', '0001_initial')]
operations = [
migrations.DeleteModel('Tribble'),
migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
]

Django migrations refuse to acknowledge a model no longer inherits from old parent

I'm trying to remove a concrete parent model and copy some of its fields directly onto the child. I have performed a similar process several times before using South (the same parent model on other children), but since upgrading to Django 1.7, it's just not playing the game. Django gives the following error during the migrate process:
FieldError: Local field <field_name> in class <child_model> clashes with field of similar name from base class <parent_model>
At the point that both makemigrations and migrate are run and this error occurs, the parent model no longer appears in the code as the parent of the class, yet the migration still complains.
A similar question has been asked here before and the accepted solution involves creating a new model, copying data to it, replacing the existing model, and copying the data back. Unfortunately, this approach will not work in my case. It is an established model with numerous foreign keys pointing back to it, which would be lost if the original records were removed.
The (simplified) approach I used under South was:
Remove the model as the parent and add the new fields to the child
Add an explicit primary key field with the same name as the old auto *_ptr_id field
Run schemamigration and edit the file to:
Remove South's attempt to remove and recreate the *_ptr field
Add some custom logic to remove the ForeignKey-ness of the *_ptr_id field and add PrimaryKey-ness
Use a datamigration to grab the values off the old parent and add them to the new fields on the child
This is the approach I was hoping would be reasonably easily adapted to native Django migrations, and I got (with some tweaking) the *_ptr_id field replacement logic going. But Django won't let me add the new fields. I have tried the following:
Removing the parent model and performing the custom *_ptr_id replacement, migrating, and adding the fields as a second migration.
Removing the parent model and not editing the file to manually mess with *_ptr_id, but leaving Django's remove/add operations and migrating with --fake.
In both scenarios, a later migration that adds new fields with the same name as those on the old parent causes the above error. Django refuses to acknowledge that the old parent model is no longer a parent of the class.
Has anyone else had this problem and been able to work around it?
Edit:
This appears to be caused by the bases argument of the CreateModel schema operation class listing the parent model, and the lack of a matching operation to update the list. The documentation for CreateModel lists this argument as optional, but are there any side-effects to manually changing this value in a previously applied migration?
After hitting this same problem in Django 1.11, I followed the edited advice and found the initial 'CreateModel' call in my migrations files. Commenting out the bases=('{appname}.{modelname}',), line and explicitly setting my model's primary key to be 'id' then re-running makemigrations and migrate fixed the issue for me.
I'm not sure if the primary key change is relevant to the solution, I'm just laying out variables.
Another option for people less invested in their model name (and data): rename it when you remove the parent. Note that while this worked for me, switching back to the original model name in another migration hit this same "once a child, always a child" error, so you'll still need to edit the 'bases=' line in your migrations.
Thanks oogles and Community!

What's the Django 1.7+ equivalent to South's add_ignored_fields()?

Back in previous versions of Django, we all used South to do migrations, and because it wasn't as smart as we might have liked, we sometimes needed to tell it to explicitly ignore some fields because such fields were too complicated for it to handle. We did this with add_ignored_fields and everything pretty much worked.
In our case, we have a "field" on our model that's a subclass of CharField that actually attaches two additional fields to the model via the contribute_to_class method. It's kind of a weird process, but it makes sense for our use case and worked just fine until we upgraded to Django 1.7.
However, with the new migrations system, our use of add_ignored_fields() no longer works and our unit tests are exploding as it tries to create a test database table with these fields. Specifically it says:
django.db.utils.OperationalError: duplicate column name: our_column_name
In this case our_column_name is one of the fields this special CharField subclass creates.
In a perfect world, we could have the migration code handle these fields properly, but I'd settle for the 1.7 version of add_ignored_fields() if it exists.
I should also point out that we've found the explanation for deconstruct in the Django documentation, but I'm afraid that it doesn't make any sense to me. I can't figure out if it applies to this situation or not.
We've discovered this Django ticket that pretty much states that this is a design pattern that the devs aren't supporting, so we're just going to have to rewrite the model to explicitly create the other fields on the model and then reference said fields from the "parent" field a la ImageField.

How to replace a foreignkey with another using a migration

I'd like to replace an existing ForeignKey pointing at my User model with one pointing at a profile model.
The change in the model is:
created_by=models.ForeignKey(settings.AUTH_USER_MODULE)
To:
created_by=models.ForeignKey(settings.PROFILE_MODEL)
The auto-generated migration looks like (with constants subbed in):
migrations.AlterField(
model_name=MODEL,
name='created_by',
field=models.ForeignKey(to=settings.PROFILE_MODEL),
preserve_default=True,
),
I also have ManyToManyFields to deal with as well. What I have in my head is I'd like a function to run on each MODEL object to resolve the user object to the profile object. How would I go about doing this?
The relationship between user and profile is (and vice versa):
User.profile = Profile
Edit: Forgot to mention, if the auto-generated migration is run you get the following error:
ValueError: Lookup failed for model referenced by field
APP1.MODEL.created_by: APP2.PROFILE_MODEL
As I understand now you want to migrate only our app without expecting anything to be changed to the global auth User model. Then it's easy. Migrations work nice with symbolic settings names.
I tried it with Django 1.7. It is possible to switch between settings.AUTH_USER_MODEL and settings.PROFILE_MODEL back and forth without any problem. A migration can be created and applied after every change. The tested model had also a ManyToManyField and mutual relationships between User and Profile.
I see you have APP1 and APP2. Maybe you make migrations for both and they are circular dependent so that a part of other application migration should be applied before the current one migration can be completely applied and vice versa. It can be simplified by spliting a change to more smaller and making automatic migrations after every change so that they are less dependent. A OneToOneField is better than two mutual foreign keys and its reverse relation is even so useful. A foreign key can be changed temporarily to IntegerField(null=True) in the worst case in order to simplify data migration. It is really viable more or less nice.
The question looked nice initially, but the problem should be better specified to be reproducible.
Edited by removing the original text after reading information in comments:

Django syncdb ignore a specific model

Is there a way to make syncdb ignore a specific table?
I decided to declare a M2M twice because from one model I wanted it to appear as a simple M2M that works with filter_horizontal. In another I added a through field and show it in an inline. I used db_table to make the simple one use the same through table. This all works well usually, BUT in syncdb I always get an error the first run because it thinks it must create the table twice, but it already exists the second time. Not a problem until I get to testing which creates the test database and fails. Maybe I'm just an idiot for using this hack. Is there anyway to make Django ignore this error or specify not to create the m2m through table the second time?
I ended up using the Model Meta option managed = False to make syncdb ignore the model. Then used initial sql data to create the sql for the table by hand. If I could tell django that only the manytomany field was not to be managed I wouldn't need to write custom SQL, but this can't be done.