Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert django model to abstract model if it already has related classes

Lets say i have the following base model:

class human(models.Model):
   gender = models.BooleanField()
   age = models.IntegerField()
   name = models.CharField(max_length=200)

And two models inheriting it:

class superhero(human):
   can_fly = models.BooleanField()

class villain(human):
   fingerprint = models.ImageField()

At some time in my development process i realized that i don't actually need the human class directly. I only need it to be a set of template parameters for superhero and villain models. If now i go to human Meta class and set abstract=True and change my models like so:

class human(models.Model):
   gender = models.BooleanField()
   age = models.IntegerField()
   name = models.CharField(max_length=200)

   class Meta:
       abstract = True

class superhero(human):
   can_fly = models.BooleanField()

class villain(human):
   fingerprint = models.ImageField()

attempt to make migrations and migrate will raise the following error

Local field u'gender' in class 'superhero' clashes with field of similar name from base class 'human'

How can i switch to abstract class keeping all my migrations without tinkering the database directly?

like image 565
George Avatar asked Nov 15 '15 16:11

George


People also ask

What class must the class inherit to become a model in Django?

Model inheritance. Model inheritance in Django works almost identically to the way normal class inheritance works in Python, but the basics at the beginning of the page should still be followed. That means the base class should subclass django.

How do you create an abstract class for a model?

To create an abstract class, just use the abstract keyword before the class keyword, in the class declaration. You can observe that except abstract methods the Employee class is same as normal class in Java. The class is now abstract, but it still has three fields, seven methods, and one constructor.

What is abstract base class in Django?

An abstract model is a base class in which you define fields you want to include in all child models. Django doesn't create any database table for abstract models. A database table is created for each child model, including the fields inherited from the abstract class and the ones defined in the child model.


Video Answer


2 Answers

So after reading the docs again I found a solution:

The error was raised because of the way Django saves models to the database. All models that inherit from the base model human don't have all human fields in their own tables. Instead they have only their own fields and a foreign key that links them to the corresponding lines in human table. But when you inherit from abstract class all the fields are saved directly to your model's table. So when I tried to change human class to abstract=True and inherit it in superhero class Django tried to create all fields from human table in superhero table, which still has a foreign key to existing human entry with fields named exactly the same.

warning

Following this instruction will make the desired result but unfortunately will destroy all entries of human superhero and villain models

  1. Comment superhero and villain models so Django deletes them
  2. Make migrations and migrate so the superhero and villain tables are deleted
  3. Set abstract=True in human class
  4. Make migrations and migrate again. This will delete human table because now it is an abstract class
  5. Uncomment superhero and villain models
  6. Make migrations and migrate. This will create villain and superhero tables with all the fields from human class

This is it.

P.S. Why I needed to move to abstract class? Because I wanted to make all my villains and superheroes unique using unique_together parameter that makes some DB level restrictions. To make this possible all superhero fields had to be in one table. Now it works.

like image 121
George Avatar answered Oct 27 '22 20:10

George


This is an old question, but the answer saved me a lot of efforts. I just want to add something.

When making a model class to inherite from an abstract model, Django migrations will remove that model.

MyModel(models.Model):
    # some fields to be inherited later from the abstract model
    author = models.ForeignKey('auth.User')
    # other fields specific to this model

Now if you create an abstract model:

MyAbstractModel(models.Model):
    # fields to be used by children classes

    class Meta:
        abstract = True

and let your model inherit from it:

MyModel(MyAbstractModel):
    author = models.ForeignKey('auth.User')
    # other fields specific to this model

If you run makemigrations and migrate on your app Django will remove that model and delete the correspondent DB table.

You can outsmart Django by commenting out the code for that model. Django will remove it with all other consequences. Now you can uncomment the code and run the migrations again and Django will create the DB table again.

However you will likely have your admin, views, forms, etc., to import your model. This means the migrations will throw error, because that class can't be found. You'll have to comment out the code in all files where your model is imported.

Easier would be to skip the commenting out and write the wanted migration manually:

from __future__ import unicode_literals

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        # app_label should be your app
        # and 000x_the_last_migration is the name of the migration
        # it depends on, usually the last one
        ('app_label', '000x_the_last_migration'),
    ]

    operations = [
        # do this for all ForeignKey, ManyToManyField, OneToOneField
        # where model_name is obviously the model name and name is the
        # field name
        migrations.RemoveField(
            model_name='mymodel',
            name='author',
        ),
        # finally delete the model
        migrations.DeleteModel(
            name='MyModel',
        ),
    ]

Now you can run the migration:

python manage.py migrate app_label

later inherit from the abstract model (see code above, 3rd block) and make new migration:

python manage.py makemigrations app_label

This should save you commenting out large chunks of code. If you want to save the data in the DB table, then you can use dumpdata and loaddata.

TLDR

This wasn't a step for step guide and requires experience with Django. In short you have to:

  1. Create abstract model
  2. Write the migration manually for the model that has to inherit from the abstract model
  3. Run the migration (this deletes the DB table with all entries!)
  4. Inherit from the abstract model
  5. Run makemigrations and migrate
like image 29
cezar Avatar answered Oct 27 '22 19:10

cezar