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:
- Settings via environment variables with django-environ.
- Celery configuration.
- Django REST Framework configuration + Swagger with drf-yasg.
- Typical user CRUD APIs.
- Code formatting with Black.
- Imports sorting with isort.
- Pre-commit hook for running test & linters on each commit.
- Custom user model
- Custom documentation with drf-yasg.
- Authentication setup using dj-rest-auth
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
Install cookiecutter and packaging:
pip install cookiecutter packaging
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
Answer the questions in wizard.
Steps after project setup
Install the dependencies:
pip install -r requirements_Dev.txt
Copy .env
file from example file and set your settings:
cp .env.template .env
Run migrations:
python manage.py migrate
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 defaultstartapp
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>
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',
...
]
eg: if your app_name
is foo
then this would be:
INSTALLED_APPS = [
...
'{{ cookiecutter.project_slug }}.apps.foo.apps.FooConfig',
...
]
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 thisREADME.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,
}
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."
}
}
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):
...
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)