├── .gitignore ├── LICENSE ├── README.md ├── cookiecutter.json ├── hooks └── post_gen_project.py └── {{cookiecutter.project_slug}} ├── .env.template ├── .gitignore ├── README.md ├── deploy ├── nginx │ └── conf.d │ │ └── django_static.conf └── wsgi.py ├── docker-compose.override.yml ├── docker-compose.production.yml ├── docker-compose.yml ├── docker ├── django │ ├── Dockerfile │ ├── entrypoint │ ├── start_dev │ └── start_production └── node │ └── Dockerfile ├── githooks └── pre-commit ├── manage.py ├── package.json ├── postcss.config.js ├── requirements ├── base.txt ├── dev.txt └── production.txt ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── {{cookiecutter.project_slug}} ├── __init__.py ├── apps ├── __init__.py ├── main │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── user │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── context_processors.py ├── settings ├── __init__.py ├── base.py ├── dev.py └── production.py ├── src ├── css │ ├── main.pcss │ └── tailwind.config.js └── js │ └── main.js ├── static └── images │ └── logo.svg ├── templates ├── base.html ├── includes │ ├── footer.html │ └── header.html └── index.html ├── urls.py └── utils └── storage.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Unit test / coverage reports 7 | htmlcov/ 8 | .tox/ 9 | .coverage 10 | .coverage.* 11 | .cache 12 | nosetests.xml 13 | coverage.xml 14 | *.cover 15 | .hypothesis/ 16 | .pytest_cache/ 17 | 18 | # Translations 19 | *.mo 20 | *.pot 21 | 22 | # Django stuff: 23 | *.log 24 | 25 | # environment 26 | .env 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dan Atkinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-docker-tailwind 2 | A Cookiecutter Django project template that uses Tailwind CSS, Wagtail CMS and works with Docker. 3 | 4 | Based on [django-cookiecutter](https://github.com/pydanny/cookiecutter-django) 5 | 6 | ## Includes 7 | 8 | * Docker - Production and Dev configs 9 | * Tailwind CSS 10 | * Webpack - Production build, optimise assets and generate favicons 11 | * API - Django Rest Framework 12 | * CMS - Wagtail 13 | 14 | ## TODO 15 | 16 | * Add Frontend JS framework options 17 | * Production webserver config 18 | * Custom user profile 19 | * Initial auth setup 20 | 21 | ## Usage 22 | 23 | To generate your project get Cookiecutter: 24 | 25 | ```python 26 | pip install cookiecutter 27 | ``` 28 | 29 | Run Cookiecutter with this template: 30 | 31 | ```python 32 | cookiecutter https://github.com/dantium/django-docker-tailwind 33 | ``` 34 | 35 | Enter your project details at the prompts, once the project has been created enter the directory: 36 | 37 | ```python 38 | cd project_slug 39 | ``` 40 | 41 | Run with docker: 42 | 43 | ```python 44 | docker-compose up 45 | ``` 46 | 47 | Create a superuser: 48 | 49 | ```python 50 | docker-compose run django_project_slug ./manage.py createsuperuser 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "Django Project", 3 | "project_slug": "django_project", 4 | "project_domain": "danatkinson.com", 5 | "author_name": "Dan Atkinson", 6 | "author_email": "dan@danatkinson.com", 7 | "timezone": "UTC", 8 | "use_wagtail": "y", 9 | "use_sentry": "y", 10 | "use_gc_storage": "y" 11 | } 12 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup envs for dev environment 3 | """ 4 | 5 | import secrets 6 | import shutil 7 | 8 | 9 | def get_random_string(): 10 | return secrets.token_urlsafe(16) 11 | 12 | 13 | def set_key_random(file_path, key): 14 | secret = get_random_string() 15 | 16 | with open(file_path, 'r+') as config_file: 17 | file_contents = config_file.read().replace(key, secret, 1) 18 | config_file.seek(0) 19 | config_file.write(file_contents) 20 | config_file.truncate() 21 | 22 | 23 | def copy_dev_env(): 24 | env_template = '.env.template' 25 | env_file = '.env' 26 | shutil.copyfile(env_template, env_file) 27 | set_key_random(env_file, '__DJANGO_SECRET_KEY__') 28 | set_key_random(env_file, '__POSTGRES_PASSWORD___') 29 | 30 | 31 | def main(): 32 | copy_dev_env() 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.env.template: -------------------------------------------------------------------------------- 1 | DOMAIN_NAME={{ cookiecutter.project_domain }} 2 | DJANGO_SECRET_KEY=__DJANGO_SECRET_KEY__ 3 | 4 | POSTGRES_HOST=postgres 5 | POSTGRES_PORT=5432 6 | POSTGRES_DB={{ cookiecutter.project_slug }} 7 | POSTGRES_USER={{ cookiecutter.project_slug }} 8 | POSTGRES_PASSWORD=__POSTGRES_PASSWORD___ 9 | 10 | # DATABASE_URL=postgres://user:password@host:port/db 11 | # DJANGO_ALLOWED_HOSTS=www.{{ cookiecutter.project_domain }} 12 | # DJANGO_STATIC_URL 13 | # DJANGO_MEDIA_URL 14 | 15 | {%- if cookiecutter.use_sentry == 'y' %} 16 | # SENTRY_DSN=__SENTRY_DSN__ 17 | {%- endif %} 18 | 19 | {%- if cookiecutter.use_gc_storage == 'y' %} 20 | # GS_BUCKET_NAME=__GS_BUCKET_NAME__ 21 | {%- endif %} 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Unit test / coverage reports 7 | htmlcov/ 8 | .tox/ 9 | .coverage 10 | .coverage.* 11 | .cache 12 | nosetests.xml 13 | coverage.xml 14 | *.cover 15 | .hypothesis/ 16 | .pytest_cache/ 17 | 18 | # Translations 19 | *.mo 20 | *.pot 21 | 22 | # Django stuff: 23 | *.log 24 | media 25 | serve_static 26 | # environment 27 | .env 28 | 29 | # node 30 | node_modules/ 31 | 32 | # other 33 | letsencrypt 34 | 35 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/README.md: -------------------------------------------------------------------------------- 1 | # {{ cookiecutter.project_slug }} 2 | 3 | Add project details here... 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/deploy/nginx/conf.d/django_static.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | location /static/ { 4 | alias /var/www/static/; 5 | add_header "Access-Control-Allow-Origin" *; 6 | } 7 | location /media/ { 8 | alias /var/www/media/; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/deploy/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for {{cookiecutter.project_slug}} 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/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | import sys 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | 15 | app_path = os.path.abspath( 16 | os.path.dirname(os.path.abspath(__file__)) 17 | ) 18 | sys.path.append(os.path.join(app_path, "{{ cookiecutter.project_slug }}")) 19 | 20 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{cookiecutter.project_slug}}.settings') 21 | 22 | application = get_wsgi_application() 23 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | # https://docs.docker.com/compose/extends/ 2 | # Specific dev config 3 | 4 | version: '3.6' 5 | services: 6 | django: 7 | ports: 8 | - "8000:8000" 9 | image: {{ cookiecutter.project_slug }}_dev_django 10 | build: 11 | args: 12 | DJANGO_ENV: dev 13 | command: /app/docker/django/start_dev 14 | entrypoint: /app/docker/django/entrypoint 15 | node: 16 | build: 17 | context: . 18 | dockerfile: ./docker/node/Dockerfile 19 | image: {{ cookiecutter.project_slug }}_dev_node 20 | volumes: 21 | - .:/app 22 | - /app/node_modules 23 | command: npm run dev 24 | ports: 25 | - "3000:3000" 26 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker-compose.production.yml: -------------------------------------------------------------------------------- 1 | # Production docker 2 | 3 | version: '3.6' 4 | services: 5 | django: 6 | build: 7 | target: production_build 8 | args: 9 | DJANGO_ENV: production 10 | command: /start_production 11 | ports: 12 | - 8000:8000 13 | labels: 14 | #- traefik.backend=django 15 | - traefik.enable=true 16 | # redirect http - https 17 | - traefik.http.routers.http.rule=Host(`www.{{cookiecutter.project_domain}}`) 18 | - traefik.http.routers.http.entrypoints=http 19 | - traefik.http.routers.http.middlewares=redirect 20 | - traefik.http.middlewares.redirect.redirectscheme.scheme=https 21 | - traefik.http.middlewares.redirect.redirectscheme.permanent=true 22 | 23 | - traefik.http.routers.django.rule=Host(`www.{{cookiecutter.project_domain}}`) 24 | - traefik.http.routers.django.entrypoints=https 25 | - traefik.http.routers.django.tls.certresolver=tlschallenge 26 | 27 | nginx: 28 | image: nginx:alpine 29 | restart: unless-stopped 30 | ports: 31 | - "8001:8001" 32 | volumes: 33 | - ./serve_static/:/var/www/static/ 34 | - ./media/:/var/www/media/ 35 | - ./deploy/nginx/conf.d/:/etc/nginx/conf.d/ 36 | 37 | depends_on: 38 | - django 39 | 40 | labels: 41 | - traefik.enable=true 42 | - traefik.http.routers.nginx.rule=Host(`media.{{cookiecutter.project_domain}}`) 43 | - traefik.http.routers.nginx.entrypoints=https 44 | - traefik.http.routers.nginx.tls.certresolver=tlschallenge 45 | 46 | traefik: 47 | image: traefik:v2.0 48 | restart: always 49 | # Enables the web UI and tells Traefik to listen to docker 50 | command: 51 | - "--providers.docker.exposedbydefault=false" 52 | - "--api.insecure=true" 53 | - "--providers.docker" 54 | - "--entrypoints.https.address=:443" 55 | - "--entrypoints.http.address=:80" 56 | - "--certificatesresolvers.tlschallenge.acme.tlschallenge=true" 57 | #- "--certificatesresolvers.ssl.acme.httpchallenge.entrypoint=http" 58 | #- "--certificatesresolvers.tlschallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" 59 | - "--certificatesresolvers.tlschallenge.acme.email={{cookiecutter.author_email}}" 60 | - "--certificatesresolvers.tlschallenge.acme.storage=/letsencrypt/acme.json" 61 | 62 | ports: 63 | - "80:80" 64 | - "8080:8080" 65 | - "443:443" 66 | volumes: 67 | - /var/run/docker.sock:/var/run/docker.sock 68 | - "./letsencrypt:/letsencrypt" 69 | 70 | depends_on: 71 | - django 72 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Common docker config used in dev and production 2 | 3 | version: '3.6' 4 | services: 5 | postgres: 6 | image: "postgres:9.6.9-alpine" 7 | volumes: 8 | - postgres_data:/var/lib/postgresql/data 9 | - ./backups/:/backups 10 | env_file: 11 | - .env 12 | 13 | django: 14 | build: 15 | target: dev_build 16 | context: . 17 | dockerfile: ./docker/django/Dockerfile 18 | command: /start_dev 19 | volumes: 20 | - .:/app 21 | depends_on: 22 | - postgres 23 | env_file: 24 | - .env 25 | 26 | volumes: 27 | postgres_data: 28 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker/django/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.4-alpine3.9 as base_build 2 | 3 | ARG DJANGO_ENV 4 | 5 | ENV DJANGO_ENV=${DJANGO_ENV} \ 6 | GUNICORN_TIMEOUT=90 \ 7 | # python: 8 | PYTHONFAULTHANDLER=1 \ 9 | PYTHONUNBUFFERED=1 \ 10 | PYTHONHASHSEED=random 11 | 12 | WORKDIR /app 13 | 14 | COPY ./requirements /requirements 15 | 16 | RUN apk update \ 17 | && apk add --no-cache --virtual .builddeps gcc python3-dev musl-dev postgresql-dev \ 18 | jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \ 19 | && pip install -r /requirements/${DJANGO_ENV}.txt \ 20 | && find /usr/local \ 21 | \( -type d -a -name test -o -name tests \) \ 22 | -o \( -type f -a -name '*.pyc' -o -name '*.pyo' \) \ 23 | -exec rm -rf '{}' + \ 24 | && runDeps="$( \ 25 | scanelf --needed --nobanner --recursive /usr/local \ 26 | | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ 27 | | sort -u \ 28 | | xargs -r apk info --installed \ 29 | | sort -u \ 30 | )" \ 31 | && apk add --virtual .rundeps $runDeps \ 32 | && apk del .builddeps 33 | 34 | COPY . /app 35 | 36 | FROM base_build as production_build 37 | 38 | CMD gunicorn deploy.wsgi \ 39 | --bind 0.0.0.0:$PORT \ 40 | --timeout $GUNICORN_TIMEOUT 41 | 42 | FROM base_build as dev_build 43 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker/django/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" 8 | 9 | postgres_ready() { 10 | python << END 11 | import sys 12 | import psycopg2 13 | try: 14 | psycopg2.connect( 15 | dbname="${POSTGRES_DB}", 16 | user="${POSTGRES_USER}", 17 | password="${POSTGRES_PASSWORD}", 18 | host="${POSTGRES_HOST}", 19 | port="${POSTGRES_PORT}", 20 | ) 21 | except psycopg2.OperationalError: 22 | sys.exit(-1) 23 | sys.exit(0) 24 | END 25 | } 26 | until postgres_ready; do 27 | >&2 echo 'Waiting for PostgreSQL to become available...' 28 | sleep 1 29 | done 30 | >&2 echo 'PostgreSQL is available' 31 | 32 | exec "$@" 33 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker/django/start_dev: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | python manage.py migrate 9 | python manage.py runserver 0.0.0.0:8000 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker/django/start_production: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | python manage.py migrate --noinput 8 | python manage.py collectstatic --noinput 9 | /usr/local/bin/gunicorn {{cookiecutter.project_slug}}.wsgi --bind 0.0.0.0:8000 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/docker/node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-stretch-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY ./package.json /app 6 | 7 | RUN npm install && npm cache clean --force 8 | 9 | ENV PATH ./node_modules/.bin/:$PATH 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PATH=/usr/local/bin:$PATH 3 | docker-compose run node npm run build 4 | git add . 5 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{cookiecutter.project_slug}}.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ cookiecutter.project_slug }}", 3 | "version": "1.0.0", 4 | "description": "", 5 | "dependencies": { 6 | 7 | }, 8 | "devDependencies": { 9 | "@babel/core": "^7.4.5", 10 | "@babel/preset-env": "^7.4.5", 11 | "@glidejs/glide": "^3.3.0", 12 | "babel-loader": "^8.0.6", 13 | "css-loader": "^3.0.0", 14 | "glob-all": "^3.1.0", 15 | "purgecss-webpack-plugin": "^1.5.0", 16 | "favicons-webpack-plugin": "^1.0.1", 17 | "file-loader": "^4.0.0", 18 | "mini-css-extract-plugin": "^0.7.0", 19 | "optimize-css-assets-webpack-plugin": "^5.0.3", 20 | "postcss-loader": "^3.0.0", 21 | "sass-loader": "^7.1.0", 22 | "style-loader": "^0.23.1", 23 | "tailwindcss": "^1.0.3", 24 | "terser-webpack-plugin": "^1.3.0", 25 | "webpack": "4.33.0", 26 | "webpack-cli": "^3.3.3", 27 | "webpack-merge": "^4.2.1" 28 | }, 29 | "scripts": { 30 | "test": "", 31 | "dev": "webpack --config webpack.dev.js", 32 | "build": "webpack --config webpack.prod.js --progress --hide-modules" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}.git" 37 | }, 38 | "keywords": [], 39 | "author": "{{ cookiecutter.author_name }}", 40 | "license": "MIT", 41 | "homepage": "https://{{ cookiecutter.project_domain }}" 42 | } 43 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss') 2 | 3 | module.exports = { 4 | plugins: [ 5 | tailwindcss('./{{ cookiecutter.project_slug }}/src/css/tailwind.config.js'), 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements/base.txt: -------------------------------------------------------------------------------- 1 | django==3.0.8 2 | django-environ==0.4.5 3 | djangorestframework==3.10.3 4 | redis==3.3.8 5 | django-redis==4.10.0 6 | psycopg2==2.8.3 7 | gunicorn==19.9.0 8 | django-compressor==2.3 9 | {%- if cookiecutter.use_wagtail == 'y' %} 10 | wagtail==2.6.2 11 | {%- endif %} 12 | {%- if cookiecutter.use_sentry == 'y' %} 13 | sentry-sdk==0.12.3 14 | {%- endif %} 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | django-debug-toolbar==2.0 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements/production.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | gunicorn==19.9.0 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | 4 | const styleRule = { 5 | test: /\.pcss$/, 6 | use: [ 7 | MiniCssExtractPlugin.loader, 8 | //{loader: 'style-loader'}, 9 | { loader: 'css-loader', options: { sourceMap: true, importLoaders: 1 } }, 10 | {loader: 'postcss-loader'} 11 | //'sass-loader' 12 | ] 13 | }; 14 | 15 | const cssRule = { 16 | test: /\.css$/, 17 | use: [MiniCssExtractPlugin.loader, 'css-loader'] 18 | } 19 | 20 | const assetRule = { 21 | test: /.(jpg|png|woff(2)?|eot|ttf|svg)$/, 22 | loader: 'file-loader' 23 | }; 24 | 25 | const jsRule = { 26 | test: /\.m?js$/, 27 | exclude: /(node_modules|bower_components)/, 28 | use: { 29 | loader: 'babel-loader', 30 | options: { 31 | presets: ['@babel/preset-env'] 32 | } 33 | } 34 | }; 35 | 36 | 37 | module.exports = { 38 | context: __dirname, 39 | 40 | entry: ['./{{ cookiecutter.project_slug }}/src/js/main.js', './{{ cookiecutter.project_slug }}/src/css/main.pcss'], 41 | output: { 42 | filename: 'js/main.js', 43 | chunkFilename: '[name]-[hash].js', 44 | path: path.resolve(__dirname, '{{ cookiecutter.project_slug }}/static') 45 | }, 46 | resolve: { 47 | mainFields: ['browser', 'module', 'main'] 48 | }, 49 | module: { 50 | rules: [styleRule, cssRule] }, 51 | 52 | plugins: [ 53 | new MiniCssExtractPlugin({ 54 | filename: 'css/[name].css', 55 | 56 | }), 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | 5 | module.exports = merge(common, { 6 | mode: 'development', 7 | watch: true, 8 | watchOptions: { 9 | aggregateTimeout: 300, 10 | poll: 1000, 11 | ignored: ['/node_modules/'] 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | const glob = require("glob-all"); 4 | const path = require('path'); 5 | const PurgecssPlugin = require("purgecss-webpack-plugin"); 6 | const TerserJSPlugin = require('terser-webpack-plugin'); 7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 8 | const FaviconsWebpackPlugin = require('favicons-webpack-plugin') 9 | 10 | 11 | class TailwindExtractor { 12 | static extract(content) { 13 | return content.match(/[A-Za-z0-9-_:\/]+/g) || []; 14 | } 15 | } 16 | 17 | const purgecss = new PurgecssPlugin({ 18 | paths: glob.sync([ 19 | //path.join(__dirname, "{{ cookiecutter.project_slug }}/static/js/**/*.vue"), 20 | path.join(__dirname, "{{ cookiecutter.project_slug }}/templates/**/*.html"), 21 | path.join(__dirname, "{{ cookiecutter.project_slug }}/static/js/*.js") 22 | ]), 23 | extractors: [ 24 | { 25 | extractor: TailwindExtractor, 26 | extensions: ["html", "js", "vue"] 27 | } 28 | ], 29 | whitelist: ['a'] 30 | }) 31 | 32 | const favicon = new FaviconsWebpackPlugin({ 33 | logo: path.resolve(__dirname, '{{ cookiecutter.project_slug }}/static/images/logo.svg'), 34 | //outputPath: path.resolve(__dirname, 'favicon/'), 35 | prefix: 'favicon/', 36 | }); 37 | 38 | module.exports = merge(common, { 39 | mode: 'production', 40 | plugins: [purgecss, favicon], 41 | optimization: { 42 | minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})], 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/migrations/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/main/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | name = 'user' 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/migrations/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/apps/user/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/context_processors.py: -------------------------------------------------------------------------------- 1 | import environ 2 | 3 | env = environ.Env() 4 | 5 | 6 | def extra_template_values(request): 7 | """ Values made available to templates """ 8 | data = { 9 | {%- if cookiecutter.use_sentry == 'y' %} 10 | 'SENTRY_DSN': env("SENTRY_DSN", default=''), 11 | {%- endif %} 12 | } 13 | return data 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/settings/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | environment = os.getenv('DJANGO_ENV', 'dev') 4 | if environment == 'dev': 5 | from .dev import * # noqa F405 6 | else: 7 | from .production import * # noqa F405 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for {{cookiecutter.project_slug}} project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import PurePath 14 | import environ 15 | 16 | env = environ.Env() 17 | 18 | # Build paths inside the project like this: BASE_DIR.joinpath('static') 19 | BASE_DIR = PurePath(__file__).parent.parent 20 | 21 | # load .env if not using Docker 22 | if env.bool("LOAD_ENV", default=False): 23 | env.read_env(BASE_DIR.joinpath('.env')) 24 | 25 | # Quick-start development settings - unsuitable for production 26 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 27 | 28 | # SECURITY WARNING: keep the secret key used in production secret! 29 | SECRET_KEY = env("DJANGO_SECRET_KEY") 30 | 31 | # SECURITY WARNING: don't run with debug turned on in production! 32 | DEBUG = True 33 | 34 | ALLOWED_HOSTS = [] 35 | 36 | SITE_ID = 1 37 | 38 | # Application definition 39 | 40 | INSTALLED_APPS = [ 41 | 'django.contrib.admin', 42 | 'django.contrib.auth', 43 | 'django.contrib.contenttypes', 44 | 'django.contrib.sessions', 45 | 'django.contrib.messages', 46 | 'django.contrib.staticfiles', 47 | 'django.contrib.sites', 48 | 49 | 'rest_framework', 50 | 51 | {%- if cookiecutter.use_wagtail == 'y' %} 52 | 'wagtail.contrib.forms', 53 | 'wagtail.contrib.redirects', 54 | 'wagtail.embeds', 55 | 'wagtail.sites', 56 | 'wagtail.users', 57 | 'wagtail.snippets', 58 | 'wagtail.documents', 59 | 'wagtail.images', 60 | 'wagtail.search', 61 | 'wagtail.admin', 62 | 'wagtail.core', 63 | 64 | 'modelcluster', 65 | 'taggit', 66 | {%- endif %} 67 | ] 68 | 69 | MIDDLEWARE = [ 70 | 'django.middleware.security.SecurityMiddleware', 71 | 'django.contrib.sessions.middleware.SessionMiddleware', 72 | 'django.middleware.common.CommonMiddleware', 73 | 'django.middleware.csrf.CsrfViewMiddleware', 74 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 75 | 'django.contrib.messages.middleware.MessageMiddleware', 76 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 77 | {%- if cookiecutter.use_wagtail == 'y' %} 78 | 'wagtail.core.middleware.SiteMiddleware', 79 | 'wagtail.contrib.redirects.middleware.RedirectMiddleware', 80 | {%- endif %} 81 | ] 82 | 83 | ROOT_URLCONF = '{{cookiecutter.project_slug}}.urls' 84 | 85 | TEMPLATES = [ 86 | { 87 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 88 | 'DIRS': [BASE_DIR.joinpath('templates')], 89 | 'APP_DIRS': True, 90 | 'OPTIONS': { 91 | 'context_processors': [ 92 | 'django.template.context_processors.debug', 93 | 'django.template.context_processors.request', 94 | 'django.contrib.auth.context_processors.auth', 95 | 'django.contrib.messages.context_processors.messages', 96 | ], 97 | }, 98 | }, 99 | ] 100 | 101 | WSGI_APPLICATION = 'deploy.wsgi.application' 102 | 103 | 104 | # Database 105 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 106 | 107 | DATABASES = { 108 | 'default': env.db(), 109 | } 110 | 111 | 112 | # Password validation 113 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 114 | 115 | AUTH_PASSWORD_VALIDATORS = [ 116 | { 117 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 118 | }, 119 | { 120 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 121 | }, 122 | { 123 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 124 | }, 125 | { 126 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 127 | }, 128 | ] 129 | 130 | 131 | # Internationalization 132 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 133 | 134 | LANGUAGE_CODE = 'en' 135 | 136 | TIME_ZONE = '{{cookiecutter.timezone}}' 137 | 138 | USE_I18N = True 139 | 140 | USE_L10N = True 141 | 142 | USE_TZ = True 143 | 144 | 145 | # Static files (CSS, JavaScript, Images) 146 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 147 | 148 | STATIC_URL = env('DJANGO_STATIC_URL', default='/static/') 149 | STATIC_ROOT = BASE_DIR.joinpath('serve_static') 150 | STATICFILES_DIRS = ( 151 | BASE_DIR.joinpath('static'), 152 | ) 153 | MEDIA_URL = env('DJANGO_MEDIA_URL', default='/media/') 154 | MEDIA_ROOT = BASE_DIR.joinpath('media') 155 | 156 | {%- if cookiecutter.use_wagtail == 'y' %} 157 | WAGTAIL_SITE_NAME = '{{cookiecutter.project_name}}' 158 | {%- endif %} 159 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/settings/dev.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from .base import * 4 | 5 | DEBUG = True 6 | 7 | INSTALLED_APPS += ["debug_toolbar"] # noqa F405 8 | MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405 9 | 10 | DEBUG_TOOLBAR_CONFIG = { 11 | "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], 12 | "SHOW_TEMPLATE_CONTEXT": True, 13 | } 14 | 15 | DEBUG_TOOLBAR_PANELS = [ 16 | 'debug_toolbar.panels.versions.VersionsPanel', 17 | 'debug_toolbar.panels.timer.TimerPanel', 18 | 'debug_toolbar.panels.settings.SettingsPanel', 19 | 'debug_toolbar.panels.headers.HeadersPanel', 20 | 'debug_toolbar.panels.request.RequestPanel', 21 | 'debug_toolbar.panels.sql.SQLPanel', 22 | 'debug_toolbar.panels.staticfiles.StaticFilesPanel', 23 | 'debug_toolbar.panels.templates.TemplatesPanel', 24 | 'debug_toolbar.panels.cache.CachePanel', 25 | 'debug_toolbar.panels.signals.SignalsPanel', 26 | 'debug_toolbar.panels.logging.LoggingPanel', 27 | 'debug_toolbar.panels.redirects.RedirectsPanel', 28 | 'debug_toolbar.panels.profiling.ProfilingPanel', 29 | ] 30 | 31 | ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] 32 | 33 | # Set INTERNAL_IPS when using Docker 34 | hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) 35 | INTERNAL_IPS = [ip[:-1] + "1" for ip in ips] 36 | 37 | EMAIL_BACKEND = env( 38 | "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" 39 | ) 40 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/settings/production.py: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.use_sentry == 'y' %} 2 | import sentry_sdk 3 | from sentry_sdk.integrations.django import DjangoIntegration 4 | {%- endif %} 5 | 6 | from .base import * 7 | 8 | DEBUG = False 9 | 10 | ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.project_domain }}"]) 11 | 12 | LOGGING = { 13 | "version": 1, 14 | "disable_existing_loggers": False, 15 | "formatters": { 16 | "verbose": { 17 | "format": "%(levelname)s %(asctime)s %(module)s " 18 | "%(process)d %(thread)d %(message)s" 19 | } 20 | }, 21 | "handlers": { 22 | "console": { 23 | "level": "DEBUG", 24 | "class": "logging.StreamHandler", 25 | "formatter": "verbose", 26 | }, 27 | }, 28 | "loggers": { 29 | "django": { 30 | "handlers": ["console"], 31 | "level": env('DJANGO_LOG_LEVEL', default='INFO'), 32 | "propagate": True, 33 | }, 34 | }, 35 | } 36 | 37 | {%- if cookiecutter.use_gc_storage == 'y' %} 38 | # Google cloud storage 39 | DEFAULT_FILE_STORAGE = '{{ cookiecutter.project_slug }}.utils.storage.GCMediaStorage' 40 | STATICFILES_STORAGE = '{{ cookiecutter.project_slug }}.utils.storage.GCStaticStorage' 41 | 42 | GS_BUCKET_NAME = env('GS_BUCKET_NAME') 43 | {%- endif %} 44 | 45 | {%- if cookiecutter.use_sentry == 'y' %} 46 | sentry_sdk.init( 47 | dsn=env("SENTRY_DSN"), 48 | integrations=[DjangoIntegration()], 49 | environment=env('DJANGO_ENV', default='production'), 50 | ) 51 | {%- endif %} 52 | 53 | # TODO add mail settings 54 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/src/css/main.pcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @tailwind components; 4 | 5 | @tailwind utilities; 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/src/css/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: {}, 3 | variants: {}, 4 | plugins: [], 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/src/js/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantium/django-docker-tailwind/86f6bfa2c47613911da6629d92d9dc88f7f52791/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/src/js/main.js -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% load static %}{% endraw %} 2 | 3 | 4 |
5 | 6 |