Site configurations for your Django project

Author: Darryl Buswell

Dec 31, 2020

All of our products come packaged with a set of models to manage your site configurations. It won't be relevant for all projects, but having a central place to manage all of your site's global settings, navigation menus, social links etc., can be extremely handy. Not only that, but you can take it one step further and create separate site configurations and switch between each as desired.

There are a million ways you could go about doing something like this. But we like to first create a 'core' application for all of our projects. This application includes our site configuration models, and serves out their data using a context processor. As an example, let's first create our base site configuration model within our core app.

core/models.py



class SiteConfiguration(models.Model):
    site_name = models.CharField(max_length=255, default='Site Name')
    site_url = models.URLField(blank=True)
    site_description = models.CharField(max_length=160, blank=True, null=True)
    is_active = models.BooleanField(default=False)

    class Meta:
        verbose_name = "Site Configuration"
        verbose_name_plural = "Site Configurations"

Here, we include a field for our site name, URL for the site, a description of the configuration and a boolean as to whether the configuration is active. Note that when we call our current configuration, we are going to look for the last (latest) record where is_active is true. Sure, we could enforce a save method to ensure only one record remains active, but it's simple enough to handle a filter on the model accordingly.

Next, we are going to create a model to handle our sites navigation links and use our site configuration model as a foreign key to reference its entries.

core/models.py



class NavigationLink(models.Model):
    site_config = models.ForeignKey(SiteConfiguration, related_name='navigation_link', verbose_name='Config',
                                    on_delete=models.SET_NULL, blank=True, null=True)
    parent = models.ForeignKey('self', on_delete=models.CASCADE, related_name='child', blank=True, null=True)

    order = models.PositiveIntegerField(default=0)
    title = models.CharField(max_length=255)
    target = models.CharField(max_length=255)

    class Meta:
        verbose_name = "Navigation Link"
        verbose_name_plural = "Navigation Links"
        ordering = ['order']

You may notice the parent field in this model. This field allows us to reference parent to child relationships in our navigation items. That is, we can indicate whether an item is part of a sub-menu, and make sure we treat it accordingly in our template.

core/models.py



class SocialLink(models.Model):
    site_config = models.ForeignKey(SiteConfiguration, related_name='social_link', verbose_name='Config',
                                    on_delete=models.SET_NULL, blank=True, null=True)

    order = models.PositiveIntegerField(default=0)
    title = models.CharField(max_length=255)
    icon_class = models.CharField(max_length=255, blank=True, null=True)
    social_url = models.URLField(blank=True)

    class Meta:
        verbose_name = "Social Link"
        verbose_name_plural = "Social Links"
        ordering = ['order']

Note here that we like to include a field to store the icon class. Since we tend to make use of icon packages, we can make use of this field to store the appropriate icon class reference for each of our social links.

Finally, we want to include a post save routine so that each time we create a new site configuration, it creates the related navigation and social link entries for us to edit.

core/models.py



from django.db.models.signals import post_save

def site_config_post_save(sender, instance, created, **kwargs):

    if created:

        related_models = [
            NavigationLink,
            SocialLink,
        ]

        for related_model in related_models:
            related_model.objects.create(site_config=instance)
            
post_save.connect(site_config_post_save, sender=SiteConfiguration, dispatch_uid='site_config_post_save')

Ok, we are ready to make our admin view so that we can edit our configurations. We can get this done with a single view for our site configuration model, and include inlines for our navigation and social links to keep everything neat and tidy.

core/admin.py



class NavigationLinkInlineAdmin(admin.TabularInline):
    model = NavigationLink
    extra = 0

    list_display = [
        'order',
        'title',
        'target',
        'parent',
    ]


class SocialLinkInlineAdmin(admin.TabularInline):
    model = SocialLink
    extra = 0

    list_display = [
        'order',
        'title',
        'icon_class',
        'social_url',
    ]

@admin.register(SiteConfiguration)
class SiteConfigurationAdmin(admin.ModelAdmin):
    list_display = [
        'id',
        'site_name',
        'site_url',
        'maintenance_mode',
        'is_active',
    ]

    list_display_links = [
        'id',
    ]

    fields = [
        'site_name',
        'site_url',
        'site_description',
        'maintenance_mode',
        'is_active',
    ]

    inlines = [
        NavigationLinkInlineAdmin,
        SocialLinkInlineAdmin,
    ]

As a final step. Let's include our context processor, so we can call our site configuration, as well as our navigation and social links in our templates.

core/context_processors.py



from apps.core.models import SiteConfiguration

def core(request):

    site_config = SiteConfiguration.objects.filter(is_active=True).last()

    return {{
        'site_config': site_config,
        'navigation_links': site_config.navigation_link.all(),
        'social_links': site_config.social_link.all(),
    }}

And don't forget to register the context processor in your Django config.

settings.py



TEMPLATES = [
    {
        'OPTIONS': {
            'context_processors': [
                ...
                'apps.core.context_processors.core',
            ],
        },
    },
]

And we are done. You now have a switchable site configuration which you can call from any part of your site. Easy peasy.


Share this post: