├── asserts ├── main.js └── style.css ├── core ├── __init__.py ├── migrations │ └── __init__.py ├── admin.py ├── models.py ├── apps.py ├── urls.py ├── views.py └── tests.py ├── confetti ├── __init__.py ├── settings │ ├── __init__.py │ ├── prod.example.py │ ├── dev.py │ └── base.py ├── urls.py └── wsgi.py ├── docs └── img │ └── confetti.png ├── .dockerignore ├── dev.env ├── Pipfile ├── docker-entrypoint.sh ├── deployment ├── certbot │ └── conf │ │ ├── ssl-dhparams.pem │ │ ├── options-ssl-nginx.conf │ │ └── live │ │ └── example.com │ │ ├── fullchain.pem │ │ └── privkey.pem └── nginx │ └── app.conf ├── Dockerfile ├── templates └── index.html ├── manage.py ├── docker-compose.yml ├── docker-compose.prod.yml ├── LICENSE ├── .gitignore ├── README.md └── Pipfile.lock /asserts/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /confetti/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /confetti/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /asserts/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: blue; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | -------------------------------------------------------------------------------- /docs/img/confetti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnydman/confetti/HEAD/docs/img/confetti.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | venv/ 4 | **/__pycache__/ 5 | **/node_modules/ 6 | *.md 7 | .idea 8 | -------------------------------------------------------------------------------- /core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /dev.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=admin 2 | POSTGRES_PASSWORD=admin 3 | POSTGRES_DB=admin 4 | POSTGRES_PORT=5432 5 | POSTGRES_HOST_NAME=db 6 | -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from core import views 4 | 5 | urlpatterns = [ 6 | path('', views.home) 7 | ] 8 | -------------------------------------------------------------------------------- /core/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def home(request): 5 | return render(request, 'index.html', {}) 6 | 7 | -------------------------------------------------------------------------------- /confetti/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('admin/', admin.site.urls), 6 | path('', include('core.urls')) 7 | ] 8 | -------------------------------------------------------------------------------- /confetti/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "confetti.settings") 6 | 7 | application = get_wsgi_application() 8 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = "*" 8 | psycopg2-binary = "*" 9 | gunicorn = "*" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = ">=3.7" 15 | -------------------------------------------------------------------------------- /core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import Client 2 | from django.test import TestCase 3 | 4 | 5 | class ExampleTestCase(TestCase): 6 | def test_index_page(self): 7 | c = Client() 8 | resp = c.get('/') 9 | self.assertEqual(resp.status_code, 200) 10 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -ex 3 | 4 | until nc -w 1 -z db 5432; do 5 | >&2 echo "Postgres is unavailable - sleeping" 6 | sleep 1 7 | done 8 | sleep 2 9 | >&2 echo "Postgres is up - executing command" 10 | 11 | python manage.py migrate --noinput 12 | python manage.py collectstatic --noinput 13 | -------------------------------------------------------------------------------- /deployment/certbot/conf/ssl-dhparams.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y netcat postgresql-client \ 7 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 8 | 9 | WORKDIR /app 10 | 11 | COPY Pipfile* /app/ 12 | RUN pip install pipenv && \ 13 | pipenv install --system --deploy --ignore-pipfile 14 | 15 | COPY docker-entrypoint.sh /app/ 16 | RUN chmod +x docker-entrypoint.sh 17 | 18 | COPY . /app/ 19 | 20 | CMD ["sh", "/app/docker-entrypoint.sh"] 21 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | Document 11 | 12 | 13 | 14 |

Hello World!

15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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", "confetti.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - .:/app/ 9 | entrypoint: python manage.py runserver 0.0.0.0:8000 10 | ports: 11 | - "127.0.0.1:8000:8000" 12 | env_file: 13 | - dev.env 14 | environment: 15 | - DJANGO_SETTINGS_MODULE=confetti.settings.dev 16 | depends_on: 17 | - db 18 | db: 19 | image: "postgres:13-alpine" 20 | env_file: 21 | - dev.env 22 | ports: 23 | - "127.0.0.1:5433:5432" 24 | volumes: 25 | - ./dbdata:/var/lib/postgresql/data 26 | 27 | -------------------------------------------------------------------------------- /confetti/settings/prod.example.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .base import * 3 | except ImportError: 4 | raise ImportError( 5 | "Can't find the file with basic settings. Did you import?" 6 | ) 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.postgresql', 11 | 'NAME': os.environ['POSTGRES_DB'], 12 | 'USER': os.environ['POSTGRES_USER'], 13 | 'PASSWORD': os.environ['POSTGRES_PASSWORD'], 14 | 'HOST': os.environ['POSTGRES_HOST_NAME'], 15 | 'PORT': int(os.environ['POSTGRES_PORT']), 16 | } 17 | } 18 | 19 | DEBUG = False 20 | 21 | ALLOWED_HOSTS = ['your_host'] 22 | 23 | # Just for local development/testing, this key doesn't used in production! 24 | SECRET_KEY = 'very_strong_key!' 25 | -------------------------------------------------------------------------------- /deployment/certbot/conf/options-ssl-nginx.conf: -------------------------------------------------------------------------------- 1 | # This file contains important security parameters. If you modify this file 2 | # manually, Certbot will be unable to automatically provide future security 3 | # updates. Instead, Certbot will print and log an error message with a path to 4 | # the up-to-date file that you will need to refer to when manually updating 5 | # this file. 6 | 7 | ssl_session_cache shared:le_nginx_SSL:10m; 8 | ssl_session_timeout 1440m; 9 | ssl_session_tickets off; 10 | 11 | ssl_protocols TLSv1.2 TLSv1.3; 12 | ssl_prefer_server_ciphers off; 13 | 14 | ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; 15 | -------------------------------------------------------------------------------- /confetti/settings/dev.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .base import * 3 | except ImportError: 4 | raise ImportError( 5 | "Cannot find the file with basic settings. Did you import it?" 6 | ) 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.postgresql', 11 | 'NAME': os.environ.get('POSTGRES_DB', 'postgres'), 12 | 'USER': os.environ.get('POSTGRES_USER', 'postgres'), 13 | 'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'postgres'), 14 | 'HOST': os.environ.get('POSTGRES_HOST_NAME', 'localhost'), 15 | 'PORT': int(os.environ.get('POSTGRES_PORT', 5432)), 16 | } 17 | } 18 | 19 | DEBUG = True 20 | 21 | ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1', 'localhost'] 22 | 23 | # Just for local development/testing, this key doesn't used in production! 24 | SECRET_KEY = '^pde$1h8cem*ao%n)p=$ep5dv8#)xl#w)))7^l3^%a0q!!_b)a' 25 | -------------------------------------------------------------------------------- /deployment/nginx/app.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | server_name _; 5 | access_log /var/log/nginx/access.log; 6 | error_log /var/log/nginx/error.log; 7 | 8 | location /.well-known/acme-challenge/ { 9 | root /var/www/certbot; 10 | } 11 | location / { 12 | return 301 https://$host$request_uri; 13 | } 14 | } 15 | 16 | server { 17 | listen 443 ssl; 18 | server_name _; 19 | 20 | access_log /var/log/nginx/access.log; 21 | error_log /var/log/nginx/error.log; 22 | 23 | # replace example.com with your domain name 24 | ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 25 | ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 26 | # uncomment lines after you've got certificates 27 | # include /etc/letsencrypt/options-ssl-nginx.conf; 28 | # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 29 | 30 | location / { 31 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 32 | proxy_set_header Host $host; 33 | proxy_set_header X-Real-IP $remote_addr; 34 | proxy_redirect off; 35 | proxy_pass http://web:8000; 36 | } 37 | location /static/ { 38 | autoindex off; 39 | gzip_static on; 40 | add_header Cache-Control public; 41 | alias /var/www/static/; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | restart: always 8 | entrypoint: gunicorn -w 3 --bind=0.0.0.0:8000 confetti.wsgi 9 | volumes: 10 | - .:/app/ 11 | expose: 12 | - "8000" 13 | env_file: 14 | - .env 15 | environment: 16 | - DJANGO_SETTINGS_MODULE=confetti.settings.prod 17 | depends_on: 18 | - db 19 | db: 20 | image: "postgres:13-alpine" 21 | restart: always 22 | env_file: 23 | - .env 24 | volumes: 25 | - ./dbdata-prod:/var/lib/postgresql/data 26 | expose: 27 | - "5432" 28 | nginx: 29 | image: "nginx:1.19-alpine" 30 | restart: always 31 | ports: 32 | - 80:80 33 | - 443:443 34 | volumes: 35 | - ./deployment/nginx:/etc/nginx/conf.d 36 | - ./deployment/certbot/conf:/etc/letsencrypt 37 | - ./static:/var/www/static 38 | command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\";'" 39 | depends_on: 40 | - web 41 | certbot: 42 | image: "certbot/certbot" 43 | restart: always 44 | volumes: 45 | - ./deployment/certbot/conf:/etc/letsencrypt 46 | entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" 47 | depends_on: 48 | - nginx 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Django nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # IDE 107 | .idea 108 | 109 | # Project 110 | static/ 111 | **/prod.py 112 | -------------------------------------------------------------------------------- /deployment/certbot/conf/live/example.com/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CONFETTI 2 | > The way to start a Django project with Docker 3 | 4 |

5 | 6 | ## Technology Stack 7 | 8 | * Docker v20.x 9 | * Docker-compose v1.28 10 | * Django v3.x 11 | 12 | ## Getting Started 13 | 14 | ### Development 15 | 16 | 1. Clone this git repository to your local machine. 17 | 18 | 2. Install ```docker``` and ```docker-compose``` using official documentation. 19 | 20 | 3. Add user to the docker group to run commands without sudo: 21 | ``` 22 | sudo usermod -aG docker $USER 23 | ``` 24 | 4. Update settings in `dev.env` and `confetti/settings/prod.py`(recommended). 25 | 26 | 5. Go to the project root and run: 27 | ``` 28 | docker-compose up 29 | ``` 30 | 31 | 6. Create Django superuser in the container(in the second shell): 32 | ``` 33 | docker-compose exec web python manage.py createsuperuser 34 | ``` 35 | 36 | 7. Run tests(optional): 37 | ``` 38 | docker-compose exec web python manage.py test 39 | ``` 40 | 41 | 8. Navigate to `http://localhost:8000/`. 42 | 43 | # Production 44 | 45 | 1. Create and fill `.env` and `confetti/settings/prod.py`. 46 | 47 | 2. Run application: 48 | ``` 49 | docker-compose -f docker-compose.prod.yml up 50 | ``` 51 | 3. Generate certificates(see next section). 52 | 53 | 4. Navigate to `https://localhost/`. 54 | 55 | ## Tips 56 | How to generate ssl certificates? [Follow this guide.](https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71) 57 | 58 | The simplest way with less headaches: 59 | 1. ssh to the server and go to the project root, assume that you configured domains properly: 60 | ``` 61 | docker-compose -f docker-compose.prod.yml exec nginx sh 62 | ``` 63 | 2. Install certbot: 64 | ``` 65 | apk update && apk add certbot certbot-nginx 66 | ``` 67 | 3. Generate certificates, we use volumes so we don't need to copy certs manually, 68 | don't forget to commit them or even better exclude them from git tracking: 69 | ``` 70 | certbot certonly --nginx # follow instructions 71 | ``` 72 | 73 | 4. Update data in `deployment/nginx/app.conf`, and then: 74 | ``` 75 | docker-compose -f docker-compose.prod.yml up --build 76 | ``` 77 | 78 | Ad Hoc docker/docker-compose commands (put in .bash_aliases): 79 | ``` 80 | alias dcubn=docker-compose build --no-cache 81 | alias dcub=docker-compose up --build 82 | alias dcu=docker-compose up 83 | ``` 84 | 85 | > ⭐️ Thanks everyone who has starred the project, it means a lot! 86 | 87 | Happy coding :blush: 88 | -------------------------------------------------------------------------------- /confetti/settings/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | PROJECT_DIR = os.path.dirname(BASE_DIR) 6 | 7 | # Quick-start development settings - unsuitable for production 8 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 9 | 10 | # Application definition 11 | 12 | INSTALLED_APPS = [ 13 | 'django.contrib.admin', 14 | 'django.contrib.auth', 15 | 'django.contrib.contenttypes', 16 | 'django.contrib.sessions', 17 | 'django.contrib.messages', 18 | 'django.contrib.staticfiles' 19 | ] 20 | 21 | MIDDLEWARE = [ 22 | 'django.middleware.security.SecurityMiddleware', 23 | 'django.contrib.sessions.middleware.SessionMiddleware', 24 | 'django.middleware.common.CommonMiddleware', 25 | 'django.middleware.csrf.CsrfViewMiddleware', 26 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 27 | 'django.contrib.messages.middleware.MessageMiddleware', 28 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 29 | ] 30 | 31 | ROOT_URLCONF = 'confetti.urls' 32 | 33 | TEMPLATES = [ 34 | { 35 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 36 | 'DIRS': [os.path.join(PROJECT_DIR, 'templates')], 37 | 'APP_DIRS': True, 38 | 'OPTIONS': { 39 | 'context_processors': [ 40 | 'django.template.context_processors.debug', 41 | 'django.template.context_processors.request', 42 | 'django.contrib.auth.context_processors.auth', 43 | 'django.contrib.messages.context_processors.messages', 44 | ], 45 | }, 46 | }, 47 | ] 48 | 49 | WSGI_APPLICATION = 'confetti.wsgi.application' 50 | 51 | # Password validation 52 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 53 | 54 | AUTH_PASSWORD_VALIDATORS = [ 55 | { 56 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 57 | }, 58 | { 59 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 60 | }, 61 | { 62 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 63 | }, 64 | { 65 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 66 | }, 67 | ] 68 | 69 | # Internationalization 70 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 71 | 72 | LANGUAGE_CODE = 'en-us' 73 | 74 | TIME_ZONE = 'UTC' 75 | 76 | USE_I18N = True 77 | 78 | USE_L10N = True 79 | 80 | USE_TZ = True 81 | 82 | # Static files (CSS, JavaScript, Images) 83 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 84 | 85 | STATIC_URL = '/static/' 86 | 87 | STATICFILES_DIRS = [os.path.join(PROJECT_DIR, 'asserts')] 88 | 89 | STATIC_ROOT = os.path.join(PROJECT_DIR, 'static') 90 | 91 | MEDIA_URL = '/media/' 92 | MEDIA_ROOT = os.path.join(PROJECT_DIR, 'media') 93 | -------------------------------------------------------------------------------- /deployment/certbot/conf/live/example.com/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKQIBAAKCAgEAoO65TXPB9DIBRTAV1xJnjOGwR5VgNh34dsyDt1eaDjT+Q4c8 3 | OBP5+SnFu5Pwjjn62Q1r0rYf5Mwyt1rG4VxlHlyS3D8ji19cbq13dfn4kVzPsZTz 4 | AufHzqAZuI7L7YR0tRfV6UBFuMWFZCJkFazGQfo0FpAzE446FSFA2pP+0VYHH1GC 5 | Id4wmMxbVaZONjWujJlSS2SDlUfvD4dLJDAx/mLk40VtkaPSeFDbQo+ZZYhT1DG+ 6 | NNLdtPBCgzPbDn41jGQ/UxWbMDXn42vIwO/CJSh5QGHPcnTN8HcOp5/2g63TdASg 7 | 8vDzjru46mdhrkz6Rw74Rdm599cpySNqCxmCUUVSYnxk+zn4aNeayL7PBTrw+RLd 8 | rExcXD6D/BxV7hFrbjOn7b6nzMHa43dYpzRygcMngbVPY4mvi+7txGWTsqJRFtnh 9 | +KfDIZ+0Oy2BbgVo76LkKNmIGKQfm4zIk8jma6jjDNnKUdcdA054mgBeQt3SVjjJ 10 | eHTv7CS5hqNwmlOJsAqFkfLTpmR1MGck2Ahh28aX8nwEifhS3Obx5LMocPg+NNvK 11 | hVeM6fFs+r4lQzupnGF1RecqAC8a6UAdbV/et0gXC/2gLnyHYZqxen6r1MMSCmWJ 12 | vCKSXp1F/cLf3Mnf4AWrVvRh9SUAmzty5L1WRm68cIAtIDxXm/F2/t6Xdy0CAwEA 13 | AQKCAgBTofv2fjPLPblfRU/0RwI9OoQrjztCpGydAEdrHkFXJL8GRN9MnUUI8WZq 14 | xsT6hLMR3WGtTSxh8KRimPBF6Yg7MQy5HK7SHSqh8c/lJZTBpiPhO3DWj7FMdfMn 15 | 8D2WH4GvIsXtw5L4JN3fXef5Q6J8YxRyrVl9zintFSzo6oRAMZ7d7bPc+eTzYzAL 16 | 4LNAhMFDG+Kif4fKKmnX/g1Urx8r/iTI3ynDyAj2sb5gZtPm6eP2oCsitHq3LyzB 17 | lk8+xuP5I5etC8tsUHdDvp3DdlXEjbYFeU8fQghL8PNStVSctdbvsgAagJFyGahN 18 | WJBwzlw6VVM8qZt1awGyH49aWVVZJ8xlPU3iWCPkA+hhtFJOGfHJozZnfeVCBQeM 19 | mvDGKxCvPr0SOSGqqf2snK40Yp5La2e1XABIO+V5J/qU7ubQpC61aFWfJat9hChc 20 | 6FCNEVFr58rWO3qqBMbyOHkxt3A6mYcAO4IYH/Tcz0Q8m+9lKKIpImRPdc/AQzlg 21 | aczqo1qeXlwbLOCtpCR8tj7TI7805vMGzhOUpV1HcKuVRYTe+achTqTu7S/VEH0j 22 | 0KkfTlj39mxGTsHI3P7l2VJkmaWPZGr0NOuOrO7CDdZzaxCh+yrJwAUaXgPR2rpn 23 | 6Jb0h+uZ4ypJiYYa22La/fBFG1ssvbNKWVCSj2gLMIzExAfFWQKCAQEA0Iz5TUr3 24 | Km0LDPI1dApMPENaKPcG5w1ZFQAzzysGNSbEZR6f2etIRsdJuJO/OAZP7lT7FBhr 25 | PxHFQX8hIM0NOIhfVQ2DWVnnjTNSL33L0bbFYGXL3mmzq8kGtjIQoy6EWNABJ9FL 26 | 43LASojtXqAuXskBpGtRTLRdJscE9ycGjKxIiR7a8GgX9tipUQUrOaDDVPb1K6pe 27 | OZvF/1ZcF+rKEP5CGNFw6sW/2lJzvcBk5kx3U9QnmOq2dNr9SkmjSB6X2PhND6HH 28 | t7rc+FCzyJ6+6YXkg2KnbGovzyt/eBmT3/mw+ouE+Oq3aLeCp0FdCFLLc8JJ8XIp 29 | jAyqWmDlcPrGFwKCAQEAxYw7XXh0+Yi4KFef5k04gR8278ss16kPzYuG+oUU10VS 30 | iyPl89xQcEAIu4QPYb27/Q7F6+6sbsX3hj4ebKF80O57uVz2oIR5G+pMu3dkytnU 31 | FJd7xDnRlsfZUZgDoZO/nKvjyxc6SCyX+1/eLgwQYH6AL8jzJDWuFpq7BAatIsDU 32 | DX3qIUEZSJGXllw34BHk2UxW6xnSzTUuhbBTL2v7ApB1clz+VxwGWp24OL8RXCv7 33 | 4cabCxAxIJzGexwRzP1BQkYF15TAVS6Kw2rLPWEgw/Bh3++19sxYm7XXZrrSNIIB 34 | DRt8Q7fKb1Fchdt8xAeVbqRmp4sNx56k2ztm26p7WwKCAQAh82E+eP7F1FyhLekP 35 | zAFfKiPKWXSxNpYS+iZ1Cm8HRhlg0Nx7EHeCtjzFJKBTkYt/pCv/uH31FYKESBrv 36 | iRIm6uSCJyID8MPmU72EZ7k/mb6GE+xdd929EIvcK0hUGGWJQ8t8XPE6xNqTB7mE 37 | 1xTVb2mqB6KDlzU2891WEu9ZBU8Fq5U+3+NO1zSxMOM5jXdfJJkDE8glHrvb9oYl 38 | ip58p7fA7I+yWKED/LpE4MZMS5R9fCM6HLO5UFE9v8EqI3ja5Ik/CzpFKdGBRzoN 39 | ZfJ0d8ycqvnQh3TjCBTHS3qsUqzCDOaH00YqcP1/Fv/7M+dVIAMDoG8tEn7FGdMg 40 | UmLfAoIBAQC9AJPzs8HG1IXNIzi68Pq0nxbCidMXaorSD0JPAXCVHaABu5DQpeLX 41 | 4ov4TDsjOpJXZQD739heC03sxBX7vO1cQC9jg4v6u4Pbr2lGms52Pb5pDR2++cqK 42 | xh4nsJcYyfEzhkllD44Va4TlXi2GRMiOable/pWR+UHcIqH3EiQn8fFwDx4oPs5f 43 | 3dv4YbabzML1G9K1EHndF5qxEagk4NzA8VFM36f/07JIlEmjETscwoA03P2Nsiou 44 | l5BWTN7DDzKWo8QHJCx30xW0LcLRsNHWZXBe/ktMGupT38B8axWeYxt6XOehKoCl 45 | zGQHNZ3DN7ZCq0wD1EiUyjXUAptAY3WtAoIBAQCLT04n0HnLA2DKghEKMum7bL4D 46 | J1ELnsOnMoekocv4S+882PMhH4Z9xWEl0aRnRlRYEV6wNaKkOz9QFOfKjXXeKsap 47 | MRTMNR8ZGDgGdT1AXUtSeunOR/UewrcGZTvQs9qM6VCf53oUZGn11pwNF01Dd+Nv 48 | B2CjNiRpm/elB+evmOCIbkYn0Ll+qzFFl/1C1JHc8LH9Hw/agYytTUMGVlgh2pkl 49 | PD0IBEYPx9K+NkpwnPiR2HHufK489FM7HLSkMD/6Hk8pM1BKz0qhH74wkgt0zRrJ 50 | wyQD2tmhXu5IH60EAiOv12s6wJPOLEKNRUTNWJDBb4Y6Eju1jyc1Z+z0MKKE 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "c730f59856c41d64fddf3a3ded42c89ffe99715e231a88d5aece1248a632ceb1" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": ">=3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "asgiref": { 20 | "hashes": [ 21 | "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", 22 | "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" 23 | ], 24 | "markers": "python_version >= '3.5'", 25 | "version": "==3.3.1" 26 | }, 27 | "django": { 28 | "hashes": [ 29 | "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f", 30 | "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7" 31 | ], 32 | "index": "pypi", 33 | "version": "==3.1.6" 34 | }, 35 | "gunicorn": { 36 | "hashes": [ 37 | "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", 38 | "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" 39 | ], 40 | "index": "pypi", 41 | "version": "==20.0.4" 42 | }, 43 | "psycopg2-binary": { 44 | "hashes": [ 45 | "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", 46 | "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", 47 | "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", 48 | "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", 49 | "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", 50 | "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", 51 | "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", 52 | "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", 53 | "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", 54 | "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", 55 | "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", 56 | "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", 57 | "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", 58 | "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", 59 | "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", 60 | "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", 61 | "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", 62 | "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", 63 | "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", 64 | "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", 65 | "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", 66 | "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", 67 | "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", 68 | "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", 69 | "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", 70 | "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", 71 | "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", 72 | "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", 73 | "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", 74 | "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", 75 | "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", 76 | "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", 77 | "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", 78 | "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", 79 | "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" 80 | ], 81 | "index": "pypi", 82 | "version": "==2.8.6" 83 | }, 84 | "pytz": { 85 | "hashes": [ 86 | "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", 87 | "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" 88 | ], 89 | "version": "==2021.1" 90 | }, 91 | "sqlparse": { 92 | "hashes": [ 93 | "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", 94 | "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" 95 | ], 96 | "markers": "python_version >= '3.5'", 97 | "version": "==0.4.1" 98 | } 99 | }, 100 | "develop": {} 101 | } 102 | --------------------------------------------------------------------------------