Siteconfig: The easy way to toggle Django apps on and off across multiple websites
Recently updated on
The Siteconfig package (django_site_config) allows you to configure a single app in different ways for different areas of your Django-powered website, or to deactivate an app entirely for some of those parts, while leaving it active for others.
The inspiration behind Siteconfig began with a large multi-site healthcare system client. As with most healthcare systems, there were a number of different website types needed - hospitals, specialty practices, clinics and nursing schools - many sharing specific web-based apps. Siteconfig allows us to identify a menu of Django apps, and make them configurable on a case-by-case basis. The end result is that when the healthcare system brings on a new hospital, we can simply go to Siteconfig, register the new section/microsite/website and simply turn on apps such as physian directory, ecards, event calendar, news center, form builder and site search.
What follows is a technical walk through of the configuration and practical application of Siteconfig.
Siteconfig is available from the Imaginary Landscape github repo at: https://github.com/ImaginaryLandscape/django_site_config
An example Django project that demonstrates Siteconfig and includes all code snippets below is also available at: https://github.com/ImaginaryLandscape/site_config_example_project
Initial Configuration
Apps that have been registered with Siteconfig can be configured either via the Django admin or via the settings file. The user's choice of backend (either the "model" or the "settings" backend) determines which of these approaches will be in effect for a given project. Siteconfig is most flexible when using the model backend, so that's what we'll demonstrate below.
The Virtual Environment
As with any project, your first step should be creating a virtual environment. The example project assumes a virtual environment that runs Python 3.10 or higher, and that contains Django and Siteconfig at a minimum. If you are familiar with the Poetry package manager, the "poetry.lock" file in the example project should pull in everything you need.
The Model Backend
Once you've created your virtual environment, add both the "Siteconfig" package and the model backend to your project's `INSTALLED_APPS` setting. You should also add a couple of custom context processors to your `TEMPLATES` variable. The relevant parts of your "settings" module might look like this:
<your_project>/settings.py
INSTALLED_APPS = [ ... 'site_config', 'site_config.backends.model_backend', ... ] ... TEMPLATES = [ { ... # (other TEMPLATES settings) 'OPTIONS': { 'context_processors': [ ... # (other context processors) 'site_config.context_processors.decide_base_template', 'site_config.context_processors.add_site_specific_options', ], }, }, ]
For the model backend only you must create a migration file, and then run the "migrate" management command:
$ ./manage.py makemigrations site_config $ ./manage.py migrate site_config
Siteconfig allows you to individually configure different combinations of websites and apps. Siteconfig uses the word "websites" to mean different URL paths under your project's base URL. The "apps" are just the apps in your project, but note that they must be customized to work with Siteconfig, so they will have to be apps to which you can make changes or that are already designed to work with Siteconfig.
The Application ("App") Object
Let's assume that "foo" and "bar" are the names of the apps we want to use with Siteconfig.
If you're using the example project, these apps should already be present and installed. If not, you can always create them with Django's built-in "startapp" management command.
For “foo” to work with Siteconfig:
- It must be included in your project's `INSTALLED_APPS` setting.
- Its "__init__.py" module must include the following:
foo/__init__.py
import site_config class FooConfig(site_config.SiteConfigBase): application_short_name = "foo" application_verbose_name = "Foo" def get_default_configs(self): return { "HELLO_MESSAGE": { "field": "django.forms.CharField", "default": "Hello from the Foo app!" }, } site_config.registry.config_registry.register(FooConfig)
The "get_default_config()" method is called when generating the change form for "websiteapplication" objects in the Django admin (more details on that below). It should return a dictionary whose keys are the names of per-site configuration variables (the string "HELLO_MESSAGE" above), and whose values are inner dictionaries. Each inner dictionary should, as shown above, have a key "field" mapped to path to a Django form field and a key "default" mapped to a default value for the variable.
The app also needs a "foo/urls.py" module. It might look like this:
foo/urls.py
from django.urls import path from .views import HelloWorldView app_name = "foo" urlpatterns = [ path("", HelloWorldView.as_view(), name="hello_world") ]
Here we assume that a class-based view "HelloWorldView" exists in the "views.py" module of the same directory. The view is just a normal Django view and doesn't require any special tweaking to work with Siteconfig:
foo/views.py
from django.views.generic import TemplateView class HelloWorldView(TemplateView): template_name = "foo/hello_world.html"
The view assumes the existence of a template. In the snippet below we assume that “base.html” is the name of your project’s base template, but you should use the name appropriate to your project. The template also contains something special that we will come back to later:
foo/templates/foo/hello_world.html
{% extends "base.html" %} {% block content %} <h1>{{ siteconfig_options.HELLO_MESSAGE }}</h1> {% endblock %}
If you’re using the example project, all of the code above should already be in place. But if you’re creating the apps yourself as you’re reading, you should now create the “bar” app. In that case, just repeat the steps above, substituting "bar" for "foo" in all cases.
With these fundamentals in place, try to get your project running (e.g., with the "runserver" management command). Once it's running, log into the Django admin (creating a superuser if necessary) and go to "<your_domain>/admin/site_config/application/" in your browser. From here, click the "Add Application" button.
This should take you to the admin change form for the "Application" object. The names "Foo" and "Bar" should appear as choices in the "Short name" drop-down selector. This list is populated by apps that are registered with Siteconfig, which we did in each app's "__init__.py" module. Select "Foo" from the drop-down and check the "Active" checkbox. You don't have to worry about putting anything in the "Description" field. Then click the "save" button.
Finally, repeat with the "Bar" app.
The "Website" Object
Now go to "<your_domain>/admin/site_config/website/" and click the "Add Website" button.
On the "website" admin change form, enter a value of "test1" in the "name" field. The value should be duplicated automatically in the "short name" field. Check the "active" checkbox (don't worry about the "description" field) and then click the "save" button. The "website" short name will form part of the path to the Siteconfig apps in your browser.
Now create a second website. Let's call it "test2" for the sake of unoriginality. Repeat the steps above, but substitute "test2" for "test1" in all cases.
The "websiteapplication" Object
Now that you have an activated app and an activated website, you can combine them by creating a "websiteapplication" object. Go to "<your_domain>/admin/site_config/websiteapplication/" and click the "Add Website Application" button. Choose "test1" from the "Website" drop-down and "foo" from the "Application" drop-down. Set the "Active" drop-down to "Enabled" and click the "save" button. If you do the same for the other combinations of website and application, you should end up with a total four "websiteapplication" instances. The "test1" and "test2" websites will each have an instance for both the "foo" and "bar" apps.
Connecting to your Main Project
Now add the following to your project's *main* "urls.py". The example below wraps all "foo" and "bar" paths in the "enable_disable_website" decorator. The decorator is not necessary for Siteconfig to function, but it allows you -- if you are using the model backend -- to turn an app on and off for a given website (path).
<your_project>/urls.py
from django.conf.urls import include from django.contrib import admin from django.urls import path, re_path from foo import FooConfig from bar import BarConfig from site_config.decorators import enable_disable_website, decorated_includes site_urlpatterns += decorated_includes( lambda func: enable_disable_website(func, FooConfig), [path('foo/', include('foo.urls', 'foo'))] ) site_urlpatterns += decorated_includes( lambda func: enable_disable_website(func, BarConfig), [path('bar/', include('bar.urls', 'bar'))] ) urlpatterns = [ path('admin/', admin.site.urls), # Other urls in your project... re_path(r'^(?P<website>[\w-]+)/', include(site_urlpatterns)), ]
Now at last all the components are in place:
- Siteconfig and the model backend have been added to the `INSTALLED_APPS` setting
- The Siteconfig-enabled apps have been added to the `INSTALLED_APPS` setting
- The Siteconfig middleware has been added to the `TEMPLATES` setting
- "application" and "website" objects have been created and enabled in the admin
- "websiteapplication" objects have been created and enabled in the admin
- The app URLs have been wrapped in the "enable_disable_website" decorator and added to the main "urls.py" module
If all of the above checks out, go to "<your_domain>/test1/foo/" in your browser, where "HelloWorldView" should display the value of the “HELLO_MESSAGE” variable.
What Now?
At this point we've gone to a lot of trouble to set up a couple of Siteconfig-powered views that could have been done much more simply. So what else does Siteconfig allow us to do? The answer is that we can now assign custom values to the fields defined in the "__init__.py" modules of the "foo" and "bar" apps. Let's go back to the admin change form of one of our "websiteapplication" instances. For example, go to "<your_domain>/admin/site_config/websiteapplication/1/" in your browser. This will be the instance for the "test1" website and the "foo" app if you created your first "websiteapplication" as described above.
At the bottom of the form you should see a section labeled "Configuration Options" that includes the "HELLO_MESSAGE" field. The value of the field will be the default value defined in the "__init__.py" module of the "foo" app. But let's try changing it to something else. Enter "All your Foo are belong to us" (or anything else you want), and click the "save" button.
Now if you refresh the front-facing page ("<your_domain>/test1/foo/"), the default message should be replaced with your custom value. At the same time, the “foo” app for the “test2” website ("<your_domain>/test2/foo/") will continue to display the default message.
This is possible because of the "siteconfig_options" variable we used in the "hello_world.html" template above. The "siteconfig_options" variable will be a dictionary whose keys are the names of all your custom configuration options, and whose values will be configurable in the Django admin. Therefore they can be accessed in the template like this:
{{ siteconfig_options.<NAME_OF_YOUR_VARIABLE> }}
You can even include HTML in the value, as might be generated by a RichText widget. You can then render it on the front end by sending the value through Django's built-in "safe" filter:
{{ siteconfig_options.<NAME_OF_YOUR_VARIABLE>|safe }}
Thus Siteconfig can help you to differentiate styling, branding, and text when using the same app in different areas of your website.
Conclusion
This completes the walk-through of the basic functions of SiteConfig, though there are other features we haven’t discussed. For example, we haven't given an example of the settings backend, or Siteconfig's template override mechanism. These additional features may be covered in a future blog post.
Need Upgrade Assistance?
Django 3.2 reaches its end of life in April 2024. If your site is on this or an earlier version, please fill out this form and we will reach out and provide a cost and time estimate for your consideration. No obligation.