base.py
with abstract models and models.py
with models extending the abstract ones and inheriting all the features. In the specific project you can either use the core app directly, or create a specific app which models extend from the base abstract models of the core app and additionally introduce new features.This is a quick example skipping all the unrelated parts like settings, urls, and templates:
- core_project
- apps
- player
- base.py
from django.db impport models
class PlayerBase(models.Model):
name = models.CharField(max_length=100)
class Meta:
abstract = True - models.py
from core_project.apps.player.base import PlayerBase
class Player(PlayerBase):
pass
- base.py
- player
- apps
- specific_project
- apps
- player
- models.py
from core_project.apps.player.base import PlayerBase
class Player(PlayerBase):
points = models.IntegerField()
- models.py
- player
- apps
The concept works fine until you need to use foreign keys or many-to-many relations in the abstract models. As Josh Smeaton has already noticed, you can't set foreign keys to abstract models as they have no own database tables and they know nothing about the models which will extend them.
Let's say, we have the following situation:
GameBase
and MissionBase
are abstract models and the model extending MissionBase
should receive a foreign key to the model extending GameBase
.Thanks to Pro Django book by Marty Alchin, I understood how the models get created in the background. By default, all python classes are constructed by the
type
class. But whenever you use __metaclass__
property for your classes, you can define a different constructor. Django models are classes constructed by ModelBase
class which extends the type
class.In order to solve the problem of foreign keys to the models extending the abstract classes, we can have a custom constructor extending the
ModelBase
class.base.py
# -*- coding: utf-8 -*-
from django.db import models
from django.db.models.base import ModelBase
from django.db.models.fields import FieldDoesNotExist
class GameMissionCreator(ModelBase):
"""
The model extending MissionBase should get a foreign key to the model extending GameBase
"""
GameModel = None
MissionModel = None
def __new__(cls, name, bases, attrs):
model = super(GameMissionCreator, cls).__new__(cls, name, bases, attrs)
for b in bases:
if b.__name__=="GameBase":
cls.GameModel = model
elif b.__name__=="MissionBase":
cls.MissionModel = model
if cls.GameModel and cls.MissionModel:
try:
cls.MissionModel._meta.get_field("game")
except FieldDoesNotExist:
cls.MissionModel.add_to_class(
"game",
models.ForeignKey(cls.GameModel),
)
return model
class GameBase(models.Model):
__metaclass__ = GameMissionCreator
title = models.CharField(max_length=100)
class Meta:
abstract = True
class MissionBase(models.Model):
__metaclass__ = GameMissionCreator
title = models.CharField(max_length=100)
class Meta:
abstract = True
models.py
# -*- coding: utf-8 -*-
from base import *
class Game(GameBase):
pass
class Mission(MissionBase):
pass
GameMissionCreator
is a constructor of GameBase
, MissionBase
, Game
, and Mission
classes. When it creates a class extending GameBase
, the game model is registered as a property. When it creates a class extending MissionBase
, the mission model is registered as a property. When both models are registered, a foreign key is added dynamically from one model to the other.One drawback of this constructor-class example is that if there are more than one classes extending
GameBase
or MissionBase
, then the code won't function correctly.Anyway, the example shown illustrates the possible solution and gives a direction for further development of the idea.