├── .gitignore ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── data └── nginx │ ├── conf.d │ └── nginx.conf │ └── conf_ssl.d │ └── nginx.conf ├── django_docker_setup ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── settings.cpython-37.pyc ├── settings.py ├── settings_local.py ├── urls.py └── wsgi.py ├── docker-compose.prod.ssl.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── init-letsencrypt.sh ├── manage.py ├── requirements_dev.txt ├── requirements_prod.txt └── sample_app ├── __init__.py ├── admin.py ├── apps.py ├── migrations └── __init__.py ├── models.py ├── tests.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | data/postgres 2 | data/certbot 3 | .vscode/ 4 | .idea/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | # environment varibles 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | ENV app_port 8000 7 | 8 | COPY . /code/ 9 | WORKDIR /code 10 | RUN pip install -r requirements_dev.txt 11 | 12 | EXPOSE ${app_port} 13 | 14 | CMD ["python3", "manage.py", "runserver"] -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | # environment varibles 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | ENV app_port 8000 7 | 8 | COPY . /code/ 9 | WORKDIR /code 10 | RUN pip install -r requirements_prod.txt 11 | 12 | EXPOSE ${app_port} 13 | 14 | CMD ["python3", "manage.py", "runserver"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Docker compose project base with Django , PosgreSQL , Nginx , Gunicorn and Certbot. 2 | 3 | You can use this setup in both your development and production environments. Development setup uses python's webserver. Production setup uses gunicorn and nginx. 4 | 5 | # Setup Project 6 | 7 | Before starting to run this project ***docker*** and ***docker-compose*** should be installed on your computer. 8 | 9 | ## Installation 10 | 11 | - Docker Installation: https://docs.docker.com/v17.12/install/ 12 | - Docker Compose Installation: https://docs.docker.com/compose/install/ 13 | 14 | ## Configurations 15 | 16 | - Replace ***django_docker_setup*** your project's name 17 | - Replace ***sample_app*** with your app's name 18 | - Replace ***sample_database_name*** with your database name 19 | - Replace ***example.com*** domain names in *data/nginx/conf_ssl.d/nginx.conf* and *init-letsencrypt* folders with your domain name(s). 20 | 21 | You can replace these names by using search and replace feature in your IDE or editor. 22 | 23 | ## Run Project 24 | 25 | ### Run project for development environment 26 | 27 | ``` 28 | docker-compose up 29 | ``` 30 | 31 | ### Run project for production environment without Certbot 32 | 33 | **In case you don't have domain or you don't want to get SSL certificates for your domain(s) yet**,run the command below. 34 | 35 | ``` 36 | docker-compose -f docker-compose.prod.yml up 37 | ``` 38 | 39 | ### Run project for production environment with Certbot 40 | 41 | You can get your SSL certificates from Let's Encrypt by running *init-letsencrypt.sh* script. 42 | 43 | First make the script executable by command below, 44 | 45 | ``` 46 | chmod u+x init-letsencrypt.sh 47 | ``` 48 | 49 | Then run the script, 50 | 51 | ``` 52 | ./init-letsencrypt.sh 53 | ``` 54 | 55 | This script will also start your containers. In case you down your containers, you can restart them by following command, 56 | 57 | ``` 58 | docker-compose -f docker-compose.prod.ssl up 59 | ``` -------------------------------------------------------------------------------- /data/nginx/conf.d/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream project_name { 2 | server web:8000; 3 | } 4 | 5 | server { 6 | 7 | listen 80; 8 | 9 | location / { 10 | proxy_pass http://project_name; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | proxy_set_header Host $host; 13 | proxy_redirect off; 14 | } 15 | 16 | # serve static files 17 | location /static/ { 18 | alias /static/; 19 | } 20 | 21 | # serve media files 22 | location /media/ { 23 | alias /media/; 24 | } 25 | } -------------------------------------------------------------------------------- /data/nginx/conf_ssl.d/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream website { 2 | server web:8000; 3 | } 4 | 5 | server { 6 | listen 80; 7 | server_tokens off; 8 | server_name *.example.com example.com; 9 | 10 | 11 | location /.well-known/acme-challenge/ { 12 | root /var/www/certbot; 13 | } 14 | 15 | location / { 16 | return 301 https://$host$request_uri; 17 | } 18 | } 19 | 20 | 21 | 22 | server { 23 | listen 443 ssl; 24 | server_name *.example.com example.com; 25 | server_tokens off; 26 | 27 | 28 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 29 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 30 | include /etc/letsencrypt/options-ssl-nginx.conf; 31 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 32 | 33 | 34 | # serve static files 35 | location /static/ { 36 | alias /static/; 37 | } 38 | 39 | # serve media files 40 | location /media/ { 41 | alias /media/; 42 | } 43 | 44 | 45 | location / { 46 | proxy_pass http://website; 47 | proxy_set_header Host $http_host; 48 | proxy_set_header X-Real-IP $remote_addr; 49 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 50 | } 51 | } -------------------------------------------------------------------------------- /django_docker_setup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ermissa/django-docker-setup/504f24c7297d651a773a6ea0cbedb7a897899579/django_docker_setup/__init__.py -------------------------------------------------------------------------------- /django_docker_setup/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ermissa/django-docker-setup/504f24c7297d651a773a6ea0cbedb7a897899579/django_docker_setup/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /django_docker_setup/__pycache__/settings.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ermissa/django-docker-setup/504f24c7297d651a773a6ea0cbedb7a897899579/django_docker_setup/__pycache__/settings.cpython-37.pyc -------------------------------------------------------------------------------- /django_docker_setup/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_docker_setup project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.15. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '96sxg)y-+ou0^d_5776a7rzitxtm+&5xcrqngns6y#+#fxxzdi' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'sample_app' 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'django_docker_setup.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'django_docker_setup.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE':'django.db.backends.postgresql', 80 | 'NAME':'sample_database_name', 81 | 'USER':'postgres', 82 | 'PASSWORD':'1234', 83 | 'PORT' : 5432, 84 | 'HOST': 'db', 85 | 'ATOMIC_REQUESTS': True, 86 | }, 87 | } 88 | 89 | 90 | # Password validation 91 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 92 | 93 | AUTH_PASSWORD_VALIDATORS = [ 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 105 | }, 106 | ] 107 | 108 | 109 | # Internationalization 110 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 111 | 112 | LANGUAGE_CODE = 'en-us' 113 | 114 | TIME_ZONE = 'UTC' 115 | 116 | USE_I18N = True 117 | 118 | USE_L10N = True 119 | 120 | USE_TZ = True 121 | 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 125 | 126 | STATIC_URL = '/static/' 127 | 128 | #Asagidaki ifade django'ya,belirli bir app'e bagli olmayan static dosyalar 129 | #icin nereye bakması gerektigini soyluyor 130 | STATICFILES_DIRS = ( 131 | os.path.join(BASE_DIR, 'static'), 132 | ) 133 | 134 | #asagidaki satir collectstatic komutu calistirildiginda statik dosyalarin 135 | #nereye toplanacagini belirtiyor.Bu ifadeye gore app'lerin disinda staticfiles 136 | #diye bir klasore toplanacak. 137 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 138 | -------------------------------------------------------------------------------- /django_docker_setup/settings_local.py: -------------------------------------------------------------------------------- 1 | # pull in the normal settings 2 | from django_docker_setup.settings import * 3 | 4 | # no debug for us 5 | DEBUG = True 6 | ALLOWED_HOSTS = ['*'] 7 | -------------------------------------------------------------------------------- /django_docker_setup/urls.py: -------------------------------------------------------------------------------- 1 | """django_docker_setup URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /django_docker_setup/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_docker_setup project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_docker_setup.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /docker-compose.prod.ssl.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: postgres 6 | container_name: postgres-db-prod 7 | environment: 8 | - POSTGRES_DB=sample_database_name 9 | - POSTGRES_USER=postgres 10 | - POSTGRES_PASSWORD=1234 11 | - POSTGRES_HOST=127.0.0.1 12 | volumes: 13 | - ./data/postgres/data:/var/lib/postgresql/data 14 | 15 | 16 | nginx: 17 | image: nginx:1.15-alpine 18 | restart: unless-stopped 19 | volumes: 20 | - ./data/nginx/conf_ssl.d:/etc/nginx/conf.d 21 | - ./data/certbot/conf:/etc/letsencrypt 22 | - ./data/certbot/www:/var/www/certbot 23 | - ./static:/static 24 | - ./mediafiles:/media 25 | command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" 26 | ports: 27 | - "80:80" 28 | - "443:443" 29 | depends_on: 30 | - web 31 | 32 | 33 | certbot: 34 | image: certbot/certbot 35 | restart: unless-stopped 36 | volumes: 37 | - ./data/certbot/conf:/etc/letsencrypt 38 | - ./data/certbot/www:/var/www/certbot 39 | entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 5d & wait $${!}; done;'" 40 | 41 | 42 | web: 43 | build: 44 | context: ./ 45 | dockerfile: Dockerfile.prod 46 | container_name: django-web-prod 47 | restart: on-failure 48 | volumes: 49 | - .:/code 50 | command: bash -c "python manage.py makemigrations sample_app --noinput && python manage.py migrate --noinput && gunicorn django_docker_setup.wsgi:application --bind 0.0.0.0:8000" 51 | ports: 52 | - "8000:8000" 53 | depends_on: 54 | - db 55 | 56 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: postgres 6 | container_name: postgres-db-prod 7 | environment: 8 | - POSTGRES_DB=sample_database_name 9 | - POSTGRES_USER=postgres 10 | - POSTGRES_PASSWORD=1234 11 | - POSTGRES_HOST=127.0.0.1 12 | volumes: 13 | - ./data/postgres/data:/var/lib/postgresql/data 14 | 15 | nginx: 16 | image: "nginx" 17 | restart: always 18 | volumes: 19 | - ./data/nginx/conf.d:/etc/nginx/conf.d 20 | - ./static:/static 21 | - ./mediafiles:/media 22 | ports: 23 | - "80:80" 24 | depends_on: 25 | - web 26 | 27 | web: 28 | build: 29 | context: ./ 30 | dockerfile: Dockerfile.prod 31 | container_name: django-web-prod 32 | restart: on-failure 33 | volumes: 34 | - .:/code 35 | command: bash -c "python manage.py makemigrations sample_app --noinput && python manage.py migrate --noinput && gunicorn django_docker_setup.wsgi:application --bind 0.0.0.0:8000" 36 | ports: 37 | - "8000:8000" 38 | depends_on: 39 | - db 40 | 41 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | db: 5 | image: postgres 6 | environment: 7 | - POSTGRES_DB=sample_database_name 8 | - POSTGRES_USER=postgres 9 | - POSTGRES_PASSWORD=1234 10 | - POSTGRES_HOST=127.0.0.1 11 | ports: 12 | - "5432:5432" 13 | volumes: 14 | - ./data/postgres/data:/var/lib/postgresql/data 15 | 16 | web: 17 | build: . 18 | restart: on-failure 19 | volumes: 20 | - .:/code 21 | command: bash -c "python manage.py makemigrations sample_app --noinput && python manage.py migrate --noinput && python3 manage.py runserver 0.0.0.0:8000 --settings=django_docker_setup.settings_local" 22 | ports: 23 | - "8000:8000" 24 | depends_on: 25 | - db -------------------------------------------------------------------------------- /init-letsencrypt.sh: -------------------------------------------------------------------------------- 1 | ################# 2 | 3 | #!/bin/bash 4 | 5 | domains=(example.com www.example.com) 6 | rsa_key_size=4096 7 | data_path="./data/certbot" 8 | email="info@example.com" # Adding a valid address is strongly recommended 9 | staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits 10 | 11 | if [ -d "$data_path" ]; then 12 | read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision 13 | if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then 14 | exit 15 | fi 16 | fi 17 | 18 | if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then 19 | echo "### Downloading recommended TLS parameters ..." 20 | mkdir -p "$data_path/conf" 21 | curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf >"$data_path/conf/options-ssl-nginx.conf" 22 | curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem >"$data_path/conf/ssl-dhparams.pem" 23 | echo 24 | fi 25 | 26 | for domain in "${domains[@]}"; do 27 | echo "### Removing old certificate for $domain ..." 28 | docker-compose run --rm --entrypoint "\ 29 | rm -Rf /etc/letsencrypt/live/$domain && \ 30 | rm -Rf /etc/letsencrypt/archive/$domain && \ 31 | rm -Rf /etc/letsencrypt/renewal/$domain.conf" certbot 32 | echo 33 | done 34 | 35 | for domain in "${domains[@]}"; do 36 | echo "### Creating dummy certificate for $domain ..." 37 | path="/etc/letsencrypt/live/$domain" 38 | mkdir -p "$data_path/conf/live/$domain" 39 | docker-compose run --rm --entrypoint "\ 40 | openssl req -x509 -nodes -newkey rsa:1024 -days 1\ 41 | -keyout "$path/privkey.pem" \ 42 | -out "$path/fullchain.pem" \ 43 | -subj '/CN=localhost'" certbot 44 | echo 45 | done 46 | 47 | echo "### Starting nginx ..." 48 | docker-compose up --force-recreate -d 49 | echo 50 | 51 | for domain in "${domains[@]}"; do 52 | echo "### Removing dummy certificate for $domain ..." 53 | docker-compose run --rm --entrypoint "\ 54 | rm -Rf /etc/letsencrypt/live/$domain" certbot 55 | echo 56 | done 57 | 58 | echo "### Requesting Let's Encrypt certificates ..." 59 | 60 | # Select appropriate email arg 61 | case "$email" in 62 | "") email_arg="--register-unsafely-without-email" ;; 63 | *) email_arg="--email $email" ;; 64 | esac 65 | 66 | # Enable staging mode if needed 67 | if [ $staging != "0" ]; then staging_arg="--staging"; fi 68 | 69 | for domain in "${domains[@]}"; do 70 | docker-compose run --rm --entrypoint "\ 71 | certbot certonly --webroot -w /var/www/certbot \ 72 | $staging_arg \ 73 | $email_arg \ 74 | -d $domain \ 75 | --rsa-key-size $rsa_key_size \ 76 | --agree-tos \ 77 | --force-renewal" certbot 78 | echo 79 | done 80 | 81 | echo "### Reloading nginx ..." 82 | docker-compose exec nginx nginx -s reload 83 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_docker_setup.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | Django>=2.0,<3.0 2 | psycopg2>=2.7,<3.0 3 | djangorestframework -------------------------------------------------------------------------------- /requirements_prod.txt: -------------------------------------------------------------------------------- 1 | Django>=2.0,<3.0 2 | psycopg2>=2.7,<3.0 3 | djangorestframework 4 | gunicorn==19.9.0 -------------------------------------------------------------------------------- /sample_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ermissa/django-docker-setup/504f24c7297d651a773a6ea0cbedb7a897899579/sample_app/__init__.py -------------------------------------------------------------------------------- /sample_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /sample_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SampleAppConfig(AppConfig): 5 | name = 'sample_app' 6 | -------------------------------------------------------------------------------- /sample_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ermissa/django-docker-setup/504f24c7297d651a773a6ea0cbedb7a897899579/sample_app/migrations/__init__.py -------------------------------------------------------------------------------- /sample_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /sample_app/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /sample_app/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | --------------------------------------------------------------------------------