From fcacdcfe8f4a4e1ad56bc8d303436f2b388d1a36 Mon Sep 17 00:00:00 2001 From: saqibur Date: Tue, 25 Jan 2022 09:15:53 +0600 Subject: [PATCH] Add initial structure --- .gitignore | 154 +++++ README.md | 227 ++++++- apps/app/__init__.py | 0 apps/app/admin.py | 3 + apps/app/api/__init__.py | 0 apps/app/api/v1/__init__.py | 0 apps/app/api/v1/serializers.py | 0 apps/app/api/v1/tests.py | 3 + apps/app/api/v1/urls.py | 0 apps/app/api/v1/views.py | 0 apps/app/api/v2/__init__.py | 0 apps/app/api/v2/serializers.py | 0 apps/app/api/v2/tests.py | 0 apps/app/api/v2/urls.py | 0 apps/app/api/v2/views.py | 2 + apps/app/apps.py | 6 + apps/app/management/__init__.py | 0 apps/app/management/commands.py | 0 apps/app/migrations/__init__.py | 0 apps/app/models.py | 3 + apps/app/urls.py | 0 apps/app/utils.py | 0 apps/app/views.py | 0 apps/second_app/__init__.py | 0 apps/second_app/admin.py | 3 + apps/second_app/apps.py | 6 + apps/second_app/migrations/__init__.py | 0 apps/second_app/models.py | 3 + apps/second_app/tests.py | 3 + apps/second_app/views.py | 3 + config/__init__.py | 0 config/asgi.py | 16 + config/settings.py | 123 ++++ config/settings/__init__.py | 0 config/settings/base.py | 109 ++++ config/settings/development.py | 14 + config/settings/local.py | 14 + config/settings/local_template.py | 14 + config/settings/production.py | 12 + config/urls.py | 21 + config/wsgi.py | 16 + deployments/.env.example | 22 + deployments/django-project/Dockerfile | 0 deployments/django-project/entrypoint.sh | 0 deployments/docker-compose.yml | 0 deployments/nginx/Dockerfile | 0 deployments/nginx/default.conf | 33 + docs/CHANGELOG.md | 0 docs/CONTRIBUTING.md | 0 docs/deployment.md | 0 docs/local-development.md | 0 docs/swagger.yaml | 728 +++++++++++++++++++++++ manage.py | 22 + pytest.ini | 0 requirements/common.txt | 0 requirements/development.txt | 1 + requirements/local.txt | 1 + requirements/production.txt | 1 + 58 files changed, 1561 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 apps/app/__init__.py create mode 100644 apps/app/admin.py create mode 100644 apps/app/api/__init__.py create mode 100644 apps/app/api/v1/__init__.py create mode 100644 apps/app/api/v1/serializers.py create mode 100644 apps/app/api/v1/tests.py create mode 100644 apps/app/api/v1/urls.py create mode 100644 apps/app/api/v1/views.py create mode 100644 apps/app/api/v2/__init__.py create mode 100644 apps/app/api/v2/serializers.py create mode 100644 apps/app/api/v2/tests.py create mode 100644 apps/app/api/v2/urls.py create mode 100644 apps/app/api/v2/views.py create mode 100644 apps/app/apps.py create mode 100644 apps/app/management/__init__.py create mode 100644 apps/app/management/commands.py create mode 100644 apps/app/migrations/__init__.py create mode 100644 apps/app/models.py create mode 100644 apps/app/urls.py create mode 100644 apps/app/utils.py create mode 100644 apps/app/views.py create mode 100644 apps/second_app/__init__.py create mode 100644 apps/second_app/admin.py create mode 100644 apps/second_app/apps.py create mode 100644 apps/second_app/migrations/__init__.py create mode 100644 apps/second_app/models.py create mode 100644 apps/second_app/tests.py create mode 100644 apps/second_app/views.py create mode 100644 config/__init__.py create mode 100644 config/asgi.py create mode 100644 config/settings.py create mode 100644 config/settings/__init__.py create mode 100644 config/settings/base.py create mode 100644 config/settings/development.py create mode 100644 config/settings/local.py create mode 100644 config/settings/local_template.py create mode 100644 config/settings/production.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 deployments/.env.example create mode 100644 deployments/django-project/Dockerfile create mode 100644 deployments/django-project/entrypoint.sh create mode 100644 deployments/docker-compose.yml create mode 100644 deployments/nginx/Dockerfile create mode 100644 deployments/nginx/default.conf create mode 100644 docs/CHANGELOG.md create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/deployment.md create mode 100644 docs/local-development.md create mode 100644 docs/swagger.yaml create mode 100644 manage.py create mode 100644 pytest.ini create mode 100644 requirements/common.txt create mode 100644 requirements/development.txt create mode 100644 requirements/local.txt create mode 100644 requirements/production.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a4f6c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,154 @@ +# Current Project Ignores +config/settings/local.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +# Visual Studio Code +.vscode diff --git a/README.md b/README.md index 65c554b..5c2eaae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,226 @@ -# Django REST Starter Template -A template for getting started with django rest framework projects +# Django Project Structure +This is a template/project structure for developing django-based applications - +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 next big thing your team develops. + +First, we'll define the scope of today's discussion. + +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. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +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 + + + +## 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 +Two Scoops of Django by Daniel and Audrey Feldroy + +Where to Write Business Logic: +* https://stackoverflow.com/questions/57387711/django-rest-framework-where-to-write-complex-logic-in-serializer-py-or-views-py + +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/ diff --git a/apps/app/__init__.py b/apps/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/admin.py b/apps/app/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/app/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/app/api/__init__.py b/apps/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v1/__init__.py b/apps/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v1/serializers.py b/apps/app/api/v1/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v1/tests.py b/apps/app/api/v1/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/app/api/v1/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/app/api/v1/urls.py b/apps/app/api/v1/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v1/views.py b/apps/app/api/v1/views.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v2/__init__.py b/apps/app/api/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v2/serializers.py b/apps/app/api/v2/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v2/tests.py b/apps/app/api/v2/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v2/urls.py b/apps/app/api/v2/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/api/v2/views.py b/apps/app/api/v2/views.py new file mode 100644 index 0000000..d3ab8cf --- /dev/null +++ b/apps/app/api/v2/views.py @@ -0,0 +1,2 @@ +APIView +def does one thing \ No newline at end of file diff --git a/apps/app/apps.py b/apps/app/apps.py new file mode 100644 index 0000000..ed327d2 --- /dev/null +++ b/apps/app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app' diff --git a/apps/app/management/__init__.py b/apps/app/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/management/commands.py b/apps/app/management/commands.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/migrations/__init__.py b/apps/app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/models.py b/apps/app/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/app/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/app/urls.py b/apps/app/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/utils.py b/apps/app/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/views.py b/apps/app/views.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/second_app/__init__.py b/apps/second_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/second_app/admin.py b/apps/second_app/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/second_app/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/second_app/apps.py b/apps/second_app/apps.py new file mode 100644 index 0000000..73ac591 --- /dev/null +++ b/apps/second_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'second_app' diff --git a/apps/second_app/migrations/__init__.py b/apps/second_app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/second_app/models.py b/apps/second_app/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/second_app/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/second_app/tests.py b/apps/second_app/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/second_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/second_app/views.py b/apps/second_app/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/second_app/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..f2191c2 --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for config project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_asgi_application() diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..496e6a4 --- /dev/null +++ b/config/settings.py @@ -0,0 +1,123 @@ +""" +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' diff --git a/config/settings/__init__.py b/config/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/base.py b/config/settings/base.py new file mode 100644 index 0000000..616cada --- /dev/null +++ b/config/settings/base.py @@ -0,0 +1,109 @@ +""" +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' + +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' + + +# 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' diff --git a/config/settings/development.py b/config/settings/development.py new file mode 100644 index 0000000..592ed60 --- /dev/null +++ b/config/settings/development.py @@ -0,0 +1,14 @@ +from .base import * + +DEBUG = True + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'DB_NAME', + 'USER': 'DB_USER', + 'PASSWORD': 'DB_PASS', + 'HOST': 'DB_HOST', + 'PORT': '5432', + } +} diff --git a/config/settings/local.py b/config/settings/local.py new file mode 100644 index 0000000..592ed60 --- /dev/null +++ b/config/settings/local.py @@ -0,0 +1,14 @@ +from .base import * + +DEBUG = True + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'DB_NAME', + 'USER': 'DB_USER', + 'PASSWORD': 'DB_PASS', + 'HOST': 'DB_HOST', + 'PORT': '5432', + } +} diff --git a/config/settings/local_template.py b/config/settings/local_template.py new file mode 100644 index 0000000..592ed60 --- /dev/null +++ b/config/settings/local_template.py @@ -0,0 +1,14 @@ +from .base import * + +DEBUG = True + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'DB_NAME', + 'USER': 'DB_USER', + 'PASSWORD': 'DB_PASS', + 'HOST': 'DB_HOST', + 'PORT': '5432', + } +} diff --git a/config/settings/production.py b/config/settings/production.py new file mode 100644 index 0000000..be69680 --- /dev/null +++ b/config/settings/production.py @@ -0,0 +1,12 @@ +from .base import * + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'DB_NAME', + 'USER': 'DB_USER', + 'PASSWORD': 'DB_PASS', + 'HOST': 'DB_HOST', + 'PORT': '5432', + } +} diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..f997b89 --- /dev/null +++ b/config/urls.py @@ -0,0 +1,21 @@ +"""config URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..08031f8 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for config project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_wsgi_application() diff --git a/deployments/.env.example b/deployments/.env.example new file mode 100644 index 0000000..b501a15 --- /dev/null +++ b/deployments/.env.example @@ -0,0 +1,22 @@ +# README +# 1. No spaces before or after `=`. +# 2. Don't use quotations around strings, they're counted as strings by default. +# 3. Change values according to your environment. +# 4. Docker will automatically take all values from this file automatically. +# 5. If values exist in the environment, it will not take values from this file. +# 6. Rename this file from `.env.example` to `.env` + + +SECRET_KEY=django-insecure-$227hjjmuq2e!)o^@2v#+(-=@$v362o@8g#s9!2)tjn1)1a +DEBUG=True +ALLOWED_HOSTS=* +ENV_NAME=DEV + +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=example_db +DB_USERNAME=postgres +DB_PASSWORD=12345678 + +# Super user +SUPERUSER_EMAIL=superuser@example.com diff --git a/deployments/django-project/Dockerfile b/deployments/django-project/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/deployments/django-project/entrypoint.sh b/deployments/django-project/entrypoint.sh new file mode 100644 index 0000000..e69de29 diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/deployments/nginx/Dockerfile b/deployments/nginx/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/deployments/nginx/default.conf b/deployments/nginx/default.conf new file mode 100644 index 0000000..b2ab801 --- /dev/null +++ b/deployments/nginx/default.conf @@ -0,0 +1,33 @@ +# Gunicorn server +upstream django { + server domain.com:9000; +} + +# Redirect all requests on the www subdomain to the root domain +server { + listen 80; + server_name www.domain.com; + rewrite ^/(.*) http://domain.com/$1 permanent; +} + +# Serve static files and redirect any other request to Gunicorn +server { + listen 80; + server_name domain.com; + root /var/www/domain.com/; + access_log /var/log/nginx/domain.com.access.log; + error_log /var/log/nginx/domain.com.error.log; + + # Check if a file exists at /var/www/domain/ for the incoming request. + # If it doesn't proxy to Gunicorn/Django. + try_files $uri @django; + + # Setup named location for Django requests and handle proxy details + location @django { + proxy_pass http://django; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..83ccdf1 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,728 @@ +openapi: 3.0.1 +info: + title: Swagger Petstore + description: 'This is a sample server Petstore server. You can find out more about Swagger + at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For + this sample, you can use the api key `special-key` to test the authorization filters.' + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: +- url: https://petstore.swagger.io/v2 +- url: http://petstore.swagger.io/v2 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + operationId: updatePet + requestBody: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + 400: + description: Invalid ID supplied + content: {} + 404: + description: Pet not found + content: {} + 405: + description: Validation exception + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + x-codegen-request-body-name: body + post: + tags: + - pet + summary: Add a new pet to the store + operationId: addPet + requestBody: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + 405: + description: Invalid input + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + x-codegen-request-body-name: body + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: true + schema: + type: array + items: + type: string + default: available + enum: + - available + - pending + - sold + responses: + 200: + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid status value + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: Muliple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: true + schema: + type: array + items: + type: string + responses: + 200: + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid tag value + content: {} + deprecated: true + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid ID supplied + content: {} + 404: + description: Pet not found + content: {} + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + type: string + description: Updated name of the pet + status: + type: string + description: Updated status of the pet + responses: + 405: + description: Invalid input + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + operationId: deletePet + parameters: + - name: api_key + in: header + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + 400: + description: Invalid ID supplied + content: {} + 404: + description: Pet not found + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + requestBody: + content: + multipart/form-data: + schema: + properties: + additionalMetadata: + type: string + description: Additional data to pass to server + file: + type: string + description: file to upload + format: binary + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + 200: + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + operationId: placeOrder + requestBody: + description: order placed for purchasing the pet + content: + '*/*': + schema: + $ref: '#/components/schemas/Order' + required: true + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + 400: + description: Invalid Order + content: {} + x-codegen-request-body-name: body + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value >= 1 and <= 10. Other + values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + maximum: 10.0 + minimum: 1.0 + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + 400: + description: Invalid ID supplied + content: {} + 404: + description: Order not found + content: {} + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with positive integer value. Negative + or non-integer values will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + minimum: 1.0 + type: integer + format: int64 + responses: + 400: + description: Invalid ID supplied + content: {} + 404: + description: Order not found + content: {} + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + content: {} + x-codegen-request-body-name: body + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + operationId: createUsersWithArrayInput + requestBody: + description: List of user object + content: + '*/*': + schema: + type: array + items: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + content: {} + x-codegen-request-body-name: body + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + description: List of user object + content: + '*/*': + schema: + type: array + items: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + content: {} + x-codegen-request-body-name: body + /user/login: + get: + tags: + - user + summary: Logs user into the system + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + 200: + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + 400: + description: Invalid username/password supplied + content: {} + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + operationId: logoutUser + responses: + default: + description: successful operation + content: {} + /user/{username}: + get: + tags: + - user + summary: Get user by user name + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + 400: + description: Invalid username supplied + content: {} + 404: + description: User not found + content: {} + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be updated + required: true + schema: + type: string + requestBody: + description: Updated user object + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + required: true + responses: + 400: + description: Invalid user supplied + content: {} + 404: + description: User not found + content: {} + x-codegen-request-body-name: body + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + 400: + description: Invalid username supplied + content: {} + 404: + description: User not found + content: {} +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + description: User Status + format: int32 + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..8e7ac79 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e69de29 diff --git a/requirements/common.txt b/requirements/common.txt new file mode 100644 index 0000000..e69de29 diff --git a/requirements/development.txt b/requirements/development.txt new file mode 100644 index 0000000..c3899b0 --- /dev/null +++ b/requirements/development.txt @@ -0,0 +1 @@ +-r common.txt \ No newline at end of file diff --git a/requirements/local.txt b/requirements/local.txt new file mode 100644 index 0000000..c3899b0 --- /dev/null +++ b/requirements/local.txt @@ -0,0 +1 @@ +-r common.txt \ No newline at end of file diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100644 index 0000000..c3899b0 --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1 @@ +-r common.txt \ No newline at end of file