Django single file project. Again

Not yet broken

This article is a follow up on the one I've written few years ago. Special shoutout and thanks and hugs and kudos to Javier Buzzi for taking the time to improve the original script.

TL;DR: download old version & download new version


VERY IMPORTANT DISCLAIMER

The content associated with mlvin.xyz may be very offensive to some people. I've lost the ownership of mlvin.xyz a very long time ago, sad situation. I'm not anymore linked to this domain name, nor to the content hosted on this website. By the way F*** OVH.

What are those improvements?

Application label?

Our models are not defined as in a classical Django application. So, each model should declare which app it belongs to. You can read more about app_label Meta option here.

The problem with the first script was duplication:

class Author(models.Model):
    name = models.CharField(max_length=200)
    class Meta:
        app_label = APP_LABEL

class Book(models.Model):
    author = models.ForeignKey(Author, related_name='books')
    title = models.CharField(max_length=400)
    class Meta:
        app_label = APP_LABEL

We want to define app_label once and for all our models:

# The current name of the file, which will be the name of our app
APP_LABEL, _ = os.path.splitext(os.path.basename(os.path.abspath(__file__)))

Then, we need to overwrite the Django function in charge of:

  • looking for an application configuration
  • attach the model to the found/correct application configuration.

This function is named get_containing_app_config

def get_containing_app_config(module):
    if module == '__main__':
        # if inside single file project,
        # retrieve the application configuration using APP_LABEL variable
        return apps.get_app_config(APP_LABEL)

    # otherwise, do as you always do
    return apps._get_containing_app_config(module)

# save the original function
apps._get_containing_app_config = apps.get_containing_app_config

# ask Django to use our custom function
apps.get_containing_app_config = get_containing_app_config

Migrations folder?

There is a Django settings named MIGRATION_MODULE. According to the documentation:

A dictionary specifying the package where migration modules can be found on a per-app basis. The default value of this setting is an empty dictionary, but the default package name for migration modules is ``migrations``.


In the first version of the script, all the migrations were put in a folder named migrations. With the following configuration, we can:

  • name the migration folder how we want.
  • make sure that we have one migration folder per application.

# Where migrations folder need to be created
APP_MIGRATION_MODULE = '%s_migrations' % APP_LABEL
APP_MIGRATION_PATH = os.path.join(BASE_DIR, APP_MIGRATION_MODULE)

# Create the migration folder
# and a __init__.py file if necessary:
if not os.path.exists(APP_MIGRATION_PATH):
    os.makedirs(APP_MIGRATION_PATH)
    open(os.path.join(APP_MIGRATION_PATH, '__init__.py'), 'w').close()

We also took the time to update the Django settings, somewhere inside the call to settings.configure function, you'll find:

# Django needs to be told where is the migration module
MIGRATION_MODULES={APP_LABEL: APP_MIGRATION_MODULE},

Not so fake modules?

A correctly configured Django application should normally have a model modules, a urls modules and a tests module. Python provides us with an interesting class: types.ModuleType, that will help us create a module, you guessed it:

# Used so you can do 'from <name of file>.models import *'
models_module = ModuleType('%s.models' % (APP_LABEL))
tests_module = ModuleType('%s.tests' % (APP_LABEL))
urls_module = ModuleType('%s.urls' % (APP_LABEL))

# we created the urlpatterns list earlier,
# let's attach this variable to the urls_module:
urls_module.urlpatterns = urlpatterns

# A model module needs to have classes that inherits from models.Model
# let's correctly add all our models we defined earlier
# as attributes to the models_module:
for variable_name, value in list(locals().items()):
    # We are only interested in models
    if inspect.isclass(value) and issubclass(value, models.Model):
        setattr(models_module, variable_name, value)

# Setup the fake modules
sys.modules[models_module.__name__] = models_module
sys.modules[urls_module.__name__] = urls_module
sys.modules[APP_LABEL].models = models_module
sys.modules[APP_LABEL].urls = urls_module

Going further?

I really hope you've learned something, I did \o/. You may want to browse some of the gists kept by Javier Buzzi.

You may also be really interested by the work done for django-micro a lightweight wrapper around Django that turns it into a microframework.

If you have any kind of interesting improvements, advices, suggestions, please send me an email, or post a comment on Mastodon. Still not took the time to install a comment system, shame on me.

o/