23fc1367e8f2c24d7e415230f3b885d659cd58e3
Django/Best Practices for Structuring a Django Project.md
| ... | ... | @@ -0,0 +1,458 @@ |
| 1 | +https://itnext.io/best-practices-for-structuring-a-django-project-23b8c1181e3f |
|
| 2 | + |
|
| 3 | +# Best Practices for Structuring a Django Project | by eyong kevin | ITNEXT |
|
| 4 | +[ |
|
| 5 | + |
|
| 6 | + |
|
| 7 | + |
|
| 8 | + |
|
| 9 | + |
|
| 10 | +](https://medium.com/@tonyparkerkenz?source=post_page---byline--23b8c1181e3f--------------------------------) |
|
| 11 | + |
|
| 12 | +[ |
|
| 13 | + |
|
| 14 | + |
|
| 15 | + |
|
| 16 | + |
|
| 17 | + |
|
| 18 | +](https://itnext.io/?source=post_page---byline--23b8c1181e3f--------------------------------) |
|
| 19 | + |
|
| 20 | +Best Practices for Structuring a Django Project |
|
| 21 | + |
|
| 22 | +In this article, I’ll walk you through a boilerplate I created for my Django projects. It uses best practices and will permit you to build industrial standard Django projects. |
|
| 23 | + |
|
| 24 | +Instead of struggling with your Django project structures. Rather, spend your energy building exciting features. |
|
| 25 | + |
|
| 26 | +> It has received much appreciation from my peers and students from DCI and CF. |
|
| 27 | +> |
|
| 28 | +> **NB**: Check my [Django boilerplate](https://github.com/Eyongkevin/django-boilerplate) from github |
|
| 29 | + |
|
| 30 | +We will cover the following; |
|
| 31 | + |
|
| 32 | +* Starting with a virtual environment |
|
| 33 | +* Creating Django Project |
|
| 34 | +* Requirement |
|
| 35 | +* Settings |
|
| 36 | +* Django Applications |
|
| 37 | +* Command shortcuts with Makefile |
|
| 38 | + |
|
| 39 | +Starting with a virtual environment (VE) |
|
| 40 | +---------------------------------------- |
|
| 41 | + |
|
| 42 | +> A Virtual Environment is a lightweight Python installation with its own package directories |
|
| 43 | + |
|
| 44 | +Installing packages globally usually requires elevated privileges such as `root`, `sudo`, etc. When this is done, the package `setup.py` is executed with superuser privileges. This is a huge security risk if the package contains malware |
|
| 45 | + |
|
| 46 | +Also, the command `pip isntall -U <package>` installs and updates the package and all its dependencies. This may mess with the existing packages or even break your other projects. |
|
| 47 | + |
|
| 48 | +Lastly, installing everything globally pollutes the list of packages, making it hard to keep track of specific project’s dependencies. |
|
| 49 | + |
|
| 50 | +Creating a VE and Install Django |
|
| 51 | +-------------------------------- |
|
| 52 | + |
|
| 53 | +Since `Python 3.6`, we can create VE with the command |
|
| 54 | + |
|
| 55 | +``` |
|
| 56 | +python3 -m venv <folder-name> [--prompt <ve-name>] |
|
| 57 | +``` |
|
| 58 | + |
|
| 59 | + |
|
| 60 | +**NB**: In Ubuntu, we must install `python3-venv` via `apt` . This is because `venv` is excluded from the default Python installation in Ubuntu |
|
| 61 | + |
|
| 62 | +Another alternative would be `virtualenv` which can be installed globally with `pip install virtualenv` |
|
| 63 | + |
|
| 64 | +Let’s now create a virtual environment for our Django project. In a folder called `Django_First` , open a terminal, and run the command |
|
| 65 | + |
|
| 66 | +``` |
|
| 67 | +python -m venv .venv --prompt django-first |
|
| 68 | +``` |
|
| 69 | + |
|
| 70 | + |
|
| 71 | +After we have created our virtual environment, we need to source or activate it before installing any packages. |
|
| 72 | + |
|
| 73 | +``` |
|
| 74 | +source .venv/bin/activate |
|
| 75 | +``` |
|
| 76 | + |
|
| 77 | + |
|
| 78 | +Then, install Django. Here I am using `~=` to install the newest version within the same major release, which is, `5`in the case below. |
|
| 79 | + |
|
| 80 | +``` |
|
| 81 | +pip install Django ~=5.0.0 |
|
| 82 | +``` |
|
| 83 | + |
|
| 84 | + |
|
| 85 | +Creating Django Project |
|
| 86 | +----------------------- |
|
| 87 | + |
|
| 88 | +To create a Django project, we use the command `django-admin` and subcommand `startproject` . Then we specify some folders after that. |
|
| 89 | + |
|
| 90 | +``` |
|
| 91 | +django-admin startproject <project-name> [<config-folder-name>] |
|
| 92 | +``` |
|
| 93 | + |
|
| 94 | + |
|
| 95 | +Say, we want our project name to be `djangofirst` . Many newbies will create it like this |
|
| 96 | + |
|
| 97 | +``` |
|
| 98 | +django-admin startproject djangofirst |
|
| 99 | +``` |
|
| 100 | + |
|
| 101 | + |
|
| 102 | +This will be the structure of your project if you do it as above; |
|
| 103 | + |
|
| 104 | +``` |
|
| 105 | +. |
|
| 106 | +├── Django_First |
|
| 107 | +│ └── djangofirst |
|
| 108 | +│ ├── djangofirst |
|
| 109 | +│ │ ├── __init__.py |
|
| 110 | +│ │ ├── asgi.py |
|
| 111 | +│ │ ├── settings.py |
|
| 112 | +│ │ ├── urls.py |
|
| 113 | +│ │ └── wsgi.py |
|
| 114 | +│ └── manage.py |
|
| 115 | +``` |
|
| 116 | + |
|
| 117 | + |
|
| 118 | +We can notice a lot of indented folders and some with the same name. In this example, we created a folder for our project to be `Django_First` where we created our virtual environment. Then running the Django start project command, we ended up with two folders with the same name. |
|
| 119 | + |
|
| 120 | +**NB**: To specify the name of the configuration folder, we need to be sure that the folder exists already in that directory. |
|
| 121 | + |
|
| 122 | +Best Practice |
|
| 123 | +------------- |
|
| 124 | + |
|
| 125 | +Given that we have created the folder that carries the name of our project, to effectively create a Django project without having all the nested folders, we can run the command |
|
| 126 | + |
|
| 127 | +``` |
|
| 128 | +django-admin startproject config . |
|
| 129 | +``` |
|
| 130 | + |
|
| 131 | + |
|
| 132 | +This will be our new structure |
|
| 133 | + |
|
| 134 | +``` |
|
| 135 | +. |
|
| 136 | +├── Django_First |
|
| 137 | +│ ├── config |
|
| 138 | +│ │ ├── __init__.py |
|
| 139 | +│ │ ├── asgi.py |
|
| 140 | +│ │ ├── settings.py |
|
| 141 | +│ │ ├── urls.py |
|
| 142 | +│ │ └── wsgi.py |
|
| 143 | +│ └── manage.py |
|
| 144 | +``` |
|
| 145 | + |
|
| 146 | + |
|
| 147 | +From this new structure, the initial name of our project is maintained, and the configuration is now `config` . |
|
| 148 | + |
|
| 149 | +Requirements |
|
| 150 | +------------ |
|
| 151 | + |
|
| 152 | +When it’s time to install packages, most developers will search for a package on `pypi` and install the version they want, or they’ll just use `pip install <package-name>` . And the latest version will be installed. Others will have just a single `requirements.txt` file. |
|
| 153 | + |
|
| 154 | +Best Practice |
|
| 155 | +------------- |
|
| 156 | + |
|
| 157 | +If you are careful with backward compatibility and, also aware of the different environments you are running your project in, then you’ll want to make sure everyone running your project is using the right version. Also, unnecessary packages should not be installed in the environments. |
|
| 158 | + |
|
| 159 | +A `requirements.txt` file is important for complex projects so that, anyone can get your project, create their virtual environment, and install all packages with the right version from this file, with the command `pip install -r requirements.txt` . |
|
| 160 | + |
|
| 161 | +Here, we will consider the different environments our project can run in. For example; `development` , `stagging` , `production` , etc. We don’t want a package like `ipython` , which is commonly used during development to be installed in production for example. |
|
| 162 | + |
|
| 163 | +So, we will create the different environments with their specific requirement files and packages. |
|
| 164 | + |
|
| 165 | +* `base.txt` : This will contain packages that are common to all environments |
|
| 166 | +* `dev.txt` : This will contain packages used only in development |
|
| 167 | + |
|
| 168 | +``` |
|
| 169 | +. |
|
| 170 | +├── Django_First |
|
| 171 | +│ ├── config |
|
| 172 | +│ │ ├── __init__.py |
|
| 173 | +│ │ ├── asgi.py |
|
| 174 | +│ │ ├── settings.py |
|
| 175 | +│ │ ├── urls.py |
|
| 176 | +│ │ └── wsgi.py |
|
| 177 | +│ ├── manage.py |
|
| 178 | +│ └── requirements |
|
| 179 | +│ ├── base.txt |
|
| 180 | +│ └── dev.txt |
|
| 181 | +``` |
|
| 182 | + |
|
| 183 | + |
|
| 184 | +Now that we have our structure, let’s look at the packages we could install for each environment. |
|
| 185 | + |
|
| 186 | +``` |
|
| 187 | +# base.txt |
|
| 188 | +Django~=5.0 |
|
| 189 | +django-environ==0.11.2 |
|
| 190 | +# dev.txt |
|
| 191 | +-r base.txt |
|
| 192 | +ipython==8.18.1 |
|
| 193 | +django-extensions==3.2.3 |
|
| 194 | +``` |
|
| 195 | + |
|
| 196 | + |
|
| 197 | +Here, we have |
|
| 198 | + |
|
| 199 | +* `Django` : which is the main framework, |
|
| 200 | +* `django-environ` : to read environment variables |
|
| 201 | +* `ipython` : for a powerful interactive Python shell |
|
| 202 | +* `django_extensions` : To auto-import necessary packages and modules from Django when we start a shell. |
|
| 203 | + |
|
| 204 | +**NB**: Installing from `dev.txt`indirectly install from `base.txt` thanks to the line `-r base.txt` . Also, many other environments can be created like production, etc. Check [Django Web Application to Railway](https://www.youtube.com/watch?v=dtze68tNVbI) to see how I used the production environment to host a Django project on railway. |
|
| 205 | + |
|
| 206 | +Settings |
|
| 207 | +-------- |
|
| 208 | + |
|
| 209 | +By default, Django comes with a file `setting.py`that contains settings for the Django project. This is okay for a very simple Django project. |
|
| 210 | + |
|
| 211 | +Best Practice |
|
| 212 | +------------- |
|
| 213 | + |
|
| 214 | +Just like we did for the requirements above, we want to be sure we can handle many different environments. Plus having configurations for all the environments in one file can’t be scalable. |
|
| 215 | + |
|
| 216 | +Here, we will create a folder called `settings/`then in it, create different Python modules based on the environments. |
|
| 217 | + |
|
| 218 | +``` |
|
| 219 | +. |
|
| 220 | +├── Django_First |
|
| 221 | +│ ├── config |
|
| 222 | +│ │ ├── __init__.py |
|
| 223 | +│ │ ├── asgi.py |
|
| 224 | +│ │ ├── settings |
|
| 225 | +│ │ │ ├── base.py |
|
| 226 | +│ │ │ └── dev.py |
|
| 227 | +│ │ ├── urls.py |
|
| 228 | +│ │ └── wsgi.py |
|
| 229 | +│ ├── manage.py |
|
| 230 | +│ └── requirements |
|
| 231 | +│ ├── base.txt |
|
| 232 | +│ └── dev.txt |
|
| 233 | +``` |
|
| 234 | + |
|
| 235 | + |
|
| 236 | +* `base.py` : This will contain configurations common to all environments |
|
| 237 | +* `dev.py` : This will contain the development configurations |
|
| 238 | + |
|
| 239 | +**base.py** |
|
| 240 | + |
|
| 241 | +``` |
|
| 242 | +from pathlib import Path |
|
| 243 | +BASE_DIR = Path(__file__).resolve().parent.parent.parent |
|
| 244 | +DEFAULT_APP = [ |
|
| 245 | + "django.contrib.admin", |
|
| 246 | + "django.contrib.auth", |
|
| 247 | + "django.contrib.contenttypes", |
|
| 248 | + "django.contrib.sessions", |
|
| 249 | + "django.contrib.messages", |
|
| 250 | + "django.contrib.staticfiles", |
|
| 251 | +] |
|
| 252 | +CREATED_APP = [] # custom apps goes here |
|
| 253 | +THIRD_PARTY_APP = [] # third party apps goes here |
|
| 254 | +INSTALLED_APPS = [*DEFAULT_APP, *CREATED_APP, *THIRD_PARTY_APP] |
|
| 255 | +MIDDLEWARE = [ |
|
| 256 | + "django.middleware.security.SecurityMiddleware", |
|
| 257 | + "django.contrib.sessions.middleware.SessionMiddleware", |
|
| 258 | + "django.middleware.common.CommonMiddleware", |
|
| 259 | + "django.middleware.csrf.CsrfViewMiddleware", |
|
| 260 | + "django.contrib.auth.middleware.AuthenticationMiddleware", |
|
| 261 | + "django.contrib.messages.middleware.MessageMiddleware", |
|
| 262 | + "django.middleware.clickjacking.XFrameOptionsMiddleware", |
|
| 263 | +] |
|
| 264 | +ROOT_URLCONF = "config.urls" |
|
| 265 | +TEMPLATES = [ |
|
| 266 | + { |
|
| 267 | + "BACKEND": "django.template.backends.django.DjangoTemplates", |
|
| 268 | + "DIRS": [], |
|
| 269 | + "APP_DIRS": True, |
|
| 270 | + "OPTIONS": { |
|
| 271 | + "context_processors": [ |
|
| 272 | + "django.template.context_processors.debug", |
|
| 273 | + "django.template.context_processors.request", |
|
| 274 | + "django.contrib.auth.context_processors.auth", |
|
| 275 | + "django.contrib.messages.context_processors.messages", |
|
| 276 | + ], |
|
| 277 | + }, |
|
| 278 | + }, |
|
| 279 | +] |
|
| 280 | +WSGI_APPLICATION = "config.wsgi.application" |
|
| 281 | +AUTH_PASSWORD_VALIDATORS = [ |
|
| 282 | + { |
|
| 283 | + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", |
|
| 284 | + }, |
|
| 285 | + { |
|
| 286 | + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", |
|
| 287 | + }, |
|
| 288 | + { |
|
| 289 | + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", |
|
| 290 | + }, |
|
| 291 | + { |
|
| 292 | + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", |
|
| 293 | + }, |
|
| 294 | +] |
|
| 295 | +LANGUAGE_CODE = "en-us" |
|
| 296 | +TIME_ZONE = "UTC" |
|
| 297 | +USE_I18N = True |
|
| 298 | +USE_TZ = True |
|
| 299 | +STATIC_URL = "static/" |
|
| 300 | +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" |
|
| 301 | +``` |
|
| 302 | + |
|
| 303 | + |
|
| 304 | +**dev.py** |
|
| 305 | + |
|
| 306 | +``` |
|
| 307 | +import environ |
|
| 308 | +from .base import * |
|
| 309 | +env = environ.Env(DEBUG=(bool, True)) |
|
| 310 | +environ.Env.read_env(str(BASE_DIR / ".env")) |
|
| 311 | +SECRET_KEY = env.str("SECRET_KEY") |
|
| 312 | +DEBUG = env.bool("DEBUG") |
|
| 313 | +ALLOWED_HOSTS = ["*"] |
|
| 314 | +THIRD_PARTY_APP = [ |
|
| 315 | + "django_extensions", |
|
| 316 | +] # third party apps goes here |
|
| 317 | +INSTALLED_APPS = INSTALLED_APPS + THIRD_PARTY_APP |
|
| 318 | +# If you want to use postgresql instead, then uncomment this and comment that of sqlite3 bellow. |
|
| 319 | +# DATABASES = { |
|
| 320 | +# "default": { |
|
| 321 | +# "ENGINE": "django.db.backends.postgresql_psycopg2", |
|
| 322 | +# "NAME": env.str("DB_NAME"), |
|
| 323 | +# "USER": env.str("DB_USER"), |
|
| 324 | +# "PASSWORD": env.str("DB_PWD"), |
|
| 325 | +# "HOST": env.str("DB_HOST"), |
|
| 326 | +# "PORT": env.str("DB_PORT"), |
|
| 327 | +# } |
|
| 328 | +# } |
|
| 329 | +DATABASES = { |
|
| 330 | + "default": { |
|
| 331 | + "ENGINE": "django.db.backends.sqlite3", |
|
| 332 | + "NAME": BASE_DIR / "db.sqlite3", |
|
| 333 | + } |
|
| 334 | +} |
|
| 335 | +``` |
|
| 336 | + |
|
| 337 | + |
|
| 338 | +Django Applications |
|
| 339 | +------------------- |
|
| 340 | + |
|
| 341 | +Many developers create their apps in the working directory by running the command |
|
| 342 | + |
|
| 343 | +``` |
|
| 344 | +python3 manage.py startapp <app-name> |
|
| 345 | +``` |
|
| 346 | + |
|
| 347 | + |
|
| 348 | +**NB**: This turns to crowd the working directory. |
|
| 349 | + |
|
| 350 | +Best Practice |
|
| 351 | +------------- |
|
| 352 | + |
|
| 353 | +Here we will create our Django applications in the `apps/`folder. This way, all applications are grouped and easy to find. Also, it makes the working directory less crowded. |
|
| 354 | + |
|
| 355 | +To create a new app, we do the following; |
|
| 356 | + |
|
| 357 | +* Create the `apps/`directory |
|
| 358 | +* cd to the `apps/`directory and run the `startapp` management command |
|
| 359 | + |
|
| 360 | +``` |
|
| 361 | +python3 ../manage.py startapp <app-name> |
|
| 362 | +``` |
|
| 363 | + |
|
| 364 | + |
|
| 365 | +**NB**: After `startapp` , what follows should be the name of the application. It doesn’t support paths. That’s why we have to cd to the `apps/` before running the command, while referring back to the `manage.py` with `../` |
|
| 366 | + |
|
| 367 | +Core App |
|
| 368 | +-------- |
|
| 369 | + |
|
| 370 | +The core application will serve the purpose of containing code that is common to two or more applications. |
|
| 371 | + |
|
| 372 | +For example, it is common for models to share the same fields; `created_at` , `modified_at` . Rather than duplicating these in all the models, we can have them in a separate application and import them where needed. |
|
| 373 | + |
|
| 374 | +**apps/core/abstracts/models.py** |
|
| 375 | + |
|
| 376 | +``` |
|
| 377 | +# apps/core/abstracts/models.py |
|
| 378 | +from django.db import models |
|
| 379 | +class CreatedModifiedAbstract(models.Model): |
|
| 380 | + created_at = models.DateTimeField(auto_now_add=True) |
|
| 381 | + modified_at = models.DateTimeField(auto_now=True) |
|
| 382 | + class Meta: |
|
| 383 | + abstract = True |
|
| 384 | +``` |
|
| 385 | + |
|
| 386 | + |
|
| 387 | +**NB**: The code above defines an abstract model that should be inherited into a model. |
|
| 388 | + |
|
| 389 | +``` |
|
| 390 | +. |
|
| 391 | +├── Django_First |
|
| 392 | +│ ├── apps |
|
| 393 | +│ │ ├── __init__.py |
|
| 394 | +│ │ └── core |
|
| 395 | +│ │ ├── __init__.py |
|
| 396 | +│ │ ├── abstracts |
|
| 397 | +│ │ │ ├── __init__.py |
|
| 398 | +│ │ │ └── models.py |
|
| 399 | +│ │ └── apps.py |
|
| 400 | +│ ├── config |
|
| 401 | +│ │ ├── __init__.py |
|
| 402 | +│ │ ├── asgi.py |
|
| 403 | +│ │ ├── settings |
|
| 404 | +│ │ │ ├── base.py |
|
| 405 | +│ │ │ └── dev.py |
|
| 406 | +│ │ ├── urls.py |
|
| 407 | +│ │ └── wsgi.py |
|
| 408 | +│ ├── manage.py |
|
| 409 | +│ └── requirements |
|
| 410 | +│ ├── base.txt |
|
| 411 | +│ └── dev.txt |
|
| 412 | +``` |
|
| 413 | + |
|
| 414 | + |
|
| 415 | +Command shortcuts with Makefile |
|
| 416 | +------------------------------- |
|
| 417 | + |
|
| 418 | +Now that we have a different structure and, we are able to run our Django project in different environments, we will have lengthy commands because we have to specify the environment we are running in. |
|
| 419 | + |
|
| 420 | +To create shortcuts for our various commands, we can use a `Makefile`. Here are common commands we can create shortcuts for when running in the development environment; |
|
| 421 | + |
|
| 422 | +**Makefile** |
|
| 423 | + |
|
| 424 | +``` |
|
| 425 | +dev-start: |
|
| 426 | + python3 manage.py runserver --settings=config.settings.dev |
|
| 427 | +dev-startapp: |
|
| 428 | + cd apps && python3 ../manage.py startapp $(app) --settings=config.settings.dev |
|
| 429 | +dev-migrate: |
|
| 430 | + python3 manage.py migrate --settings=config.settings.dev |
|
| 431 | +dev-makemigrations: |
|
| 432 | + python3 manage.py makemigrations --settings=config.settings.dev |
|
| 433 | +dev-dbshell: |
|
| 434 | + python3 manage.py dbshell --settings=config.settings.dev |
|
| 435 | +dev-shell: |
|
| 436 | + python3 manage.py shell --settings=config.settings.dev |
|
| 437 | +dev-shell-plus: |
|
| 438 | + python3 manage.py shell_plus --settings=config.settings.dev |
|
| 439 | +dev-install: |
|
| 440 | + pip install -r requirements/dev.txt |
|
| 441 | +dev-test: |
|
| 442 | + python3 manage.py test --settings=config.settings.dev |
|
| 443 | +``` |
|
| 444 | + |
|
| 445 | + |
|
| 446 | +To run a command from the Makefile, we just run `make <command-name>` where `<command-name>` is a shortcut name in the Makefile. For example, to start our Django server, we will run |
|
| 447 | + |
|
| 448 | +``` |
|
| 449 | +make dev-start |
|
| 450 | +``` |
|
| 451 | + |
|
| 452 | + |
|
| 453 | +Conclusion |
|
| 454 | +---------- |
|
| 455 | + |
|
| 456 | +The Django framework is a powerful tool for creating complex web applications. However, many developers struggle with their project’s structure, which slows down productivity. |
|
| 457 | + |
|
| 458 | +This article presented a boilerplate that can be used to create a Django project. It is structured using best practices and makes it possible to scale any Django project. |
|
| ... | ... | \ No newline at end of file |