This is part one of our series about providing initial data in Django web projects. Here is part two. This post is targeted at developers, and especially developers interested in Wagtail, Python and Django. You probably need some basic Django knowledge to make the most out of reading this article.

Here at Codista we build a lot of websites and web applications. The process of setting up a project is always quite similar: you start a project using a cookiecutter or via django-admin startproject mysite.

Followed by a few manual steps like:

  • Setting up your database
  • Creating and running database migrations
  • Creating an admin user via python manage.py createsuperuser
  • Setting up some initial project data or providing some data fixtures

After that you start developing features - your first database model, creating and running database migrations and then you hook up the first view to check out if the basic project setup is working properly.

Nothing fancy until this point. Now you are probably not developing this project alone, but you have a team providing different skills to the project. Probably a frontend developer who is mastering HTML, CSS and JS, some more backend developers and maybe even a data scientist.

The problem

At this point in the project you might ask yourself how to onboard your team members as fast and consistent as possible, without having to force them to do all these manual steps over and over again.

Since we are developers we like automating away repeating tasks, as it makes our lives easier. So we came up with an automated solution that we call the TOTAL SETUP.

Onboarding team members is just one use case for it, it can also be used to achieve the following tasks:

  • resetting the local environment (for example after you reset your database migrations), with initial data available afterwards
  • setting up the initial production data when we do the first deployment
  • setting up a staging server with initial data
  • resetting your project to a clean state after you messed up with the data or did not touch the project for a long time

That's why this approach can save you and your team a lot of time and headaches.

The Total Setup Approach

So let’s dive in. At the core of our approach are three management commands:

  • total_reset
  • total_setup
  • setup_page_tree

The total_reset command is the main command which will be executed by team members to setup their local environment. It has the following goals:

  • Create or recreate the database
  • Run the database migrations
  • Call all other management commands to setup your project data

The code below should give you an idea how the total_reset is structured. You can find the final snippet in our GitHub repository.

class Command(BaseCommand):
    """DEV ONLY: Dumps the entire DB and sets up everything anew."""

    requires_system_checks = False

    def _terminate_db_connections(self, database):
        """Terminates the database connections to be able to drop the database"""
        ...

    def _create_db(self, database):
        """Sets up the database"""
        ...

    def _drop_db(self, database):
        """Drops the database"""
        ...

    def _create_or_recreate_db(self, database):
       """creates or recreates the database"""
        ...

    def handle(self, *args, **options):
       """entry point"""
        ...

Using the total_reset command helps to automate the repetitive task of recreating a database. But there is more to it - it calls a few other management tasks to execute further setup tasks. For example it executes the migrations:

call_command("migrate", verbosity=verbosity)

It will also run the total_setup command, which is responsible for setting up initial project data & settings. It will:

  • Create the admin users
  • Setup the correct domain if you are using Django’s Site Framework
  • Setup the wagtail domain if you are using Wagtail
  • Setup all needed project data & provide data fixtures

Here's how this command looks. You can find the final snippet here.

class Command(BaseCommand):
    """Sets up initial project data & settings. Also in production!"""

    def _set_domain(self):
        """Sets the django and wagtail domains. Across all environments."""
        current_site = Site.objects.get_current()
        if settings.DEBUG:
            current_site.domain = "localhost:3000"
        elif getattr(settings, "STAGING", False):
            current_site.domain = "test.codista.com"
        else:
            current_site.domain = "www.codista.com"
        current_site.save()
        if PROJECT_USES_CMS:
            wagtail_site = WagtailSite.objects.get()
            wagtail_site.hostname = current_site.domain
            wagtail_site.save()

    def setup_production(self):
        """PRODUCTION ONLY STUFF."""
        self._set_domain()
        call_command("create_project_users", verbosity=self.verbosity)

    def setup_development(self):
        """DEVELOPMENT ONLY STUFF."""
        self.setup_production()

    def handle(self, *args, **options):
        """entry point"""
        self.verbosity = options["verbosity"]
        if not settings.DEBUG:
            if self.verbosity > 0:
                self.stdout.write("Setting up production defaults...")
            self.setup_production()
            return

        if self.verbosity > 0:
            self.stdout.write("Setting up sensible development defaults...")
        self.setup_development()

You might have noticed the create_project_users command. This command is responsible for setting up the admin users for your team. You can find the full command on Github.

Takeaways

If you made it this far, you have already automated quite a bunch of repetitive tasks that would otherwise have been required to run manually. Using this approach we reached the following status:

  • Automate some simple, but as a whole quite time-consuming tasks
    • Creating and recreating the database
    • Creating your user and your team members as Django admins
    • Running the database migrations
  • Provide a process to consistently setup initial project data where you can hook into various management commands to setup additional project specific initial data & fixtures
  • Achieve consistency and predictability in setting up initial project data

In the end, all that's left is to execute ./manage.py total_reset to set up a consistent initial data set across your entire team.

If you want to learn more about providing initial data in Django / Wagtail projects, continue reading Part Two of this blog post series.

Other Posts

  • Wagtail multi-language and internationalization

    Websites often have an international audience. The process of serving translated content is referred to as "internationalization" or "multi-language" support. Here's how we're approaching that in our web projects using Wagtail.

  • Is System.Text.Json ready for prime-time?

    With the advent of .NET Core 3, there is finally builtin support for JSON, without the need to use the excellent Json.NET. Let's take a quick dive in and see if we should jump ship yet.