paint-brush
12 Essential Django Programming Tips for Developersby@thesam
857 reads
857 reads

12 Essential Django Programming Tips for Developers

by Amir ShahsafiJanuary 16th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Django has a lot of configs in settings.py and finding and managing your configs is harder than yesterday. I decided to wrap up all these to-dos and not-to-dos as an instructor so I could use them in the future and also might be helpful to others.
featured image - 12 Essential Django Programming Tips for Developers
Amir Shahsafi HackerNoon profile picture

It’s been about 6 years since I started working with Django, and through all these years I’ve made some personal customizations in Django’s structure, and coding style.


I decided to wrap up all these to-dos and not-to-dos as an instructor so I could use them in the future and also might be helpful to others.

Table of the content

  1. Modular Settings
  2. Third Party apps that could save you a lot of time:
  3. Always Have a TODO file
  4. Always Have a core web-app
  5. Using Django’s web-app to stay Cohesive
  6. Django’s custom management commands
  7. Store your service messages in a separated file
  8. Bypassing the meanings
  9. Managing a web-apps files
  10. Stick To Generic Views and Mixins
  11. Use Serializers!
  12. Fixtures

Modular Settings:

https://github.com/mrShahsafi/Edu/tree/master/djnagoForFun/djnagoForFun/settings

When you are developing your Django project, and after each new web-app you’ll notice you have a lot in your settings.py and finding and managing your configs is harder than yesterday.


As you might notice, when you are calling the very famous manage.py Django’s entry-point command, it will try to create DJANGO_SETTINGS_MODULE and set PROJECT_NAME.settings to it.


What I do, I convert the settings.py to a python module with the init.py.


Every project as least has 2 separated running environments:


  • Development Environment

  • Production Environment


And some of your setting’s configs are similar in different environments.


Until now we can include three files in our settings module

  • 1- base.py

  • 2- development.py

  • 3- production.py


As I said the base.py is for configs that are similar in different environments


base.py:


from pathlib import Path
import os  # Do not delete this

# Your everywhere service name

SITE_NAME = ""
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent


# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv(
    "DJANGO_KEY",
    default=“”,
)


# Application definition
DEFAULT_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

THIRD_PARTY_APPS = []

CREATED_APPS = []


INSTALLED_APPS = DEFAULT_APPS + CREATED_APPS + THIRD_PARTY_APPS 

MIDDLEWARE = []

ROOT_URLCONF = 
TEMPLATES = 
WSGI_APPLICATION = 
AUTH_PASSWORD_VALIDATORS = 
# Internationalization
…
# Static files (CSS, JavaScript, Images)
…
if not os.path.exists("logs"):
    os.makedirs(os.path.join(BASE_DIR, "logs"))


Pretty cool right? Let’s go to other files.


In the development.py you must include only development configs.


development.py


from .base import *

# import socket  # only if you haven't already imported this

DEBUG = True

ALLOWED_HOSTS = [
    "localhost",
    "127.0.0.1",
]

try:
    from .local import *
except Exception:
    pass

DATABASES = {
     "default": {
         "ENGINE": "django.db.backends.sqlite3",
         "NAME": BASE_DIR / "db.sqlite3",
     }
 }


INSTALLED_APPS += [
    "debug_toolbar",
    #    'django_extensions'
]

MIDDLEWARE += [
    "debug_toolbar.middleware.DebugToolbarMiddleware",
]

"""
    These commented config  will use \
        when you are running the project on Docker. 
"""
# hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
# INTERNAL_IPS = [ip[:-1] + "1" for ip in ips] + ["127.0.0.1", "10.0.2.2"]

INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"]


You might notice the local.py file that I include here , we’ll get into it later.


Now let’s take a look at our production configs.


production.py


from .base import *

DEBUG = False

ALLOWED_HOSTS = [#YOUR_PRODUCTION_HOSTS_ADDRESSESS]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": os.getenv("DB_NAME", default="db"),
        "USER": os.getenv("DB_USER", default="root"),
        "PASSWORD": os.getenv("DB_PASS", default="root-password"),
        "HOST": "postgres",
        "PORT": "5432",
    }
}


When we have our three files, we must somehow manage them at the entry point of the settings module and I mean the init.py: your module initializer file:


init.py:


from os import getenv

env = getenv("DJANGO_ENV", default="development")

if env == "production":
    print("You Are in the Production Mode.")
    from .porduction import *
elif env == "development":
    print("Warning! You Are in the Development Mode.\nDo Not use in any server.")
    from .development import *


Well, now it is time to explain that mysterious local.py file! Everything that only belongs to your local machine configs is going to lie there. For example, your email SMTP configs, your local database and etc. and remember, the local.py will always be in the .gitignore.


Of course, Django usually contains more third-party modules like DRF, Celery, Allauth, cache services, CORS headers, channels, Sentry. It is simple now, you’ll make a separate file for each of these modules and call it at the end of your base.py file. Here I only explain the DRF and you will get the pattern.


base.py


# At the end of your base.py

try:
 from .drf_settings import *
except Exception:
 pass


drf_settings.py:


from datetime import timedelta
from .base import SITE_NAME

REST_FRAMEWORK = {
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
    "DEFAULT_PARSER_CLASSES": (
        "rest_framework.parsers.JSONParser",
        "rest_framework.parsers.MultiPartParser",
        "rest_framework.parsers.FileUploadParser",
    ),
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
    "DEFAULT_PERMISSION_CLASSES": (
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ),
    "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
    "DEFAULT_PAGINATION_CLASS": "core.pagination.CustomPagination",
    "PAGE_SIZE": 9,
    # "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning", # when we active versioning the swagger ui brakes!= ""
    "TEST_REQUEST_DEFAULT_FORMAT": "json",
    "EXCEPTION_HANDLER": "core.errors.custom_exception_handler",
}



Third Party apps that could save you a lot of time:

Sentry : It will manage your project transactions, errors, etc. and report to you weekly

Django debug toolbar : For the development, it offers very good tools like the SQL query time, available signals and etc.

Django Chacheops : A slick app that supports automatic or manual queryset caching and automatic granular event-driven invalidation.

Django Admin Honeypot : Django application that provides utilities for preventing automated form spam.

Django Seed : Django-seed uses the faker library to generate test data for your Django models. This has been “hard-forked” from django_faker in order to support newer versions of Python and Django

Django Filter : Django-filter is a reusable Django application allowing users to declaratively add dynamic QuerySet filtering from URL parameters.

Django Allauth : Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. And most of the time, it will satisfy your requirements

DRF Spectacular : Sane and flexible OpenAPI 3.0 schema generation for If your are using Django REST framework and writing APIs.

Django Cities : Place models and worldwide place data for Django.

Always Have a TODO :

https://github.com/mrShahsafi/Edu/blob/master/djnagoForFun/TODO.md


When you have a lot of tasks that need to be done very fast and right, you can not mention all your to-dos in your task management service.


Most of the developers write their to-dos in the codebase and in most of the time, they forget to do them!


Make sure you have a todo.md or everything else is suitable for you to store all your tod-os list in it!


# The project Todo List items

---
- [ ] un-done Task
- [x] done Task
---
## Models

## APIs
- [ ] django channels middleware for JwtAuth
## Others

## Bugs

## Emails
- [x] Forgot Password link

## Tests
- [ ] model tests for Advertisement

## NOTES


Another benefit of the tod-os file is you can review all your tod-os in just one file later and archive them as a project document.

Always have a core web-app:

https://github.com/mrShahsafi/Edu/tree/master/djnagoForFun/core


You will always have some models, validations, normalizers, permissions, etc. that you could use in all your future web apps.


I call this the common web-app ‘core’.


Feel free to call it anything you want.

Using Django’s web-app to stay Cohesive

https://github.com/mrShahsafi/socialmedia

As much as your web-apps perform an atomic action, you will have more space to maintain them in the future.


For example, the authentication/authorization process must not be in the user’s app; they are 2 different steps and by separating their web-apps you will easily copy/paste their module to your new project with a very small amount of changes.

Django custom management commands:

https://github.com/mrShahsafi/Winance/tree/main/wallet/management/commands


Django comes with a very useful topic: Custom Management cmd. By following these instructions, your custom command will be like this

python manage.py my_custom_command


For example, maybe you want to create fake_user command or test your machine dependencies before you perform a special action. In this way, all your commands have one structure both for writing and execution. And believe me, it is very important when your project becomes a little complex.

Store your service messages in a separated file:

https://github.com/mrShahsafi/Edu/blob/master/djnagoForFun/core/responses/api_messages.py

When you raise an error or return a permission denied , you’ll have a message to show.


Stay away from hardcoding them into your logic.


All you need just a file that contains all these messages as variables.

Bypassing the meanings

https://github.com/mrShahsafi/Winance/blob/main/wallet/models/base.py


Sometimes some actions do not really need to done.


For example, you need to delete big amount of data from your database daily.


It will be a expensive transaction for your database.


Use abstract model that contains is_deleted attribute with the False default value and try to inherit all your other models from this common model.


All you need to do for bulk delete is turn the is_deleted flag of your instances to True and make sure you filter your querysets with:

qs = Model.objects.filter(is_deleted=Flase)


What I have just done here is an example, but you get the idea.

Managing a web-apps files

Don’t always write all your code in just one file.


If you see your .py is become bigger than what could be red in a one or two scrolls,

you need to break it to separate files, especially when it comes to views.py.


A view’s task is just to deliver the logics to a specific controller.


What I do is add a new layer to my views called logics.


Lets say we have a registration view called RegistrationApi, you can write the registrations logics in auth.logics.registration_api_logic and use it in your view.

Stick to Generic Views and Mixins:

Django developers think the Generic views and mixins are for the beginners as they grow as backend developers in the industry.


Well I must say, that is not true at all.


This is the power of the Django.


Then why you are using Django at all if you need to write everything from the scratch?


Also when you are using something like the DRF spectacular for Documenting the views , it is very easy for these type of modules to sync your schema and view when you have been use the generic views .

Use Serializers!

This concept may be a little strange to you but how many time do you use the request.data instead of using a serializer for it?


I’m sure it happens sometimes.


Remember that the serializers are the best practice to yourAPI view (from validating to creating )

And again, when you are using something like the DRF spectacular for Documenting, it is very easy for these type of modules to sync your schema.


And also remember, when you breaking your tasks apart as atomic process, they will be very easy to maintain in the future.


So if you wanna hear from me:



Separate your serializers by their identities



These three serializers:

  • MyModelListSerializer
  • MyModelDetailSerializer
  • MyModelInputSerializer

are better than just having MyModelSerializer.

Fixtures:

Django introduced fixtures concept to provide initial data for models.

Always have the fixtures directory; it helps you to store all your fixed files and data in specific place.


Conclusion:

Hope you’ll enjoy using Django with these discussed concepts and structures.

Also, I’ve prepared a service that could generate better Django projects and web-apps for you. You can check it out here.


Thanks.