Django Models inheritance and avoid using multiple django.setup() calls

Viewed 280

UPDATE #1

Added Repository structure


We are using Django's ORM portion of the framework.

We have 1 Shared app, and a couple of Apps, without relationship between themselves. the Shared app defines an abstract BaseModel that all other models from all other apps inherit from, along with a few shared models, that may be referenced as a foreign key in the other apps.

We are using a monorepo, so no different repositories or anything like that.

These models can be used in various applications, from (non django) web apps, and to processing units that are deployed when needed and gets destroyed when done with their processing.

I'm trying to understand if there's a single place we can call

import django
django.setup()

and remove the need to call it in the top of every app that imports one of the models, and that will comply with both of our unittests and service run.

This is the shared models.py file

import json
import uuid
from datetime import datetime

import common.db.enums as shared_enums
from django.db import models
from django.db.models import CASCADE

MAX_VARCHAR_LENGTH = 255


def generate_uuid():
    return uuid.uuid4().hex


class UUIDString(models.UUIDField):  # pragma: no cover
    def from_db_value(self, value, expression, connection):
        if isinstance(value, uuid.UUID):
            return value.hex
        else:
            return value



class BaseModel(models.Model):  # pragma: no cover
    id = UUIDString(primary_key=True, default=generate_uuid, editable=False, unique=True, db_index=True)
    created_on = models.DateTimeField(default=datetime.now)
    updated_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True


class Customer(BaseModel):  # pragma: no cover
    customer_guid = UUIDString(max_length=MAX_VARCHAR_LENGTH, db_index=True, unique=True)


class FlowInstance(BaseModel):  # pragma: no cover
    name = models.CharField(max_length=MAX_VARCHAR_LENGTH, null=False)
    customer = models.ForeignKey(Customer, on_delete=CASCADE, db_index=True)
    flow_type = models.IntegerField(null=True, choices=shared_enums.FlowType.get_choices())
    created_by = models.CharField(max_length=MAX_VARCHAR_LENGTH, null=True)


class FlowRun(BaseModel):  # pragma: no cover
    flow_instance = models.ForeignKey(FlowInstance, on_delete=CASCADE, db_index=True)
    status = models.IntegerField(null=True, choices=shared_enums.ExecutionStatus.get_choices())
    start_time = models.DateTimeField(null=True)
    finish_time = models.DateTimeField(null=True)


class FlowRunResults(BaseModel):  # pragma: no cover
    flow_run = models.OneToOneField(FlowRun, on_delete=CASCADE, db_index=True)
    errors = models.TextField(null=True)
    output_location = models.TextField(null=True)

And this is one of the apps using the shared models.py

from common.db.shared.models import BaseModel, FlowInstance, FlowRun, UUIDString, MAX_VARCHAR_LENGTH
from django.db import models
from django.db.models.deletion import CASCADE, DO_NOTHING


class Action(BaseModel):  # pragma: no cover
    action_id = models.CharField(max_length=MAX_VARCHAR_LENGTH, null=True, db_index=True)
    action_name = models.CharField(max_length=MAX_VARCHAR_LENGTH, null=True)


class DataSetDetails(BaseModel):  # pragma: no cover
    action = models.OneToOneField(Action, on_delete=DO_NOTHING, db_index=True, null=False)
    frequency = models.IntegerField(null=True)
    identified_frequency = models.IntegerField(null=True)


class RunDataSet(BaseModel):  # pragma: no cover
    dataset_details = models.ForeignKey(DataSetDetails, null=False, on_delete=CASCADE)
    flow_run = models.ForeignKey(FlowRun, on_delete=DO_NOTHING, null=True)
    storage_path = models.TextField(null=True)


class DataCollection(BaseModel):  # pragma: no cover

    name = models.CharField(max_length=MAX_VARCHAR_LENGTH, null=False)
    target = models.OneToOneField(DataSetDetails, on_delete=DO_NOTHING, null=False, related_name="target")
    related = models.OneToOneField(DataSetDetails, on_delete=DO_NOTHING, null=True, related_name="related")
    metadata = models.OneToOneField(DataSetDetails, on_delete=DO_NOTHING, null=True, related_name="metadata")
    flow_instance = models.OneToOneField(FlowInstance, on_delete=DO_NOTHING, null=False)

Our repository structure is as follows:

├── common/ <-- Modules here should be self sustainable, but can have dependencies on other commons
    ├── aws/
    ├── db/
        └── shared
            ├── migrations/
            ├── __init__.py
            ├── manage.py
            ├── models.py
            └── apps.py        
├── theme1/ <-- contain many apps related to theme1.                           
        ├── common/
            ├── theme1db/
                ├── migrations/
                ├── __init__.py
                ├── manage.py
                ├── models.py
                └── apps.py   
        ├── app1/
        └── app2/
├── theme2/ <-- contain many apps related to theme2
    ├── common/
        └── theme2db/
            ├── migrations/
            ├── __init__.py
            ├── manage.py
            ├── models.py
            └── apps.py  
        ├── service1/
        └── service2/

The structure is that every directory is basically sort of it's own repo. each directory which isn't the root /common, can only reference modules from the root /common, or common modules from inside the theme

e.g. app1, can only reference modules from theme1/app1, /theme1/common or /common

2 Answers

One solution I can think of is to have a simple Python file such as:

import django

django.setup()

Then you can import this module from your other Python modules. As modules are only initialized once no matter how many times they're imported, django.setup() will only be called once, but will work across your application. You can read a bit more on this in the Python docs.

Another idea would be to have a Python module that runs django.setup() and acts as an entry point for the rest of the application.

For the apps that are not in Django, you may be able to write management commands and call them from those apps, but it's not completely clear from your question if that will work.

Related