django-solo
Django Solo is a Python library that simplifies working with singleton models in Django applications. Singletons are database tables designed to hold only one row, often used for global settings or site-wide configurations that can be edited via the Django admin interface. It provides helper classes for models and admin, a template tag for easy retrieval, and supports caching. The library is actively maintained, with version 2.5.1 being the latest, and sees regular updates to support newer Django and Python versions.
Warnings
- breaking Older Python and Django versions are regularly dropped. Ensure your project's Python and Django versions are compatible with the django-solo version you are using. For example, Django-solo 2.2.0 dropped support for Python 3.7 and Django 4.0/4.1. Version 2.4.0 dropped support for Django <3.2 and simplified `default_app_config` removal.
- gotcha Prior to version 2.4.0, a potential cache key collision could occur for similarly named singleton models across different Django applications. This could lead to incorrect data retrieval from the cache.
- gotcha Caching is disabled by default in `django-solo`. Every call to `get_solo()` will result in a database query unless caching is explicitly enabled and configured in your Django settings.
- gotcha If migrating an existing model to a singleton model or if your `SingletonModel` might have pre-existing rows, it's recommended to set `singleton_instance_id` on the model explicitly (e.g., `singleton_instance_id = 1`) to ensure `django-solo` uses the correct instance.
- gotcha Calling `SiteConfiguration.get_solo()` directly within an `AppConfig.ready()` method can lead to `django.db.utils.OperationalError` if database migrations for the model have not yet been applied, particularly on first `migrate`.
Install
-
pip install django-solo
Imports
- SingletonModel
from solo.models import SingletonModel
- SingletonModelAdmin
from solo.admin import SingletonModelAdmin
- get_solo
{% load solo_tags %} {% get_solo 'app_label.ModelName' as config %}
Quickstart
import os
import django
from django.conf import settings
from django.template import Context, Template
from django.test import override_settings
# Configure Django for a minimal setup
settings.configure(
INSTALLED_APPS=['solo', 'my_app'],
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {'context_processors': []}
}],
USE_I18N=True,
LANGUAGE_CODE='en-us',
ROOT_URLCONF='my_project.urls', # Placeholder for minimal config
)
django.setup()
# Create a dummy app for the example
with open('my_app/__init__.py', 'w') as f: pass
with open('my_app/models.py', 'w') as f:
f.write(
"""from django.db import models
from solo.models import SingletonModel
class SiteConfiguration(SingletonModel):
site_name = models.CharField(max_length=255, default='My Site')
maintenance_mode = models.BooleanField(default=False)
def __str__(self):
return "Site Configuration"
class Meta:
verbose_name = "Site Configuration"
"""
)
with open('my_app/admin.py', 'w') as f:
f.write(
"""from django.contrib import admin
from solo.admin import SingletonModelAdmin
from my_app.models import SiteConfiguration
admin.site.register(SiteConfiguration, SingletonModelAdmin)
"""
)
# Simulate Django migrations (simplified for quickstart)
from django.apps import apps
apps.app_configs['my_app'].models_module = __import__('my_app.models', fromlist=[''])
from my_app.models import SiteConfiguration
# Accessing the singleton instance
config = SiteConfiguration.get_solo()
print(f"Initial site name: {config.site_name}")
# Modifying and saving
config.site_name = "Updated Site Name"
config.maintenance_mode = True
config.save()
# Retrieve again to confirm (without caching enabled, this hits DB)
updated_config = SiteConfiguration.get_solo()
print(f"Updated site name: {updated_config.site_name}")
print(f"Maintenance mode: {updated_config.maintenance_mode}")
# Example of using in a Django template (requires solo_tags to be loaded)
# This is illustrative, full Django setup for templates is more involved.
# For actual template rendering, you'd typically have a view and proper template loaders.
# Let's simulate a template context and rendering here.
@override_settings(TEMPLATES=[
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {'context_processors': []},
},
])
def render_template_example():
from django.template import Context, Template
from django.apps import apps
# Manually register solo_tags for this isolated test if not auto-loaded
# In a real Django project, solo would be in INSTALLED_APPS and tags loaded automatically
apps.app_configs['solo'].models_module = __import__('solo.models', fromlist=[''])
apps.app_configs['solo'].admin_module = __import__('solo.admin', fromlist=[''])
apps.app_configs['solo'].templatetags_module = __import__('solo.templatetags', fromlist=[''])
template_string = """
{% load solo_tags %}
{% get_solo 'my_app.SiteConfiguration' as site_config %}
<h1>Welcome to {{ site_config.site_name }}</h1>
{% if site_config.maintenance_mode %}
<p>Site is currently under maintenance.</p>
{% endif %}
"""
template = Template(template_string)
context = Context({})
rendered = template.render(context)
print("\n--- Template Output ---")
print(rendered)
render_template_example()
# Cleanup dummy app files
os.remove('my_app/__init__.py')
os.remove('my_app/models.py')
os.remove('my_app/admin.py')
os.rmdir('my_app')