DEV Community

Amartya Gaur
Amartya Gaur

Posted on

A cookiecutter for Django Rest Framework projects.

TLDR;

I made a cookiecutter for DRF projects, you can check it out here:
https://github.com/Ohuru-Tech/drf-cookiecutter

Now, if you do want to read the rest of it:

DRF (Django Rest Framework) is just awesome !! I have been working a lot in DRF and I find myself writing same code multiple times for different projects, it is a tedious process and goes strongly against the DRY (don't repeat yourselves) principle... (I know that applies to a single application, bear with me..). After writing the exact same configuration code for setting up a new project thousands of times, I finally came across this gem:

https://cookiecutter.readthedocs.io/en/stable/

This helped me come up with a template (sort of) which will serve as the starting point for a new Django Rest Framework project. I am writing this post to talk about it's features and urge anyone interested to try and help make it better :)

So, let's get to it:

Project outline

What's included:

If you want to just see it in action, you can skip the details below and just try it out:

Create python virtual environment.

python3 -m venv venv
source venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Install cookiecutter and packaging:

pip install cookiecutter packaging
Enter fullscreen mode Exit fullscreen mode

Step into directory you want to create project and generate project with cookiecutter:

cd /path/to/directory
cookiecutter https://github.com/Ohuru-Tech/drf-cookiecutter
Enter fullscreen mode Exit fullscreen mode

Answer the questions in wizard.

Steps after project setup

Install the dependencies:

pip install -r requirements_Dev.txt
Enter fullscreen mode Exit fullscreen mode

Copy .env file from example file and set your settings:

cp .env.template .env
Enter fullscreen mode Exit fullscreen mode

Run migrations:

python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Start building your awesome project!

This section will mostly talk about specific project details:

App Structure

All the apps are located in the {{ cookiecutter.project_slug }}/apps folder. The project uses a custom startapp command, refer {{ cookiecutter.project_slug }}/management/commands/startapp.py for more information.

The command initializes a new app with the following:

  • A models directory as opposed to a generic models.py file which is created by Django's default startapp command, this allows you to have models separated by their use-case and helps to keep the project clean.
  • A views directory which corresponds to the above reasoning for having a models directory.
  • A seriazliers directory which corresponds to the above reasoning for having a models directory.

The other files and folders are similar to how Django does (admin.py, apps.py).

This command ensures that the apps are added to the apps folder and are initialized in accordance with the project-setup.
To add a new app run:

$ python manage.py startapp <app_name>
Enter fullscreen mode Exit fullscreen mode

Then include the app to the INSTALLED_APPS list in config/base.py like so:

INSTALLED_APPS = [
    ...
    '{{ cookiecutter.project_slug }}.apps.<app_name>.apps.<app_name_capitalized>Config',
    ...
]
Enter fullscreen mode Exit fullscreen mode

eg: if your app_name is foo then this would be:

INSTALLED_APPS = [
    ...
    '{{ cookiecutter.project_slug }}.apps.foo.apps.FooConfig',
    ...
]
Enter fullscreen mode Exit fullscreen mode

It is always nice to include the complete config while adding the app in the INSTALLED_APPS list. This maintains consistency when using signals.

Settings

The project uses django-configurations for class based settings, there are two configurations available, Dev and Prod, the common settings exist in config/base.py, Dev in config/dev.py and Prod in config/prod.py.

You can use the DJANGO_CONFIGURATION environment variable to switch between the configurations, the Dev settings are used by default.

Environment Variables

Most of the configurations in the project are based on environment variables, you have the following options to use them:

  • Set them up in a .env file in the root of the project, i.e. the place where you have this README.md file, you can refer to the .env.template for the available options and easily create your configuration file using that.
  • Set them up via the environment variables themselves.
  • Set them up in the docker / kubernetes runtime via the .env file, most of the things still remain the same except that you will need to get rid of the comments in .env.template while creating the .env file.

You can still run the project without creating any of those files because there are defaults available to almost all of the required settings but, you will need to set up the db instances, rabbit mq (in case you want to use celery) and make sure the settings match the ones used by default in the configurations. It is recommended to always have a .env file managing these settings.

NOTE:- The values for {{ cookiecutter.project_slug | upper() }}_PASSWORD_RESET_URL and {{ cookiecutter.project_slug | upper() }}_EMAIL_VERIFICATION_URL needs to be set in order for these flows to work properly, they make sure that the redirection happens to the frontend URLs for password reset and email verification.

The email verification could either be a page that gets and processes the link and sends the corresponding request to BE for email verification or have a page that waits for an explicit confirmation with the user.

Documentation

The project uses drf-yasg to auto-create documentation for the API endpoints, but the generated documentation is not very good, hence, this project uses a custom schema to manage the documentation.

The details for each endpoint are managed using a class, for example, the accounts app supports profile update, so the ProfileUpdate class is used to manage the documentation for that endpoint, it looks something like this:

class ProfileUpdate:
    desc = f"""
Profile Update is an API endpoint to update the profile associated
with a particular user.

By using this endpoint, you can make a patch call to update the profile
associated with any user. The {fields_to_md(ProfileCreateUpdateSerializer.fields_names)}
are the fields that can currently be updated
    """  # noqa

    responses = {
        "200": openapi.Response(
            description="OK",
            examples=profile_update_200_example,
            schema=UserInfoSerializer,
        ),
        "401": openapi.Response(
            description="Unauthorized", examples=profile_update_401_example
        ),
        "403": openapi.Response(
            description="Permission Denied",
            examples=profile_update_403_example,
        ),
    }

    code_examples = [{"lang": "Bash", "source": profile_update_curl}]

    swagger_setup = {
        "operation_id": "Profile Update",
        "operation_description": desc,
        "request_body": ProfileCreateUpdateSerializer,
        "responses": responses,
        "code_examples": code_examples,
    }
Enter fullscreen mode Exit fullscreen mode

The examples come from config/examples and look something like this:

profile_update_200_example = {
    "application/json": {
        "id": 1,
        "name": "Test developer",
        "profile_pic": "/some_pic.jpg"
    },
}

profile_update_401_example = {
    "application/json": {
        "error": "NotAuthenticated",
        "detail": "Incorrect authentication credentials.",  # noqa
    }
}

profile_update_403_example = {
    "application/json": {
        "error": "PermissionDenied",
        "detail": "You do not have permission to perform this action."
    }
}
Enter fullscreen mode Exit fullscreen mode

After setting up the class, you can use the swagger_auto_schema decorator to plug the specification into the view.

@method_decorator(
    swagger_auto_schema(**ProfileUpdate.swagger_setup), "partial_update"
)
class ProfileViewSet(UpdateModelMixin, PsqMixin, GenericViewSet):
    ...
Enter fullscreen mode Exit fullscreen mode

In the above example, the ProfileUpdate class we declared earlier is used to plug those settings onto the partial_update action.

The views

The project is set up to use GenericViewSet, it further uses different mixins to provide the create, retrieve, update and destroy actions.
You can set up custom actions for anything else that you might need to do.

The views also use the PsqMixin to configure different serizlizers and permissions for the actions. The serializers come from the serializers directory and the permissions are from the permissions directory (or file, as per your convinience).

Routers

The GenericViewSet automatically sets up the URLs based on the mixins you use with it, it also adds the custom actions as urls. Thus, we have urls/versioned_urls.py which uses a SimpleRouter to add these viewsets and plug them at different endpoints.

Versioning

The version comes from the environment variables, you can set it up in the .env file. To add and support different versions, you can have differnt urls list in the urls/versioned_urls.py file, then update the environment to default to the latest version. You can manually include the other versions in urls/__init__.py as and when needed.

Database

The project supports PostgreSQL, PostGIS, MySQL, MySQL (GIS), Oracle, Oracle (GIS), Redshift, CockroachDB, and SQLite, you can refer this table in order to find out the details on forming the url corresponding to your database.

Deployment

I have included a Dockerfile with a custom bash script entrypoint that can help you get both the django backend and celery worker + beat scheduler up and running in a jiffy...

I plan to leverage kubernetes and make a helm chart in future to easily set up a production grade server with nginx but I can use some help around this :P...

Anyway, that's almost as much as I wanted to say about this, please check the project out, do leave a star on the repository if you like it and feel free to make PRs to improve it.

Top comments (1)