Henry George
Henry George

Reputation: 514

Avoiding circular imports in Django Models (Config class)

I've created a Configuration model in django so that the site admin can change some settings on the fly, however some of the models are reliant on these configurations. I'm using Django 2.0.2 and Python 3.6.4.

I created a config.py file in the same directory as models.py.

Let me paracode (paraphase the code? Real Enum has many more options):

# models.py
from .config import *

class Configuration(models.Model):
    starting_money = models.IntegerField(default=1000)

class Person(models.Model):
    funds = models.IntegarField(default=getConfig(ConfigData.STARTING_MONEY))

# config.py
from .models import Configuration

class ConfigData(Enum):
    STARTING_MONEY = 1
def getConfig(data):
    if not isinstance(data, ConfigData):
        raise TypeError(f"{data} is not a valid configuration type")
    try:
        config = Configuration.objects.get_or_create()
    except Configuration.MultipleObjectsReturned:
        # Cleans database in case multiple configurations exist.
        Configuration.objects.exclude(Configuration.objects.first()).delete()
        return getConfig(data)
    if data is ConfigData.MAXIMUM_STAKE:
        return config.max_stake

How can I do this without an import error? I've tried absolute imports

Upvotes: 2

Views: 437

Answers (2)

RishiG
RishiG

Reputation: 2830

Willem Van Onsem's solution is a good one. I have a different approach which I have used for circular model dependencies using django's Applications registry. I post it here as an alternate solution, in part because I'd like feedback from more experienced python coders as to whether or not there are problems with this approach.

In a utility module, define the following method:

from django.apps import apps as django_apps

def model_by_name(app_name, model_name):
  return django_apps.get_app_config(app_name).get_model(model_name)

Then in your getConfig, omit the import and replace the line

config = Configuration.objects.get_or_create()

with the following:

config_class = model_by_name(APP_NAME, 'Configuration')
config = config_class.objects.get_or_create()

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476594

You can postpone loading the models.py by loading it in the getConfig(data) function, as a result we no longer need models.py at the time we load config.py:

# config.py (no import in the head)
class ConfigData(Enum):
    STARTING_MONEY = 1

def getConfig(data):
    from .models import Configuration
    if not isinstance(data, ConfigData):
        raise TypeError(f"{data} is not a valid configuration type")
    try:
        config = Configuration.objects.get_or_create()
    except Configuration.MultipleObjectsReturned:
        # Cleans database in case multiple configurations exist.
        Configuration.objects.exclude(Configuration.objects.first()).delete()
        return getConfig(data)
    if data is ConfigData.MAXIMUM_STAKE:
        return config.max_stake

We thus do not load models.py in the config.py. We only check if it is loaded (and load it if not) when we actually execute the getConfig function, which is later in the process.

Upvotes: 3

Related Questions