├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── django_heroku ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── frontend ├── .babelrc ├── .browserslistrc ├── .eslintrc ├── .gitignore ├── .nvmrc ├── .stylelintrc.json ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── application │ │ ├── app.js │ │ └── app2.js │ ├── components │ │ └── sidebar.js │ └── styles │ │ └── index.scss ├── vendors │ ├── .gitkeep │ └── images │ │ ├── .gitkeep │ │ ├── sample.jpg │ │ └── webpack.png └── webpack │ ├── webpack.common.js │ ├── webpack.config.dev.js │ ├── webpack.config.prod.js │ └── webpack.config.watch.js ├── heroku.yml ├── manage.py └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/build 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /db.sqlite3 3 | local.py 4 | node_modules/ 5 | /venv/ 6 | .DS_Store 7 | /media 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Please remember to rename django_heroku to your project directory name 2 | FROM node:14-stretch-slim as frontend-builder 3 | 4 | WORKDIR /app/frontend 5 | 6 | COPY ./frontend/package.json /app/frontend 7 | COPY ./frontend/package-lock.json /app/frontend 8 | 9 | ENV PATH ./node_modules/.bin/:$PATH 10 | 11 | RUN npm ci 12 | 13 | COPY ./frontend . 14 | 15 | RUN npm run build 16 | 17 | ################################################################################# 18 | FROM python:3.10-slim-buster 19 | 20 | WORKDIR /app 21 | 22 | ENV PYTHONUNBUFFERED=1 \ 23 | PYTHONPATH=/app \ 24 | DJANGO_SETTINGS_MODULE=django_heroku.settings \ 25 | PORT=8000 \ 26 | WEB_CONCURRENCY=3 27 | 28 | # Install system packages required by Wagtail and Django. 29 | RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \ 30 | build-essential curl \ 31 | libpq-dev \ 32 | libmariadbclient-dev \ 33 | libjpeg62-turbo-dev \ 34 | zlib1g-dev \ 35 | libwebp-dev \ 36 | && rm -rf /var/lib/apt/lists/* 37 | 38 | RUN addgroup --system django \ 39 | && adduser --system --ingroup django django 40 | 41 | # Requirements are installed here to ensure they will be cached. 42 | COPY ./requirements.txt /requirements.txt 43 | RUN pip install -r /requirements.txt 44 | 45 | # Copy project code 46 | COPY . . 47 | COPY --from=frontend-builder /app/frontend/build /app/frontend/build 48 | 49 | RUN python manage.py collectstatic --noinput --clear 50 | 51 | # Run as non-root user 52 | RUN chown -R django:django /app 53 | USER django 54 | 55 | # Run application 56 | CMD gunicorn django_heroku.wsgi:application 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to deploy Django project to Heroku using Docker 2 | 3 | This repo is the source code of [How to deploy Django project to Heroku using Docker](https://www.accordbox.com/blog/deploy-django-project-heroku-using-docker/) 4 | 5 | 6 | -------------------------------------------------------------------------------- /django_heroku/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccordBox/django-heroku-docker/85796bf0ce5560324c1f8e93538e368e8142019a/django_heroku/__init__.py -------------------------------------------------------------------------------- /django_heroku/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for django_heroku project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_heroku.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /django_heroku/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_heroku project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | import os 13 | import environ 14 | from pathlib import Path 15 | 16 | env = environ.Env() 17 | 18 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 19 | BASE_DIR = Path(__file__).resolve().parent.parent 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = env('DJANGO_SECRET_KEY', default='django-insecure-$lko+#jpt#ehi5=ms9(6s%&6fsg%r2ag2xu_2zj1ibsj$pckud') 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = env.bool("DJANGO_DEBUG", True) 30 | 31 | ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[]) 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 43 | 'webpack_boilerplate', 44 | 'storages', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'whitenoise.middleware.WhiteNoiseMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | 58 | ROOT_URLCONF = 'django_heroku.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'django_heroku.wsgi.application' 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 81 | 82 | DATABASES = { 83 | 'default': { 84 | 'ENGINE': 'django.db.backends.sqlite3', 85 | 'NAME': BASE_DIR / 'db.sqlite3', 86 | } 87 | } 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 91 | 92 | AUTH_PASSWORD_VALIDATORS = [ 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 104 | }, 105 | ] 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 110 | 111 | LANGUAGE_CODE = 'en-us' 112 | 113 | TIME_ZONE = 'UTC' 114 | 115 | USE_I18N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 122 | 123 | STATIC_URL = 'static/' 124 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 125 | 126 | STATICFILES_DIRS = [ 127 | os.path.join(BASE_DIR, 'frontend/build'), 128 | ] 129 | 130 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" 131 | 132 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 133 | MEDIA_URL = '/media/' 134 | 135 | # Default primary key field type 136 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 137 | 138 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 139 | 140 | if 'AWS_STORAGE_BUCKET_NAME' in env: 141 | AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME') 142 | AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME 143 | AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID') 144 | AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY') 145 | AWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME') 146 | AWS_DEFAULT_ACL = 'public-read' 147 | AWS_S3_FILE_OVERWRITE = False 148 | 149 | MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN 150 | DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' 151 | 152 | if "DATABASE_URL" in env: 153 | DATABASES['default'] = env.db('DATABASE_URL') 154 | DATABASES["default"]["ATOMIC_REQUESTS"] = True 155 | -------------------------------------------------------------------------------- /django_heroku/urls.py: -------------------------------------------------------------------------------- 1 | """django_heroku URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /django_heroku/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_heroku 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/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_heroku.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": "3.0.0" 8 | } 9 | ] 10 | ], 11 | "plugins": [ 12 | "@babel/plugin-syntax-dynamic-import", 13 | "@babel/plugin-proposal-class-properties" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | [production staging] 2 | >5% 3 | last 2 versions 4 | Firefox ESR 5 | not ie < 11 6 | 7 | [development] 8 | last 1 chrome version 9 | last 1 firefox version 10 | last 1 edge version 11 | -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended" 5 | ], 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 6, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "semi": 2 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | build 6 | 7 | # misc 8 | .DS_Store 9 | 10 | npm-debug.log 11 | yarn-error.log 12 | yarn.lock 13 | .yarnclean 14 | .vscode 15 | .idea 16 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/fermium 2 | -------------------------------------------------------------------------------- /frontend/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "plugins": [ 4 | "stylelint-scss" 5 | ], 6 | "rules": { 7 | "at-rule-no-unknown": null, 8 | "scss/at-rule-no-unknown": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This project was created with [python-webpack-boilerplate](https://github.com/AccordBox/python-webpack-boilerplate) 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm run start` 10 | 11 | `npm run start` will launch a server process, which makes `live reloading` possible. 12 | 13 | If you change JS or SCSS files, the web page would auto refresh after the change. Now the server is working on port 9091 by default, but you can change it in `webpack/webpack.config.dev.js` 14 | 15 | ### `npm run watch` 16 | 17 | run webpack in `watch` mode. 18 | 19 | ### `npm run build` 20 | 21 | [production mode](https://webpack.js.org/guides/production/), Webpack would focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. 22 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "python-webpack-boilerplate", 3 | "version": "0.0.7", 4 | "description": "Webpack boilerplate for Django & Flask", 5 | "scripts": { 6 | "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.config.prod.js", 7 | "start": "webpack serve --config webpack/webpack.config.dev.js", 8 | "watch": "webpack --watch --config webpack/webpack.config.watch.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/AccordBox/python-webpack-boilerplate" 13 | }, 14 | "keywords": [ 15 | "webpack", 16 | "startkit", 17 | "frontend" 18 | ], 19 | "author": "Michael Yin", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/AccordBox/python-webpack-boilerplate/issues" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.15.0", 26 | "@babel/plugin-proposal-class-properties": "^7.14.5", 27 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 28 | "@babel/preset-env": "^7.15.0", 29 | "babel-eslint": "^10.1.0", 30 | "babel-loader": "^8.2.2", 31 | "clean-webpack-plugin": "^3.0.0", 32 | "copy-webpack-plugin": "^9.0.1", 33 | "cross-env": "^7.0.3", 34 | "css-loader": "^5.2.7", 35 | "eslint": "^7.32.0", 36 | "eslint-webpack-plugin": "^3.0.1", 37 | "file-loader": "^6.2.0", 38 | "mini-css-extract-plugin": "^2.2.0", 39 | "sass": "~1.32.0", 40 | "postcss-loader": "^6.1.1", 41 | "postcss-preset-env": "^6.7.0", 42 | "sass-loader": "^12.1.0", 43 | "style-loader": "^3.2.1", 44 | "stylelint": "^13.13.1", 45 | "stylelint-config-standard": "^22.0.0", 46 | "stylelint-webpack-plugin": "^3.0.1", 47 | "stylelint-scss": "^3.18.0", 48 | "webpack": "^5.50.0", 49 | "webpack-assets-manifest": "^5.0.1", 50 | "webpack-cli": "^4.7.2", 51 | "webpack-dev-server": "^3.11.2", 52 | "webpack-merge": "^5.8.0" 53 | }, 54 | "dependencies": { 55 | "@babel/polyfill": "^7.12.1", 56 | "bootstrap": "^5.1.1", 57 | "core-js": "^3.16.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | const postcssPresetEnv = require("postcss-preset-env"); 2 | 3 | module.exports = { 4 | plugins: [postcssPresetEnv()], 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/application/app.js: -------------------------------------------------------------------------------- 1 | // This is the scss entry file 2 | import "../styles/index.scss"; 3 | 4 | // We can import Bootstrap JS instead of the CDN link, if you do not use 5 | // Bootstrap, please feel free to remove it. 6 | import "bootstrap/dist/js/bootstrap.bundle"; 7 | 8 | // We can import other JS file as we like 9 | import "../components/sidebar"; 10 | 11 | window.document.addEventListener("DOMContentLoaded", function () { 12 | window.console.log("dom ready 1"); 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/src/application/app2.js: -------------------------------------------------------------------------------- 1 | // import scss entry file here for app2.js 2 | 3 | // import other JS components as we like 4 | import "../components/sidebar"; 5 | 6 | window.document.addEventListener("DOMContentLoaded", function () { 7 | window.console.log("dom ready 2"); 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/components/sidebar.js: -------------------------------------------------------------------------------- 1 | window.console.log("sidebar is loaded"); 2 | -------------------------------------------------------------------------------- /frontend/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | // If you comment out code below, bootstrap will use red as primary color 2 | // and btn-primary will become red 3 | 4 | // $primary: red; 5 | 6 | @import "~bootstrap/scss/bootstrap.scss"; 7 | 8 | .jumbotron { 9 | // should be relative path of the entry scss file 10 | background-image: url("../../vendors/images/sample.jpg"); 11 | background-size: cover; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/vendors/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccordBox/django-heroku-docker/85796bf0ce5560324c1f8e93538e368e8142019a/frontend/vendors/.gitkeep -------------------------------------------------------------------------------- /frontend/vendors/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccordBox/django-heroku-docker/85796bf0ce5560324c1f8e93538e368e8142019a/frontend/vendors/images/.gitkeep -------------------------------------------------------------------------------- /frontend/vendors/images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccordBox/django-heroku-docker/85796bf0ce5560324c1f8e93538e368e8142019a/frontend/vendors/images/sample.jpg -------------------------------------------------------------------------------- /frontend/vendors/images/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AccordBox/django-heroku-docker/85796bf0ce5560324c1f8e93538e368e8142019a/frontend/vendors/images/webpack.png -------------------------------------------------------------------------------- /frontend/webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob"); 2 | const Path = require("path"); 3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 4 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 5 | const WebpackAssetsManifest = require("webpack-assets-manifest"); 6 | 7 | const getEntryObject = () => { 8 | const entries = {}; 9 | glob.sync("src/application/*.js").forEach((path) => { 10 | const name = Path.basename(path, ".js"); 11 | entries[name] = Path.resolve(__dirname, `../${path}`); 12 | }); 13 | return entries; 14 | }; 15 | 16 | module.exports = { 17 | entry: getEntryObject(), 18 | output: { 19 | path: Path.join(__dirname, "../build"), 20 | filename: "js/[name].js", 21 | publicPath: "/static/", 22 | }, 23 | optimization: { 24 | splitChunks: { 25 | chunks: "all", 26 | }, 27 | 28 | runtimeChunk: "single", 29 | }, 30 | plugins: [ 31 | new CleanWebpackPlugin(), 32 | new CopyWebpackPlugin({ 33 | patterns: [ 34 | { from: Path.resolve(__dirname, "../vendors"), to: "vendors" }, 35 | ], 36 | }), 37 | new WebpackAssetsManifest({ 38 | entrypoints: true, 39 | output: "manifest.json", 40 | writeToDisk: true, 41 | publicPath: true, 42 | }), 43 | ], 44 | resolve: { 45 | alias: { 46 | "~": Path.resolve(__dirname, "../src"), 47 | }, 48 | }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.mjs$/, 53 | include: /node_modules/, 54 | type: "javascript/auto", 55 | }, 56 | { 57 | test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/, 58 | use: { 59 | loader: "file-loader", 60 | options: { 61 | name: "[path][name].[ext]", 62 | }, 63 | }, 64 | }, 65 | ], 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /frontend/webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const Path = require("path"); 2 | const Webpack = require("webpack"); 3 | const { merge } = require("webpack-merge"); 4 | const StylelintPlugin = require("stylelint-webpack-plugin"); 5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const ESLintPlugin = require("eslint-webpack-plugin"); 7 | 8 | const common = require("./webpack.common.js"); 9 | 10 | module.exports = merge(common, { 11 | target: "web", 12 | mode: "development", 13 | devtool: "inline-source-map", 14 | output: { 15 | chunkFilename: "js/[name].chunk.js", 16 | publicPath: "http://localhost:9091/", 17 | }, 18 | devServer: { 19 | inline: true, 20 | hot: true, 21 | host: "0.0.0.0", 22 | port: 9091, 23 | headers: { 24 | "Access-Control-Allow-Origin": "*", 25 | }, 26 | writeToDisk: true, 27 | }, 28 | plugins: [ 29 | new Webpack.DefinePlugin({ 30 | "process.env.NODE_ENV": JSON.stringify("development"), 31 | }), 32 | new StylelintPlugin({ 33 | files: Path.join("src", "**/*.s?(a|c)ss"), 34 | }), 35 | new ESLintPlugin({ 36 | extensions: "js", 37 | emitWarning: true, 38 | files: Path.resolve(__dirname, "../src"), 39 | }), 40 | new MiniCssExtractPlugin({ filename: "css/[name].css" }), 41 | ], 42 | module: { 43 | rules: [ 44 | { 45 | test: /\.html$/i, 46 | loader: "html-loader", 47 | }, 48 | { 49 | test: /\.js$/, 50 | include: Path.resolve(__dirname, "../src"), 51 | loader: "babel-loader", 52 | }, 53 | { 54 | test: /\.s?css$/i, 55 | use: [ 56 | MiniCssExtractPlugin.loader, 57 | { 58 | loader: "css-loader", 59 | options: { 60 | sourceMap: true, 61 | }, 62 | }, 63 | "postcss-loader", 64 | "sass-loader", 65 | ], 66 | }, 67 | ], 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /frontend/webpack/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const Webpack = require("webpack"); 2 | const { merge } = require("webpack-merge"); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const common = require("./webpack.common.js"); 5 | 6 | module.exports = merge(common, { 7 | mode: "production", 8 | devtool: "source-map", 9 | bail: true, 10 | output: { 11 | filename: "js/[name].[chunkhash:8].js", 12 | chunkFilename: "js/[name].[chunkhash:8].chunk.js", 13 | }, 14 | plugins: [ 15 | new Webpack.DefinePlugin({ 16 | "process.env.NODE_ENV": JSON.stringify("production"), 17 | }), 18 | new MiniCssExtractPlugin({ 19 | filename: "css/[name].[contenthash].css", 20 | }), 21 | ], 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.js$/, 26 | exclude: /node_modules/, 27 | use: "babel-loader", 28 | }, 29 | { 30 | test: /\.s?css/i, 31 | use: [ 32 | MiniCssExtractPlugin.loader, 33 | "css-loader", 34 | "postcss-loader", 35 | "sass-loader", 36 | ], 37 | }, 38 | ], 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /frontend/webpack/webpack.config.watch.js: -------------------------------------------------------------------------------- 1 | const Path = require("path"); 2 | const Webpack = require("webpack"); 3 | const { merge } = require("webpack-merge"); 4 | const StylelintPlugin = require("stylelint-webpack-plugin"); 5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const ESLintPlugin = require("eslint-webpack-plugin"); 7 | 8 | const common = require("./webpack.common.js"); 9 | 10 | module.exports = merge(common, { 11 | target: "web", 12 | mode: "development", 13 | devtool: "inline-source-map", 14 | output: { 15 | chunkFilename: "js/[name].chunk.js", 16 | }, 17 | plugins: [ 18 | new Webpack.DefinePlugin({ 19 | "process.env.NODE_ENV": JSON.stringify("development"), 20 | }), 21 | new StylelintPlugin({ 22 | files: Path.join("src", "**/*.s?(a|c)ss"), 23 | }), 24 | new ESLintPlugin({ 25 | extensions: "js", 26 | emitWarning: true, 27 | files: Path.resolve(__dirname, "../src"), 28 | }), 29 | new MiniCssExtractPlugin({ filename: "css/[name].css" }), 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.html$/i, 35 | loader: "html-loader", 36 | }, 37 | { 38 | test: /\.js$/, 39 | include: Path.resolve(__dirname, "../src"), 40 | loader: "babel-loader", 41 | }, 42 | { 43 | test: /\.s?css$/i, 44 | use: [ 45 | MiniCssExtractPlugin.loader, 46 | { 47 | loader: "css-loader", 48 | options: { 49 | sourceMap: true, 50 | }, 51 | }, 52 | "postcss-loader", 53 | "sass-loader", 54 | ], 55 | }, 56 | ], 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile 4 | release: 5 | image: web 6 | command: 7 | - django-admin migrate --noinput 8 | -------------------------------------------------------------------------------- /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 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_heroku.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==4.0 2 | python-webpack-boilerplate==0.0.7 3 | django-environ==0.8.1 4 | gunicorn==20.1.0 5 | whitenoise==5.3.0 6 | psycopg2-binary==2.9.2 7 | boto3==1.16.56 8 | django-storages==1.11.1 --------------------------------------------------------------------------------