Using Django South to move from concrete inheritance to abstract inheritance - python

I have an existing Django project that has several models using concrete inheritance of a base class. After closer consideration, and after reading about what people like Jacob Kaplan-Moss have to say about it, using this concrete inheritance is unnecessary in my case. I would like to migrate to using an abstract base class instead.
The thing that makes this complicated is that my site is live and I have user entered data. Thus, I'll need to keep all my data intact throughout this transition.
I'll give an example to be more concrete:
Before:
app1/models.py:
class Model1(base_app.models.BaseModel):
field1 = models.CharField(max_length=1000)
field2 = models.CharField(max_length=1000)
app2/models.py:
class Model2(base_app.models.BaseModel):
field1 = models.CharField(max_length=1000)
field2 = models.CharField(max_length=1000)
base_app/models.py:
class BaseModel(models.Model):
user = models.ForeignKey(User)
another_field = models.CharField(max_length=1000)
After:
app1/models.py:
class Model1(base_app.models.BaseModel):
field1 = models.CharField(max_length=1000)
field2 = models.CharField(max_length=1000)
app2/models.py:
class Model2(base_app.models.BaseModel):
field1 = models.CharField(max_length=1000)
field2 = models.CharField(max_length=1000)
base_app/models.py:
class BaseModel(models.Model):
user = models.ForeignKey(User)
another_field = models.CharField(max_length=1000)
class Meta:
abstract = True
Right now, my plan is to first add the abstract = True to the BaseModel. Then,for each model that uses BaseModel, one at a time:
Use south to migrate the database and create this migration using the --auto flag
Use a south data migration. For instance, I would loop through each object in Model1 to fetch the object in BaseModel that has the same pk and copy the values for each field of the BaseModel object to the Model1 object.
So first, will this work? And second, is there a better way to do this?
Update:
My final solution is described in detail here:
http://www.markliu.me/2011/aug/23/migrating-a-django-postgres-db-from-concrete-inhe/

Add NewBaseModel, we use different name so it doesn't conflict with current non-abstract one (South would actually delete BaseModel otherwise).
class NewBaseModel(models.Model):
user = models.ForeignKey(User)
another_field = models.CharField(max_length=1000)
class Meta:
abstract = True
Set Model1 and Model2 to inherit from NewBaseModel
Run schemamigration --auto, 2 new fields will be added to Model1 and Model2
Run datamigration --empty and fill new fields from values in BaseModel
Load production db and double check everything migrated correctly
Remove BaseModel and rename NewBaseModel to BaseModel
Run schemamigration --auto (this should work ;) )
Deploy!
NOTE: Use orm variable when migrating to use current state of your model schema.

Sebastjan TrepĨa's answer is probably good but, another way to do it will be to create your migration manually:
Add the abstract = True to your base model.
Run schemamigration --auto, the generated migration will probably not be good but you will use it as a base.
Edit the migration file. In the forward you should add, in this order:
a. db.delete_foreign_key(table_name, column) for each of your children models. This will remove the ForeignKey between the parent and the children table.
b. db.delete_table(BaseModel) to delete the table of the base model (this command should be probably there already, generated by --auto).
c. It's possible that you will have to rename all the primary key column of your children models to 'id'. I'm not sure about this. If you need to do this: db.rename_column(table_name, column_name, 'id') for each of your children models.
Remove all auto-generated code in forward that doesn't make sense.
Run the migration: migrate
What this method is doing is removing the table of the base class and the foreign keys between the base class table and its children because they are not use with the Abstract Base Class.
I didn't test this method so it's possible that you'll hit some problems. This approach is more complicated then the other one but the advantages are that you don't need to migrate the data and that you will understand what is happening. It should run also pretty fast, a good thing for a live migration.
You can consult the South API for more info.
One really important thing, in any method you will use, work on a local copy of your system and database. When you will be really sure that the migration is working well, backup your production DB then apply your migration and then restart your webserver (to load your modified model code).

Related

Converting a model to its base model

Consider this file :
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)
Now, let's say that I have a Restaurant, named restaurant. But this place is no longer a restaurant, so I want to transform it to a Place. For that, I do :
p = Place.objects.get(pk=place_id)
p.restaurant.delete()
p.save()
It works well, p is no longer a restaurant, but something strange happens : The primary key (ID) of p in the Place table change, like if the Place was deleted and then recreated.
Why is this happening ?
And how can I transform my restaurant to a place without changing the place ID ?
You inherited restaurant from place and Django do some stuff about this kind of relation between two table in DB.Django calls that Multi-table inheritance.
Some of the features of this type of design include the following:
PK of two objects are same.
Two object in Django point of view seems as a one object so every change in PK of one object cause automatic change in other object.
There is an automatically-created OneToOneField relation between two model.
There is atomic transaction for query in these objects.
So this is normal behavior of Django ORM and Django manage change in those two object's PK. You can read more about this concept with concrete model and multi-table inheritance in this link.

Inheritance model update to its parent model

I need extend a model from another model.
Case:
core/models.py
class Master(models.Model):
code = models.CharField(max_length=30, unique=True)
name = models.CharField(max_length=100, blank=False, null=False)
class Meta:
abstract = True
class City(Master):
zipcode = models.IntegerField()
custom/models.py
from core.models import City
class City(City)
newfield = models.CharField(max_length=20)
custom is an app.
I have tried with proxy model but it is not what I need, since proxy model adds a new table. https://docs.djangoproject.com/en/2.2/topics/db/models/#proxy-models
I need is that when I migrate add the new field to City.
More info.
In core the table is created and in custom you can add new fields that the client needs. The idea is that core is only maintained as standard.
Proxy models don't add new tables. From the docs link you mentioned:
The MyPerson class operates on the same database table as its parent Person class.
If you want one table called core_city, and another called custom_city, the second one having an extra field, you simply subclass it. Perhaps it would be easier to use an alias:
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
custom_city will have all fields from core_city, plus a newfield. The description of how this works (and an example) is covered in the docs section Multi-table inheritance.
If what you want is to have one single database table, then you should use a proxy Model, however they really don't allow you to create new fields. The field should be created in the parent model, or otherwise exist in the database and not be handled by Django migrations at all.
You are looking for Abstract base classes models:
Abstract base classes are useful when you want to put some common information into a number of other models. You write your base class and put abstract=True in the Meta class.
This is the base class:
#core/models.py
class City(Master):
zipcode = models.IntegerField()
class Meta:
abstract = True # <--- here the trick
Here your model:
#custom/models.py
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
For many uses, this type of model inheritance will be exactly what you want. It provides a way to factor out common information at the Python level, while still only creating one database table per child model at the database level.
You can update or create your class constants after its defined like this
from core.models import City
City.newfield = models.CharField(max_length=20)
You may need to use swappable models, using them you can define a City class and change it with whichever model you need later,
but that way you can't import and use the base City model directly, you may need to provide a method like get_city_model for that, as your public API.
class City(Master):
zipcode = models.IntegerField()
class Meta:
swappable = 'CORE_CITY_MODEL'
and maybe replace it later with some other model, then just set CORE_CITY_MODEL to that model in the form of 'app_name.model_name'.
The django.contrib.auth is a good example of this, you may consider checking User model and get_user_model method. Although I think you may face problems if you change your city model after you did run migrate, it may not move your data to the new table, but I'm not sure about this.

Django Meta ordering in related query

How do you set a default order for Django queries that works on related managers?
You can set a default order with Meta.ordering:
class Subject(Model):
title = TextField()
class Course(Model):
subject = ForeignKey(Subject)
class Meta:
ordering = ['id']
This will set the order when you run Course.objects.all(). But when you run subject.course_set.all(), the courses can be out of order.
Ideally, a solution would involve no changes to the vast existing codebase that queries the db.
Note: The database is Postgresql
As far as i know, there is a hack
You can make a manager method like
class SomeManagerSet(models.QuerySet):
def ordered(self):
return self.order_by("id")
And activate it in model via
objects = SomeManagerSet.as_manager()
And then use in related query like subject.course_set.ordered()

Django model names are case insensitive, right?

If I have myapp/models.py
from django.db import models
class FooBar(models.Model):
x = models.BooleanField()
class Foobar(models.Model):
y = models.BooleanField()
and add myapp to INSTALLED_APPS and do a syncdb, I only get FooBar model converted to a db table. The Foobar model is ignored.
Another strange thing to note is that when we do
from myapp import models
both FooBar and Foobar are present as attributes of models. However,
>>> models.FooBar.__name__
'FooBar'
>>> models.Foobar.__name__
'FooBar'
and both are just interfaces to the db table of FooBar (by default myapp_foobar).
I am asking this question because it seems to me from this that django model names are case insensitive and yet I have not found any documentation stating this and moreover this question was answered to the effect that django model names are case sensitive.
Django model names are not case insensitive, but basically, Django creates a lowercase table name from the app and model names. Thus FooBar, which is in myapp, will generate a myapp_foobar table, and so will Foobar.
Obviously in your example, the names will overlap. Django should warn about clashing table names, but instead, silently ignores clashing models.
To fix this, I suggest you have explicit and distinct table names for your two models. Use the Meta inner class, and set Meta.db_table. See the Django documentation on models Meta.
Example given:
class FooBar(models.Model):
x = models.BooleanField()
class Meta:
db_table = 'myapp_foobar_one'
class Foobar(models.Model):
x = models.BooleanField()
class Meta:
db_table = 'myapp_foobar_two'
In a real world environment, I would never have two similar model names in the same app. It is extremely error-prone.

How to reference two ForeignKeys in one model

I want to accomplish the following:
I have three classes derived from an abstract class:
class Person(models.Model):
name = models.CharField()
...
class Meta:
abstract = True
class TypA(Person):
...
class TypB(Person):
...
class TypC(Person):
...
In another class I would like to reference TypA and TypB as a Foreign Key, something like this:
class Project(models.Model):
worker = models.ForeignKey(TypA or TypB)
Since it is not possible to declare two different models as a Foreign Key I am on the look for solutions.
I read about Generic Foreign Keys; but I am unsure how to apply that to my model.
Another idea is to use the limit_choices_to declaration for ForeignKeys.
worker = models.ForeignKey(Person, limit_choices_to={??})
But this is not possible as it seems:
Field defines a relation with model 'Person', which is either not installed, or is abstract.
Thank you in advance for the help.
A Django ForeignKey field translates to a database foreign key. Your Person model is abstract, so that one doesn't exist in the database, so there can be no foreign keys to that one.
Likewise a database foreign key can only reference one table, not two.
If you really want a flexible relation to more than one kind of table, the only possibility I see is Django's contenttypes framework.
You also want to limit the kinds of models you can point at. For that you'd best look at How can I restrict Django's GenericForeignKey to a list of models? for an example.
you just need to reference your abstract class(like JAVA):
class Project(models.Model):
worker = models.ForeignKey(Person)
#in your code:
worker = TypeA()
worker.save()
proj = Project()
proj.worker = worker
proj.save()

Categories

Resources