├── 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 | 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
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 |
--------------------------------------------------------------------------------