Fix project tree
This commit is contained in:
parent
3f16a3f107
commit
9dfa5b7154
312
README.md
312
README.md
|
@ -3,224 +3,110 @@ This is a template/project structure for developing django-based applications -
|
||||||
either strictly through the `django-rest-framework` or just `django`.
|
either strictly through the `django-rest-framework` or just `django`.
|
||||||
|
|
||||||
The project is meant to be easily clonable, and used as the starter template for
|
The project is meant to be easily clonable, and used as the starter template for
|
||||||
the next big thing your team develops.
|
the next big thing our team develops.
|
||||||
|
|
||||||
First, we'll define the scope of today's discussion.
|
## Project Tree
|
||||||
|
```bash
|
||||||
|
.
|
||||||
|
├── apps
|
||||||
|
│ ├── app
|
||||||
|
│ │ ├── api
|
||||||
|
│ │ │ ├── v1
|
||||||
|
│ │ │ │ ├── __init__.py
|
||||||
|
│ │ │ │ ├── serializers.py
|
||||||
|
│ │ │ │ ├── services.py
|
||||||
|
│ │ │ │ ├── tests.py
|
||||||
|
│ │ │ │ ├── urls.py
|
||||||
|
│ │ │ │ └── views.py
|
||||||
|
│ │ │ ├── v2
|
||||||
|
│ │ │ │ ├── __init__.py
|
||||||
|
│ │ │ │ ├── serializers.py
|
||||||
|
│ │ │ │ ├── services.py
|
||||||
|
│ │ │ │ ├── tests.py
|
||||||
|
│ │ │ │ ├── urls.py
|
||||||
|
│ │ │ │ └── views.py
|
||||||
|
│ │ │ └── __init__.py
|
||||||
|
│ │ ├── management
|
||||||
|
│ │ │ ├── commands.py
|
||||||
|
│ │ │ └── __init__.py
|
||||||
|
│ │ ├── migrations
|
||||||
|
│ │ │ └── __init__.py
|
||||||
|
│ │ ├── templates
|
||||||
|
│ │ ├── tests
|
||||||
|
│ │ ├── admin.py
|
||||||
|
│ │ ├── apps.py
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── models.py
|
||||||
|
│ │ ├── urls.py
|
||||||
|
│ │ ├── utils.py
|
||||||
|
│ │ └── views.py
|
||||||
|
│ └── second_app
|
||||||
|
│ ├── migrations
|
||||||
|
│ │ └── __init__.py
|
||||||
|
│ ├── templates
|
||||||
|
│ ├── admin.py
|
||||||
|
│ ├── apps.py
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── models.py
|
||||||
|
│ ├── service.py
|
||||||
|
│ ├── tests.py
|
||||||
|
│ └── views.py
|
||||||
|
├── config
|
||||||
|
│ ├── settings
|
||||||
|
│ │ ├── base.py
|
||||||
|
│ │ ├── development.py
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── local.py
|
||||||
|
│ │ ├── local_template.py
|
||||||
|
│ │ └── production.py
|
||||||
|
│ ├── asgi.py
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── urls.py
|
||||||
|
│ └── wsgi.py
|
||||||
|
├── deployments
|
||||||
|
│ ├── django-project
|
||||||
|
│ │ └── Dockerfile
|
||||||
|
│ ├── nginx
|
||||||
|
│ │ ├── default.conf
|
||||||
|
│ │ └── Dockerfile
|
||||||
|
│ └── docker-compose.yml
|
||||||
|
├── docs
|
||||||
|
│ ├── CHANGELOG.md
|
||||||
|
│ ├── CONTRIBUTING.md
|
||||||
|
│ ├── deployment.md
|
||||||
|
│ ├── local-development.md
|
||||||
|
│ └── swagger.yaml
|
||||||
|
├── requirements
|
||||||
|
│ ├── common.txt
|
||||||
|
│ ├── development.txt
|
||||||
|
│ ├── local.txt
|
||||||
|
│ └── production.txt
|
||||||
|
├── static
|
||||||
|
├── entrypoint.sh
|
||||||
|
├── manage.py
|
||||||
|
├── pytest.ini
|
||||||
|
└── README.md
|
||||||
|
|
||||||
We're here to talk about a long-standing project structure - not best practices. We'll deal with those later.
|
|
||||||
|
|
||||||
However, a lot of these decisions we're based on some accepted best practices. I'll be listing all my reference material at the presentation.
|
|
||||||
|
|
||||||
Additionally, I will not be taking questions or discussions today. Instead I ask that everyone takes detailed notes, and raises issues in the GitHub repository, and offers their contributions there. While I understand this adds extra steps for suggestions, it also serves as a place to concretely discuss improvements and offer detailed suggestions and examples in written form instead of verbal assumptions.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Debugging and Tooling
|
|
||||||
* Add Silk
|
|
||||||
* Add Django Debug Toolbar
|
|
||||||
|
|
||||||
|
|
||||||
## Coding Style:
|
|
||||||
* https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
|
||||||
|
|
||||||
## Instructions:
|
|
||||||
* Should we add a note for when we add new imports?
|
|
||||||
|
|
||||||
# Design Principles
|
|
||||||
* Each application should be designed in a way to be pluggable - dragged and
|
|
||||||
dropped into any other project and it'll still work independently.
|
|
||||||
|
|
||||||
# Code Checking
|
|
||||||
* We'll use mypy for static type checking
|
|
||||||
|
|
||||||
# Code Formatting
|
|
||||||
* We'll use black as our auto-formatter
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
* We'll use pytest for testing
|
|
||||||
|
|
||||||
Disclaimer: I don't have 10 years of experience, nor do I have access to people
|
|
||||||
with 10 years of experience. What I do have is good reference material - books,
|
|
||||||
conferences, and documentation. These people are smarter than me, they are better
|
|
||||||
developers and they have more experience - I'm somewhat collecting and presenting
|
|
||||||
what they do.
|
|
||||||
|
|
||||||
|
|
||||||
Mani na
|
|
||||||
|
|
||||||
# Starting
|
|
||||||
Defining the scope of our projects
|
|
||||||
We need a project structure that is;
|
|
||||||
- Homogeneous across strativ applications
|
|
||||||
- Can be used to build Django Rest APIs and also support Django Templates
|
|
||||||
- We're limiting ourselves strictly to Django because there are developer
|
|
||||||
expectations - for instance, if you're a Django developer, you'll expect the
|
|
||||||
project to have a settings.py, a manage.py, etc.
|
|
||||||
|
|
||||||
What this allows -
|
|
||||||
When anyone visits this project, they are provided with a high-level view of the
|
|
||||||
project. We've found that this allows us to work easily with other developers
|
|
||||||
and even non-developers.
|
|
||||||
|
|
||||||
|
|
||||||
Please observe the following:
|
|
||||||
➤ We like to place all our API components into a package within an app called api/.
|
|
||||||
That allows us to isolate our API components in a consistent location. If we were to
|
|
||||||
put it in the root of our app, then we would end up with a huge list of API-specific
|
|
||||||
modules in the general area of the app.
|
|
||||||
➤ Viewsets belong in their own module.
|
|
||||||
➤ We always place routers in urls.py. Either at the app or project level, routers belong
|
|
||||||
in urls.py.
|
|
||||||
|
|
||||||
|
|
||||||
REST Framework Decisions:
|
|
||||||
For projects with a lot of small, interconnecting apps, it can be hard to hunt
|
|
||||||
down where a particular API view lives. In contrast to placing all API code
|
|
||||||
within each relevant app, sometimes it makes more sense to build an app
|
|
||||||
specifically for the API. This is where all the serializers, renderers, and views
|
|
||||||
are placed. Therefore, the name of the app should reflect its API version (see
|
|
||||||
Section 17.3.7: Version Your API).
|
|
||||||
|
|
||||||
For example, we might place all our views, serializers, and other API
|
|
||||||
components in an app titled apiv4. The downside is the possibility for the API
|
|
||||||
app to become too large and disconnected from the apps that power it. Hence we
|
|
||||||
consider an alternative in the next subsection.
|
|
||||||
|
|
||||||
|
|
||||||
# Zen of Python
|
|
||||||
```
|
```
|
||||||
Beautiful is better than ugly.
|
## Rationale
|
||||||
Explicit is better than implicit.
|
### `apps`
|
||||||
Simple is better than complex.
|
* A mother-folder containing all apps for our project.
|
||||||
Complex is better than complicated.
|
* An app can be a django template project, or an api
|
||||||
Flat is better than nested.
|
|
||||||
Sparse is better than dense.
|
|
||||||
Readability counts.
|
|
||||||
Special cases aren't special enough to break the rules.
|
|
||||||
Although practicality beats purity.
|
|
||||||
Errors should never pass silently.
|
|
||||||
Unless explicitly silenced.
|
|
||||||
In the face of ambiguity, refuse the temptation to guess.
|
|
||||||
There should be one-- and preferably only one --obvious way to do it.
|
|
||||||
Although that way may not be obvious at first unless you're Dutch.
|
|
||||||
Now is better than never.
|
|
||||||
Although never is often better than *right* now.
|
|
||||||
If the implementation is hard to explain, it's a bad idea.
|
|
||||||
If the implementation is easy to explain, it may be a good idea.
|
|
||||||
Namespaces are one honking great idea -- let's do more of those!
|
|
||||||
```
|
|
||||||
|
|
||||||
## Git Ignore
|
|
||||||
|
|
||||||
https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
|
||||||
|
|
||||||
Git Ignore:
|
|
||||||
We're gonna follow ->
|
|
||||||
https://github.com/github/gitignore/blob/main/Python.gitignore
|
|
||||||
|
|
||||||
With or without the nuclear option.
|
|
||||||
https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
|
|
||||||
|
|
||||||
|
### `config`
|
||||||
|
* Contains project configuration files, including the primary URL file
|
||||||
|
* Contains settings split into `base`, `local`, `production` and `development`
|
||||||
|
|
||||||
|
|
||||||
|
### `deployments`
|
||||||
|
* Contains Docker and nginx specific files for deploying in different
|
||||||
|
environments
|
||||||
|
|
||||||
## Tree
|
|
||||||
```
|
|
||||||
│ .gitignore
|
|
||||||
│ Dockerfile
|
|
||||||
│ entrypoint.sh
|
|
||||||
│ manage.py
|
|
||||||
│ nginx.conf
|
|
||||||
│ README.md
|
|
||||||
│
|
|
||||||
├───app
|
|
||||||
│ │ admin.py
|
|
||||||
│ │ apps.py
|
|
||||||
│ │ models.py
|
|
||||||
│ │ urls.py
|
|
||||||
│ │ utils.py
|
|
||||||
│ │ __init__.py
|
|
||||||
│ │
|
|
||||||
│ ├───api
|
|
||||||
│ │ │ __init__.py
|
|
||||||
│ │ │
|
|
||||||
│ │ ├───v1
|
|
||||||
│ │ │ serializers.py
|
|
||||||
│ │ │ tests.py
|
|
||||||
│ │ │ urls.py
|
|
||||||
│ │ │ views.py
|
|
||||||
│ │ │ viewsets.py
|
|
||||||
│ │ │ __init__.py
|
|
||||||
│ │ │
|
|
||||||
│ │ └───v2
|
|
||||||
│ │ serializers.py
|
|
||||||
│ │ tests.py
|
|
||||||
│ │ urls.py
|
|
||||||
│ │ views.py
|
|
||||||
│ │ viewsets.py
|
|
||||||
│ │ __init__.py
|
|
||||||
│ │
|
|
||||||
│ ├───management
|
|
||||||
│ │ commands.py
|
|
||||||
│ │ __init__.py
|
|
||||||
│ │
|
|
||||||
│ ├───migrations
|
|
||||||
│ │ __init__.py
|
|
||||||
│ │
|
|
||||||
│ └───templates
|
|
||||||
├───config
|
|
||||||
│ │ asgi.py
|
|
||||||
│ │ settings.py
|
|
||||||
│ │ urls.py
|
|
||||||
│ │ wsgi.py
|
|
||||||
│ │ __init__.py
|
|
||||||
│ │
|
|
||||||
│ └───settings
|
|
||||||
│ base.py
|
|
||||||
│ development.py
|
|
||||||
│ local.py
|
|
||||||
│ local_template.py
|
|
||||||
│ production.py
|
|
||||||
│
|
|
||||||
├───core
|
|
||||||
│ │ admin.py
|
|
||||||
│ │ apps.py
|
|
||||||
│ │ models.py
|
|
||||||
│ │ tests.py
|
|
||||||
│ │ views.py
|
|
||||||
│ │ __init__.py
|
|
||||||
│ │
|
|
||||||
│ └───migrations
|
|
||||||
│ __init__.py
|
|
||||||
│
|
|
||||||
├───docs
|
|
||||||
│ CHANGELOG.md
|
|
||||||
│ CONTRIBUTING.md
|
|
||||||
│ LOCAL_DEVELOPMENT.md
|
|
||||||
│ PRODUCTION_DEPLOYMENT.md
|
|
||||||
│ swagger.yaml
|
|
||||||
│
|
|
||||||
├───requirements
|
|
||||||
│ common.txt
|
|
||||||
│ development.txt
|
|
||||||
│ local.txt
|
|
||||||
│ production.txt
|
|
||||||
│
|
|
||||||
├───static
|
|
||||||
└───templates
|
|
||||||
```
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
Two Scoops of Django by Daniel and Audrey Feldroy
|
- [Two Scoops of Django by Daniel and Audrey Feldroy](https://www.feldroy.com/books/two-scoops-of-django-3-x)
|
||||||
|
- [Django Best Practices](https://django-best-practices.readthedocs.io/en/latest/index.html)
|
||||||
Where to Write Business Logic:
|
- [Cookiecutter Django](https://github.com/cookiecutter/cookiecutter-django)
|
||||||
* https://stackoverflow.com/questions/57387711/django-rest-framework-where-to-write-complex-logic-in-serializer-py-or-views-py
|
- [HackSoft Django Style Guide](https://github.com/HackSoftware/Django-Styleguide)
|
||||||
|
- [Radoslav Georgiev - Django Structure for Scale and Longevity](https://www.youtube.com/watch?v=yG3ZdxBb1oo)
|
||||||
Django Best Practices:
|
|
||||||
* https://django-best-practices.readthedocs.io/en/latest/index.html
|
|
||||||
|
|
||||||
Reference:
|
|
||||||
* https://github.com/cookiecutter/cookiecutter-django
|
|
||||||
|
|
||||||
|
|
||||||
## TODO
|
|
||||||
* Add versioning for swagger documentation - https://editor.swagger.io/
|
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for config project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 4.0.1.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.0/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/4.0/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'django-insecure-$227hjjmuq2e!)o^@2v#+(-=@$v362o@8g#s9!2)tjn1)1a'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'config.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'config.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
Loading…
Reference in New Issue