├── backend
├── common
│ ├── __init__.py
│ ├── utils
│ │ ├── __init__.py
│ │ └── tests.py
│ ├── management
│ │ ├── __init__.py
│ │ └── commands
│ │ │ ├── __init__.py
│ │ │ └── celery.py
│ ├── routes.py
│ ├── urls.py
│ ├── context_processors.py
│ ├── models.py
│ └── views.py
├── users
│ ├── __init__.py
│ ├── tests
│ │ └── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── views.py
│ ├── apps.py
│ ├── tasks.py
│ ├── managers.py
│ ├── admin.py
│ └── models.py
├── project_name
│ ├── settings
│ │ ├── __init__.py
│ │ ├── local.py.example
│ │ ├── test.py
│ │ ├── local_base.py
│ │ ├── production.py
│ │ └── base.py
│ ├── __init__.py
│ ├── celerybeat_schedule.py
│ ├── wsgi.py
│ ├── urls.py
│ └── celery.py
├── templates
│ ├── includes
│ │ └── sentry_init.html
│ ├── common
│ │ └── index.html
│ └── base.html
├── .env.example
├── Dockerfile
└── manage.py
├── frontend
├── js
│ ├── routes
│ │ └── index.js
│ ├── constants
│ │ └── index.js
│ ├── utils
│ │ ├── urls.js
│ │ ├── index.js
│ │ └── SentryBoundary.js
│ ├── app
│ │ └── example-app
│ │ │ ├── index.js
│ │ │ └── components
│ │ │ ├── index.js
│ │ │ ├── ColorChanger
│ │ │ ├── index.js
│ │ │ ├── style.scss
│ │ │ ├── __tests__
│ │ │ │ ├── ColorChanger.snaps.spec.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── ColorChanger.snaps.spec.js.snap
│ │ │ └── ColorChanger.js
│ │ │ └── ColorDisplay
│ │ │ ├── index.js
│ │ │ ├── ColorDisplay.js
│ │ │ ├── style.scss
│ │ │ └── __tests__
│ │ │ ├── __snapshots__
│ │ │ └── ColorDisplay.snaps.spec.js.snap
│ │ │ └── ColorDisplay.snaps.spec.js
│ ├── store
│ │ ├── reducers.js
│ │ ├── api.js
│ │ ├── index.js
│ │ └── rest_check.js
│ ├── index.js
│ ├── App.js
│ └── pages
│ │ └── Home.js
├── sass
│ ├── _global.scss
│ ├── _variables.scss
│ ├── helpers
│ │ ├── _functions.scss
│ │ ├── _helpers.scss
│ │ ├── _mixins.scss
│ │ ├── _typography.scss
│ │ ├── _placeholders.scss
│ │ └── _all.scss
│ ├── vendor
│ │ ├── custom-bootstrap.scss
│ │ └── _bootstrap-includes.scss
│ ├── pages
│ │ └── _all.scss
│ ├── components
│ │ └── _all.scss
│ └── style.scss
├── assets
│ └── images
│ │ ├── django-logo-negative.png
│ │ └── django-logo-positive.png
└── Dockerfile
├── runtime.txt
├── .github
├── workflows
│ ├── nightly.yml
│ ├── main.yml
│ ├── deploy.yml
│ └── shared-build
│ │ └── action.yml
├── ISSUE_TEMPLATE
│ ├── Feature_request.md
│ └── Bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── .dockerignore
├── .eslintignore
├── postcss.config.js
├── .coveragerc
├── .bandit
├── babel.config.js
├── dev-requirements.in
├── jest-setup.js
├── bin
├── run_webpack
├── run_collectstatic
└── post_compile
├── pyproject.toml
├── Procfile
├── .isort.cfg
├── requirements.in
├── .editorconfig
├── .eslintrc.js
├── server.js
├── .gitignore
├── .prospector.yaml
├── jest.config.js
├── LICENSE.txt
├── webpack.base.config.js
├── docker-compose.yml
├── CONTRIBUTING.md
├── .pre-commit-config.yaml
├── app.json
├── webpack.prod.config.js
├── Makefile
├── webpack.local.config.js
├── .bootstraprc
├── package.json
├── proj_main.yml
├── CODE_OF_CONDUCT.md
└── README.md
/backend/common/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/users/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/js/routes/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/_global.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.9.0
2 |
--------------------------------------------------------------------------------
/backend/common/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/users/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/js/constants/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/_variables.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/workflows/nightly.yml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/backend/common/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/users/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/helpers/_functions.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/helpers/_helpers.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/helpers/_mixins.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/helpers/_typography.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/project_name/settings/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/helpers/_placeholders.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/sass/vendor/custom-bootstrap.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/common/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .db
2 | node_modules
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/frontend/sass/pages/_all.scss:
--------------------------------------------------------------------------------
1 | // Pages
2 |
3 | // @import "";
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | frontend/bundles/
2 | frontend/webpack_bundles/
3 |
--------------------------------------------------------------------------------
/frontend/sass/components/_all.scss:
--------------------------------------------------------------------------------
1 | // Components
2 |
3 | // @import "";
--------------------------------------------------------------------------------
/backend/project_name/__init__.py:
--------------------------------------------------------------------------------
1 | from .celery import app as celery_app # noqa
2 |
--------------------------------------------------------------------------------
/backend/project_name/settings/local.py.example:
--------------------------------------------------------------------------------
1 | from .local_base import * # noqa
2 |
--------------------------------------------------------------------------------
/frontend/js/utils/urls.js:
--------------------------------------------------------------------------------
1 | const { Urls } = window;
2 |
3 | export default Urls;
4 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/backend/users/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render # noqa
2 |
3 |
4 | # Create your views here.
5 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/index.js:
--------------------------------------------------------------------------------
1 | import ColorChanger from './components';
2 |
3 | export default ColorChanger;
4 |
--------------------------------------------------------------------------------
/backend/users/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class UsersConfig(AppConfig):
5 | name = "users"
6 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/index.js:
--------------------------------------------------------------------------------
1 | import ColorChanger from './ColorChanger';
2 |
3 | export default ColorChanger;
4 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | omit = */virtualenvs/*
4 | */migrations/*
5 | */settings/*
6 | */tests/*
7 |
--------------------------------------------------------------------------------
/frontend/js/utils/index.js:
--------------------------------------------------------------------------------
1 | import Urls from './urls';
2 |
3 | export { Urls }; // eslint-disable-line import/prefer-default-export
4 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorChanger/index.js:
--------------------------------------------------------------------------------
1 | import ColorChanger from './ColorChanger';
2 |
3 | export default ColorChanger;
4 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorDisplay/index.js:
--------------------------------------------------------------------------------
1 | import ColorDisplay from './ColorDisplay';
2 |
3 | export default ColorDisplay;
4 |
--------------------------------------------------------------------------------
/backend/common/routes.py:
--------------------------------------------------------------------------------
1 | from .views import RestViewSet
2 |
3 | routes = [
4 | {'regex': r'rest', 'viewset': RestViewSet, 'basename': 'Rest'},
5 | ]
6 |
--------------------------------------------------------------------------------
/.bandit:
--------------------------------------------------------------------------------
1 | [bandit]
2 | exclude: test_*.py,./venv/,./env/,./node_modules/,./cacheback/,./.env,./.venv
3 | skips: B101,B311
4 | # B101: assert
5 | # B311: random
6 |
--------------------------------------------------------------------------------
/backend/templates/includes/sentry_init.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/images/django-logo-negative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stack-guru/django-react-boilerplate/HEAD/frontend/assets/images/django-logo-negative.png
--------------------------------------------------------------------------------
/frontend/assets/images/django-logo-positive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stack-guru/django-react-boilerplate/HEAD/frontend/assets/images/django-logo-positive.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | presets: ['@babel/preset-env', '@babel/preset-react'],
5 | plugins: ['react-hot-loader/babel'],
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/sass/helpers/_all.scss:
--------------------------------------------------------------------------------
1 | // Helpers
2 |
3 | @import "typography";
4 | @import "functions";
5 | @import "placeholders";
6 | @import "helpers";
7 | @import "mixins";
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14.5.0-alpine
2 |
3 | WORKDIR /app/
4 | ADD package.json /app/package.json
5 | RUN npm install
6 | ADD . /app/
7 |
8 | CMD ["npm", "run", "start"]
9 |
--------------------------------------------------------------------------------
/backend/common/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = 'common'
6 | urlpatterns = [
7 | path('', views.IndexView.as_view(), name='index'),
8 | ]
--------------------------------------------------------------------------------
/dev-requirements.in:
--------------------------------------------------------------------------------
1 | bandit
2 | black
3 | coverage
4 | django>=3,<4
5 | flake8==3.8.4
6 | isort
7 | model-bakery
8 | pre-commit
9 | pylint-django
10 | prospector[with_vulture]
11 | safety
12 |
--------------------------------------------------------------------------------
/backend/users/tasks.py:
--------------------------------------------------------------------------------
1 | from django.core import management
2 |
3 | from {{project_name}} import celery_app
4 |
5 |
6 | @celery_app.task
7 | def clearsessions():
8 | management.call_command('clearsessions')
9 |
--------------------------------------------------------------------------------
/jest-setup.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 |
3 | import enzyme from 'enzyme';
4 | import Adapter from 'enzyme-adapter-react-16';
5 |
6 | enzyme.configure({ adapter: new Adapter() });
7 |
--------------------------------------------------------------------------------
/bin/run_webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eo pipefail
3 |
4 | if [ ! -f webpack-stats.json ]; then
5 | touch webpack-stats.json
6 | chmod 777 webpack-stats.json
7 | echo "webpack-stats.json created"
8 | fi
9 | npm run build
10 |
--------------------------------------------------------------------------------
/backend/common/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | def sentry_dsn(request):
5 | return {"SENTRY_DSN": settings.SENTRY_DSN}
6 |
7 |
8 | def commit_sha(request):
9 | return {"COMMIT_SHA": settings.COMMIT_SHA}
10 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | DJANGO_SETTINGS_MODULE={{project_name}}.settings.local
2 | CELERY_BROKER_URL=amqp://broker:5672//
3 | # Please choose postgres or sqlite as your DB:
4 | # DATABASE_URL=postgres://{{project_name}}:password@db:5432/{{project_name}}
5 | # DATABASE_URL=sqlite:///backend/db.sqlite3
6 |
--------------------------------------------------------------------------------
/backend/project_name/celerybeat_schedule.py:
--------------------------------------------------------------------------------
1 | from celery.schedules import crontab # pylint:disable=import-error,no-name-in-module
2 |
3 |
4 | CELERYBEAT_SCHEDULE = {
5 | # Internal tasks
6 | "clearsessions": {"schedule": crontab(hour=3, minute=0), "task": "users.tasks.clearsessions"},
7 | }
8 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 100
3 | target-version = ['py36']
4 | quiet = true
5 | exclude = '''
6 | /(
7 | \.eggs
8 | | \.git
9 | | \.hg
10 | | \.mypy_cache
11 | | \.venv
12 | | _build
13 | | build
14 | | dist
15 | | [a-z_]+/migrations
16 | )/
17 | '''
18 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn {{project_name}}.wsgi --chdir backend --limit-request-line 8188 --log-file -
2 | worker: REMAP_SIGTERM=SIGQUIT celery --workdir backend --app={{project_name}} worker --loglevel=info
3 | beat: REMAP_SIGTERM=SIGQUIT celery --workdir backend --app={{project_name}} beat -S redbeat.RedBeatScheduler --loglevel=info
4 |
--------------------------------------------------------------------------------
/.isort.cfg:
--------------------------------------------------------------------------------
1 | [settings]
2 | line_length=100
3 | multi_line_output=3
4 | include_trailing_comma=True
5 | force_grid_wrap=0
6 | use_parentheses=True
7 | known_django=django
8 | sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
9 | lines_after_imports=2
10 | atomic=True
11 | combine_star=True
12 | skip=venv,env,node_modules,migrations,.env,.venv
13 |
--------------------------------------------------------------------------------
/frontend/js/store/reducers.js:
--------------------------------------------------------------------------------
1 | import { connectRouter } from 'connected-react-router';
2 | import { combineReducers } from 'redux';
3 |
4 | import { restCheckReducer as restCheck } from './rest_check';
5 |
6 | export const createRootReducer = (history) => {
7 | return combineReducers({
8 | router: connectRouter(history),
9 | restCheck,
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 | on:
3 | push:
4 | branches-ignore:
5 | - boilerplate-release
6 | pull_request:
7 |
8 | jobs:
9 | build:
10 | name: Build boilerplate code
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v2
15 | - uses: ./.github/workflows/shared-build
16 |
--------------------------------------------------------------------------------
/frontend/js/store/api.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import cookie from 'cookie';
3 |
4 | const api = axios.create();
5 | api.interceptors.request.use((config) => {
6 | const { csrftoken } = cookie.parse(document.cookie);
7 | if (csrftoken) {
8 | config.headers['X-CSRFTOKEN'] = csrftoken;
9 | }
10 | return config;
11 | });
12 |
13 | export default api;
14 |
--------------------------------------------------------------------------------
/requirements.in:
--------------------------------------------------------------------------------
1 | django>=3,<4
2 | celery[redis]
3 | celery-redbeat
4 | django-model-utils
5 | django-webpack-loader>=1.0.0
6 | django-js-reverse
7 | django-import-export
8 | djangorestframework
9 | django-debreach
10 | python-decouple
11 | psycopg2
12 | brotlipy
13 | django-log-request-id
14 | dj-database-url
15 | gunicorn
16 | whitenoise
17 | psutil
18 | ipython
19 | sentry-sdk
20 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorChanger/style.scss:
--------------------------------------------------------------------------------
1 | .main-container {
2 | border: 1px dashed grey;
3 | margin: 5px;
4 | padding: 5px;
5 |
6 | .app-name {
7 | color: red;
8 | padding-top: 20px;
9 | }
10 |
11 | .inner-container {
12 | margin: 0;
13 | padding: 5px;
14 |
15 | .color-picker {
16 | margin: 0px 5px 0px 5px;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/js/index.js:
--------------------------------------------------------------------------------
1 | // import pages
2 | import * as Sentry from '@sentry/browser';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 |
6 | import '../sass/style.scss';
7 |
8 | import App from './App';
9 |
10 | Sentry.init({
11 | dsn: window.SENTRY_DSN,
12 | release: window.COMMIT_SHA,
13 | });
14 |
15 | ReactDOM.render(, document.getElementById('react-app'));
16 |
--------------------------------------------------------------------------------
/backend/templates/common/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 |
4 | {% block body %}
5 |
Django React Boilerplate Example App
6 |
7 | The setup of the Django React Boilerplate Example App was successful.
8 |
9 | React Example App
10 | Here is an example of a React app included in the Django framework:
11 |
12 |
13 | {% endblock %}
14 |
--------------------------------------------------------------------------------
/backend/common/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 | from model_utils.fields import AutoCreatedField, AutoLastModifiedField
5 |
6 |
7 | class IndexedTimeStampedModel(models.Model):
8 | created = AutoCreatedField(_("created"), db_index=True)
9 | modified = AutoLastModifiedField(_("modified"), db_index=True)
10 |
11 | class Meta:
12 | abstract = True
13 |
--------------------------------------------------------------------------------
/bin/run_collectstatic:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eo pipefail
3 |
4 | indent() {
5 | RE="s/^/ /"
6 | [ $(uname) == "Darwin" ] && sed -l "$RE" || sed -u "$RE"
7 | }
8 |
9 | MANAGE_FILE=$(find . -maxdepth 3 -type f -name 'manage.py' | head -1)
10 | MANAGE_FILE=${MANAGE_FILE:2}
11 |
12 | echo "-----> Collecting static files"
13 | python $MANAGE_FILE collectstatic --noinput 2>&1 | sed '/^Copying/d;/^$/d;/^ /d' | indent
14 |
15 | echo
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | end_of_line = lf
12 |
13 | [*.js]
14 | indent_size = 2
15 |
16 | [*.html]
17 | indent_size = 2
18 |
19 | [*.scss]
20 | indent_size = 2
21 |
22 | [*.md]
23 | trim_trailing_whitespace = false
24 |
25 | [Makefile]
26 | indent_style = tab
27 |
28 | [*.yml]
29 | indent_size = 2
30 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorDisplay/ColorDisplay.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import './style.scss';
5 |
6 | const ColorDisplay = (props) => {
7 | const { color } = props;
8 |
9 | return {color};
10 | };
11 |
12 | ColorDisplay.defaultProps = {
13 | color: 'black',
14 | };
15 |
16 | ColorDisplay.propTypes = {
17 | color: PropTypes.string,
18 | };
19 |
20 | export default ColorDisplay;
21 |
--------------------------------------------------------------------------------
/frontend/js/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { hot } from 'react-hot-loader/root';
3 | import { Provider } from 'react-redux';
4 |
5 | import Home from './pages/Home';
6 | import configureStore from './store';
7 | import SentryBoundary from './utils/SentryBoundary';
8 |
9 | const store = configureStore({});
10 | const App = () => (
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
18 | export default hot(App);
19 |
--------------------------------------------------------------------------------
/backend/project_name/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for {{project_name}} project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 |
15 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{project_name}}.settings.production")
16 |
17 | application = get_wsgi_application()
18 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorDisplay/style.scss:
--------------------------------------------------------------------------------
1 | %text-color {
2 | font-weight: bold;
3 | margin: 0px 5px;
4 | };
5 |
6 | .color-black {
7 | @extend %text-color;
8 | color: black;
9 | }
10 |
11 | .color-green {
12 | @extend %text-color;
13 | color: green;
14 | }
15 |
16 | .color-red {
17 | @extend %text-color;
18 | color: red;
19 | }
20 |
21 | .color-blue {
22 | @extend %text-color;
23 | color: blue;
24 | }
25 |
26 | .color-purple {
27 | @extend %text-color;
28 | color: purple;
29 | }
30 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | root: true,
5 | extends: ['vinta/recommended'],
6 | rules: {
7 | "default-param-last": "off",
8 | },
9 | env: {
10 | es6: true,
11 | browser: true,
12 | jest: true
13 | },
14 | settings: {
15 | 'import/resolver': {
16 | webpack: {
17 | config: path.join(__dirname, '/webpack.local.config.js'),
18 | 'config-index': 1
19 | }
20 | },
21 | react: {
22 | "version": "detect"
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // Webpack dev server
2 | import webpack from 'webpack';
3 | import WebpackDevServer from 'webpack-dev-server';
4 |
5 | import config from './webpack.local.config';
6 |
7 | new WebpackDevServer(webpack(config), {
8 | publicPath: config.output.publicPath,
9 | port: 3000,
10 | hot: true,
11 | inline: true,
12 | historyApiFallback: true,
13 | headers: { 'Access-Control-Allow-Origin': '*' },
14 | }).listen(3000, '0.0.0.0', (err) => {
15 | if (err) {
16 | console.log(err);
17 | }
18 |
19 | console.log('Listening at 0.0.0.0:3000');
20 | });
21 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorDisplay/__tests__/__snapshots__/ColorDisplay.snaps.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ColorDisplay no color (should default to black) 1`] = `
4 |
7 | black
8 |
9 | `;
10 |
11 | exports[`ColorDisplay purple 1`] = `
12 |
15 | purple
16 |
17 | `;
18 |
19 | exports[`ColorDisplay unknown color 1`] = `
20 |
23 | caterpillar
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/backend/users/managers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import BaseUserManager
2 |
3 |
4 | class UserManager(BaseUserManager):
5 | def create_user(self, email, password=None, **kwargs):
6 | email = self.normalize_email(email)
7 | user = self.model(email=email, **kwargs)
8 | user.set_password(password)
9 | user.save(using=self._db)
10 | return user
11 |
12 | def create_superuser(self, **kwargs):
13 | user = self.create_user(**kwargs)
14 | user.is_superuser = True
15 | user.is_staff = True
16 | user.save(using=self._db)
17 | return user
18 |
--------------------------------------------------------------------------------
/backend/project_name/settings/test.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 |
3 |
4 | SECRET_KEY = "test"
5 |
6 | STATIC_ROOT = base_dir_join('staticfiles')
7 | STATIC_URL = '/static/'
8 |
9 | MEDIA_ROOT = base_dir_join('mediafiles')
10 | MEDIA_URL = '/media/'
11 |
12 | DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
13 | STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
14 |
15 | # Speed up password hashing
16 | PASSWORD_HASHERS = [
17 | "django.contrib.auth.hashers.MD5PasswordHasher",
18 | ]
19 |
20 | # Celery
21 | CELERY_TASK_ALWAYS_EAGER = True
22 | CELERY_TASK_EAGER_PROPAGATES = True
23 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorChanger/__tests__/ColorChanger.snaps.spec.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import React from 'react';
3 |
4 | import ColorChanger from '../ColorChanger';
5 |
6 | jest.mock('../../ColorDisplay/ColorDisplay');
7 |
8 | describe('ColorChanger', () => {
9 | test('some title', () => {
10 | const wrapper = shallow();
11 |
12 | expect(wrapper).toMatchSnapshot();
13 | });
14 |
15 | test('no title (should use default)', () => {
16 | const wrapper = shallow();
17 |
18 | expect(wrapper).toMatchSnapshot();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/frontend/sass/style.scss:
--------------------------------------------------------------------------------
1 | // Main Style
2 |
3 | // General
4 | @import "variables";
5 | @import "vendor/bootstrap-includes";
6 | @import "helpers/all";
7 | @import "global";
8 | @import "components/all";
9 | @import "pages/all";
10 |
11 | #django-background {
12 | color: #092e20;
13 | font-size: 11pt;
14 | background-repeat: no-repeat;
15 | background-size: auto 200px;
16 | background-position: center;
17 | height: 300px;
18 | background-image: url('../assets/images/django-logo-positive.png');
19 | }
20 |
21 | #django-logo-wrapper {
22 | color: #092e20;
23 | & > img {
24 | width: 100px;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/backend/project_name/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import include
2 | from django.urls import path
3 | from django.contrib import admin
4 |
5 | import django_js_reverse.views
6 | from rest_framework.routers import DefaultRouter
7 |
8 | from common.routes import routes as common_routes
9 |
10 | router = DefaultRouter()
11 |
12 | routes = common_routes
13 | for route in routes:
14 | router.register(route['regex'], route['viewset'], basename=route['basename'])
15 |
16 | urlpatterns = [
17 | path("", include("common.urls"), name="common"),
18 | path("admin/", admin.site.urls, name="admin"),
19 | path("jsreverse/", django_js_reverse.views.urls_js, name="js_reverse"),
20 |
21 | path("api/", include(router.urls), name="api"),
22 | ]
23 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorDisplay/__tests__/ColorDisplay.snaps.spec.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import React from 'react';
3 |
4 | import ColorDisplay from '../ColorDisplay';
5 |
6 | describe('ColorDisplay', () => {
7 | test('purple', () => {
8 | const wrapper = shallow();
9 |
10 | expect(wrapper).toMatchSnapshot();
11 | });
12 |
13 | test('no color (should default to black)', () => {
14 | const wrapper = shallow();
15 |
16 | expect(wrapper).toMatchSnapshot();
17 | });
18 |
19 | test('unknown color', () => {
20 | const wrapper = shallow();
21 |
22 | expect(wrapper).toMatchSnapshot();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/js/store/index.js:
--------------------------------------------------------------------------------
1 | import { routerMiddleware } from 'connected-react-router';
2 | import { createBrowserHistory } from 'history';
3 | import { createStore, applyMiddleware } from 'redux';
4 | import { composeWithDevTools } from 'redux-devtools-extension';
5 | import thunk from 'redux-thunk';
6 |
7 | import { createRootReducer } from './reducers';
8 |
9 | export const history = createBrowserHistory();
10 |
11 | const rootReducer = createRootReducer(history);
12 |
13 | const enhancer = composeWithDevTools(applyMiddleware(thunk, routerMiddleware(history)));
14 |
15 | const configureStore = (preloadedState) => {
16 | const store = createStore(rootReducer, preloadedState, enhancer);
17 | return store;
18 | };
19 |
20 | export default configureStore;
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | /backend/db.sqlite3
3 | .DS_Store
4 | .env
5 | */settings/local.py
6 | /staticfiles/*
7 | /mediafiles/*
8 | __pycache__/
9 | /.vscode/
10 |
11 | # coverage result
12 | .coverage
13 | /coverage/
14 |
15 | # pycharm
16 | .idea/
17 |
18 | # data
19 | *.dump
20 |
21 | # npm
22 | node_modules/
23 | npm-debug.log
24 |
25 | # Webpack
26 | /frontend/bundles/*
27 | /frontend/webpack_bundles/*
28 | /webpack-stats.json
29 |
30 | # Sass
31 | .sass-cache
32 | *.map
33 |
34 | # General
35 | /{{project_name}}-venv/
36 | /venv/
37 | /env/
38 | /output/
39 | /cache/
40 | boilerplate.zip
41 |
42 | # Spritesmith
43 | spritesmith-generated/
44 | spritesmith.scss
45 |
46 | # templated email
47 | tmp_email/
48 |
49 | .direnv
50 | .envrc
51 | .tool-versions
52 |
--------------------------------------------------------------------------------
/backend/common/views.py:
--------------------------------------------------------------------------------
1 | from django.views import generic
2 |
3 | from rest_framework import viewsets, status
4 | from rest_framework.decorators import action
5 | from rest_framework.response import Response
6 | from rest_framework.permissions import AllowAny
7 |
8 |
9 | class IndexView(generic.TemplateView):
10 | template_name = 'common/index.html'
11 |
12 |
13 | class RestViewSet(viewsets.ViewSet):
14 | @action(
15 | detail=False,
16 | methods=['get'],
17 | permission_classes=[AllowAny],
18 | url_path='rest-check',
19 | )
20 | def rest_check(self, request):
21 | return Response(
22 | {"result": "If you're seeing this, the REST API is working!"},
23 | status=status.HTTP_200_OK,
24 | )
--------------------------------------------------------------------------------
/backend/project_name/celery.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from django.apps import apps
5 |
6 | from celery import Celery
7 | from decouple import config
8 |
9 | from .celerybeat_schedule import CELERYBEAT_SCHEDULE
10 |
11 | settings_module = config("DJANGO_SETTINGS_MODULE", default=None)
12 | if settings_module is None:
13 | print(
14 | "Error: no DJANGO_SETTINGS_MODULE found. Will NOT start devserver. "
15 | "Remember to create .env file at project root. "
16 | "Check README for more info."
17 | )
18 | sys.exit(1)
19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
20 |
21 | app = Celery("{{project_name}}_tasks")
22 | app.config_from_object("django.conf:settings", namespace="CELERY")
23 | app.autodiscover_tasks(lambda: [n.name for n in apps.get_app_configs()])
24 | app.conf.update(CELERYBEAT_SCHEDULE=CELERYBEAT_SCHEDULE)
25 |
--------------------------------------------------------------------------------
/frontend/js/store/rest_check.js:
--------------------------------------------------------------------------------
1 | import api from './api';
2 |
3 | // Action types
4 | const types = {
5 | FETCH_REQUESTED: 'rest_check/FETCH_REQUESTED',
6 | FETCH_SUCCESS: 'rest_check/FETCH_SUCCESS',
7 | FETCH_ERROR: 'rest_check/FETCH_ERROR',
8 | };
9 |
10 | // Action creators
11 | export const creators = {
12 | fetchRestCheck: () => {
13 | return async (dispatch) => {
14 | dispatch({ type: types.FETCH_REQUESTED });
15 | try {
16 | const res = await api.get('/api/rest/rest-check/');
17 | dispatch({ type: types.FETCH_SUCCESS, data: res.data });
18 | } catch (error) {
19 | dispatch({ type: types.FETCH_ERROR, error });
20 | }
21 | };
22 | },
23 | };
24 |
25 | // Reducer
26 | export const restCheckReducer = (state = {}, action) => {
27 | if (action.type === types.FETCH_SUCCESS) return action.data;
28 | return state;
29 | };
30 |
--------------------------------------------------------------------------------
/.prospector.yaml:
--------------------------------------------------------------------------------
1 | output-format: text
2 |
3 | strictness: veryhigh
4 | test-warnings: true
5 | doc-warnings: false
6 | member-warnings: true
7 |
8 | uses:
9 | - django
10 | - celery
11 |
12 | pep8:
13 | full: true
14 | disable:
15 | - D100
16 | - D101
17 | - D102
18 | - D103
19 | - D105
20 | - D205
21 | - D400
22 | - N802 # function name should be lowercase, breaks on tests
23 | options:
24 | max-line-length: 100
25 |
26 | pyflakes:
27 | disable:
28 | - F999
29 |
30 | pylint:
31 | disable:
32 | - too-few-public-methods
33 | - invalid-name
34 | - no-self-use
35 | - no-member
36 | options:
37 | max-line-length: 100
38 |
39 | dodgy:
40 | run: true
41 |
42 | ignore-paths:
43 | - node_modules
44 | - venv
45 | - env
46 | - .env
47 | - .venv
48 |
49 | ignore-patterns:
50 | - .+/migrations/.+
51 | - .+/settings/.+
52 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | transform: {
5 | '^.+\\.jsx$': 'babel-jest',
6 | '^.+\\.js$': 'babel-jest',
7 | },
8 | moduleNameMapper: {
9 | '^.+\\.(css|scss)$': 'identity-obj-proxy',
10 | },
11 | transformIgnorePatterns: [
12 | 'node_modules/*',
13 | ],
14 | modulePaths: [
15 | 'frontend',
16 | 'frontend/js',
17 | 'frontend/js/app',
18 | ],
19 | snapshotSerializers: [
20 | 'enzyme-to-json/serializer',
21 | ],
22 | setupFiles: [
23 | './jest-setup.js',
24 | ],
25 | collectCoverageFrom: [
26 | 'frontend/js/**/*.{js,jsx}',
27 | ],
28 | coveragePathIgnorePatterns: [
29 | 'frontend/js/store.js',
30 | 'frontend/js/index.js',
31 | 'frontend/js/constants/*',
32 | 'frontend/js/pages/*',
33 | 'frontend/js/tests/*',
34 | ],
35 | coverageThreshold: {
36 | global: {
37 | statements: 10,
38 | },
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/backend/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth.admin import UserAdmin
3 | from django.utils.translation import ugettext_lazy as _
4 |
5 | from .models import User
6 |
7 |
8 | class CustomUserAdmin(UserAdmin):
9 | list_display = ("id", "email", "created", "modified")
10 | list_filter = ("is_active", "is_staff", "groups")
11 | search_fields = ("email",)
12 | ordering = ("email",)
13 | filter_horizontal = (
14 | "groups",
15 | "user_permissions",
16 | )
17 |
18 | fieldsets = (
19 | (None, {"fields": ("email", "password")}),
20 | (
21 | _("Permissions"),
22 | {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")},
23 | ),
24 | )
25 | add_fieldsets = ((None, {"classes": ("wide",), "fields": ("email", "password1", "password2")}),)
26 |
27 |
28 | admin.site.register(User, CustomUserAdmin)
29 |
--------------------------------------------------------------------------------
/backend/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load render_bundle from webpack_loader %}
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% block title %}{% endblock %}
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% render_bundle 'main' 'css' %}
16 |
17 |
18 |
19 | {% include 'includes/sentry_init.html' %}
20 |
21 |
22 | {% block body %}{% endblock %}
23 |
24 |
25 |
26 |
27 |
28 | {% render_bundle 'main' 'js' 'DEFAULT' %}
29 | {% block scripts %}{% endblock %}
30 |
31 |
32 |
--------------------------------------------------------------------------------
/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8-slim
2 |
3 | ENV PYTHONUNBUFFERED 1
4 |
5 | RUN groupadd user && useradd --create-home --home-dir /home/user -g user user
6 | WORKDIR /home/user/app/backend
7 |
8 | # Install system dependencies
9 | RUN apt-get update && apt-get install gcc build-essential libpq-dev -y && \
10 | python3 -m pip install --no-cache-dir pip-tools
11 |
12 | # install python dependencies
13 | ADD *requirements.in /home/user/app/backend/
14 | RUN pip-compile requirements.in > requirements.txt && \
15 | pip-compile dev-requirements.in > dev-requirements.txt
16 |
17 | RUN pip install -r requirements.txt && \
18 | pip install -r dev-requirements.txt && \
19 | pip install psycopg2-binary
20 |
21 | # Clean the house
22 | RUN apt-get purge libpq-dev -y && apt-get autoremove -y && \
23 | rm /var/lib/apt/lists/* rm -rf /var/cache/apt/*
24 |
25 | ADD backend/ /home/user/app/backend
26 |
27 | USER user
28 | CMD gunicorn {{project_name}}.wsgi --log-file - -b 0.0.0.0:8000 --reload
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: deploy
2 | on:
3 | workflow_run:
4 | workflows: ["main"]
5 | branches: [master]
6 | types:
7 | - completed
8 |
9 | jobs:
10 | deploy:
11 | name: Generate stable boilerplate build
12 | strategy:
13 | matrix:
14 | python-version: [3.8]
15 | node-version: [14.5]
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v2
20 | with:
21 | ref: master
22 | - run: mkdir -p github/workflows
23 | - run: mv proj_main.yml github/workflows/main.yml
24 | - run: git checkout -b boilerplate-release
25 | - run: git add github/workflows/main.yml
26 | - run: git rm proj_main.yml
27 | - run: git commit -m "Replacing project actions" --author "Vinta Software "
28 | env:
29 | GIT_COMMITTER_NAME: "Vinta Software"
30 | GIT_COMMITTER_EMAIL: "contact@vinta.com.br"
31 | - run: git push origin boilerplate-release --force
32 |
--------------------------------------------------------------------------------
/backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | import sys
5 |
6 | from decouple import config
7 |
8 |
9 | if __name__ == "__main__":
10 | settings_module = config("DJANGO_SETTINGS_MODULE", default=None)
11 |
12 | if sys.argv[1] == "test":
13 | if settings_module:
14 | print(
15 | "Ignoring config('DJANGO_SETTINGS_MODULE') because it's test. "
16 | "Using '{{project_name}}.settings.test'"
17 | )
18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{project_name}}.settings.test")
19 | else:
20 | if settings_module is None:
21 | print(
22 | "Error: no DJANGO_SETTINGS_MODULE found. Will NOT start devserver. "
23 | "Remember to create .env file at project root. "
24 | "Check README for more info."
25 | )
26 | sys.exit(1)
27 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
28 |
29 | from django.core.management import execute_from_command_line
30 |
31 | execute_from_command_line(sys.argv)
32 |
--------------------------------------------------------------------------------
/backend/users/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
2 | from django.db import models
3 | from django.utils.translation import ugettext_lazy as _
4 |
5 | from common.models import IndexedTimeStampedModel
6 |
7 | from .managers import UserManager
8 |
9 |
10 | class User(AbstractBaseUser, PermissionsMixin, IndexedTimeStampedModel):
11 | email = models.EmailField(max_length=255, unique=True)
12 | is_staff = models.BooleanField(
13 | default=False, help_text=_("Designates whether the user can log into this admin " "site.")
14 | )
15 | is_active = models.BooleanField(
16 | default=True,
17 | help_text=_(
18 | "Designates whether this user should be treated as "
19 | "active. Unselect this instead of deleting accounts."
20 | ),
21 | )
22 |
23 | objects = UserManager()
24 |
25 | USERNAME_FIELD = "email"
26 |
27 | def get_full_name(self):
28 | return self.email
29 |
30 | def get_short_name(self):
31 | return self.email
32 |
33 | def __str__(self):
34 | return self.email
35 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Vinta Serviços e Soluções Tecnológicas Ltda
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 |
--------------------------------------------------------------------------------
/backend/common/management/commands/celery.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 | import getpass
3 | import shlex
4 | from subprocess import PIPE # nosec
5 |
6 | from django.core.management.base import BaseCommand
7 | from django.utils.autoreload import run_with_reloader
8 |
9 | import psutil
10 |
11 |
12 | def restart_celery():
13 | for proc in psutil.process_iter():
14 | if proc.username() != getpass.getuser(): # skip processes not owned by user
15 | continue
16 | if proc.name() != "celery":
17 | continue
18 | # SIGTERM should only be sent to parent process, never to children processes
19 | # see: https://github.com/celery/celery/issues/2700#issuecomment-259716123
20 | if not proc.children():
21 | continue
22 | celery_proc = proc # found parent celery process
23 | celery_proc.terminate()
24 | break
25 | cmd = "celery worker -A {{project_name}} -l INFO"
26 | psutil.Popen(shlex.split(cmd), stdout=PIPE)
27 |
28 |
29 | class Command(BaseCommand):
30 | def handle(self, *args, **kwargs):
31 | print("Starting celery worker with autoreload")
32 | run_with_reloader(restart_celery)
33 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Motivation and Context
7 |
8 |
9 | ## Screenshots (if appropriate):
10 |
11 | ## Steps to reproduce (if appropriate):
12 |
13 | ## Types of changes
14 |
15 | - [ ] Bug fix (non-breaking change which fixes an issue)
16 | - [ ] New feature (non-breaking change which adds functionality)
17 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
18 |
19 | ## Checklist:
20 |
21 |
22 | - [ ] My code follows the code style of this project.
23 | - [ ] My change requires documentation updates.
24 | - [ ] I have updated the documentation accordingly.
25 | - [ ] My change requires dependencies updates.
26 | - [ ] I have updated the dependencies accordingly.
27 |
--------------------------------------------------------------------------------
/bin/post_compile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eo pipefail
3 |
4 | echo "-----> Post-compile hook"
5 |
6 | if [ -f bin/run_collectstatic ] && [ -n "$ENABLE_DJANGO_COLLECTSTATIC" ] && [ "$ENABLE_DJANGO_COLLECTSTATIC" == 1 ]; then
7 | echo "-----> Running run_collectstatic"
8 | chmod +x bin/run_collectstatic
9 | bin/run_collectstatic
10 | fi
11 |
12 | MANAGE_FILE=$(find . -maxdepth 3 -type f -name 'manage.py' | head -1)
13 | MANAGE_FILE=${MANAGE_FILE:2}
14 |
15 | echo "-----> Running manage.py check --deploy --fail-level WARNING"
16 | python $MANAGE_FILE check --deploy --fail-level WARNING
17 |
18 | if [ -n "$AUTO_MIGRATE" ] && [ "$AUTO_MIGRATE" == 1 ]; then
19 | echo "-----> Running manage.py migrate"
20 | python $MANAGE_FILE migrate --noinput
21 | fi
22 |
23 | echo "-----> Pushing source maps to Sentry"
24 | if [ -n "$SENTRY_API_KEY" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT_NAME" ] && [ -n "$SOURCE_VERSION" ]; then
25 | npx @sentry/cli --auth-token=$SENTRY_API_KEY releases --org=$SENTRY_ORG --project=$SENTRY_PROJECT_NAME files $SOURCE_VERSION upload-sourcemaps ./frontend/webpack_bundles/ --url-prefix "~/static/webpack_bundles/" --rewrite
26 | rm ./frontend/webpack_bundles/*.js.map
27 | fi
28 |
29 | echo "-----> Post-compile done"
30 |
--------------------------------------------------------------------------------
/webpack.base.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: [
6 | // defined in local or prod
7 | ],
8 | output: {
9 | // defined in local or prod
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.css$/,
15 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }, { loader: 'postcss-loader' }],
16 | },
17 | {
18 | test: /\.scss$/,
19 | use: [
20 | { loader: 'style-loader' },
21 | { loader: 'css-loader' },
22 | {
23 | loader: 'postcss-loader',
24 | options: {
25 | sourceMap: true,
26 | },
27 | },
28 | { loader: 'sass-loader' },
29 | ],
30 | },
31 | {
32 | test: /\.(svg)(\?v=\d+\.\d+\.\d+)?$/,
33 | loader: 'url-loader?limit=100000',
34 | },
35 | {
36 | test: /\.(jpg|png)?$/,
37 | loader: ['file-loader?name=i-[hash].[ext]'],
38 | },
39 | ],
40 | },
41 | plugins: [
42 | // defined in local or prod
43 | ],
44 | resolve: {
45 | modules: ['node_modules', 'bower_components', path.resolve(__dirname, 'frontend/js/')],
46 | extensions: ['.js', '.jsx'],
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/backend/project_name/settings/local_base.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 |
3 |
4 | DEBUG = True
5 |
6 | HOST = "http://localhost:8000"
7 |
8 | SECRET_KEY = "secret"
9 |
10 | STATIC_ROOT = base_dir_join("staticfiles")
11 | STATIC_URL = "/static/"
12 |
13 | MEDIA_ROOT = base_dir_join("mediafiles")
14 | MEDIA_URL = "/media/"
15 |
16 | DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
17 | STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
18 |
19 | AUTH_PASSWORD_VALIDATORS = [] # allow easy passwords only on local
20 |
21 | # Celery
22 | CELERY_TASK_ALWAYS_EAGER = True
23 | CELERY_TASK_EAGER_PROPAGATES = True
24 |
25 | # Email settings for mailhog
26 | EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
27 | EMAIL_HOST = 'mailhog'
28 | EMAIL_PORT = 1025
29 |
30 | # Logging
31 | LOGGING = {
32 | "version": 1,
33 | "disable_existing_loggers": False,
34 | "formatters": {"standard": {"format": "%(levelname)-8s [%(asctime)s] %(name)s: %(message)s"},},
35 | "handlers": {
36 | "console": {"level": "DEBUG", "class": "logging.StreamHandler", "formatter": "standard",},
37 | },
38 | "loggers": {
39 | "": {"handlers": ["console"], "level": "INFO"},
40 | "celery": {"handlers": ["console"], "level": "INFO"},
41 | },
42 | }
43 |
44 | JS_REVERSE_JS_MINIFY = False
45 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 |
3 | services:
4 | db:
5 | image: "postgres:alpine"
6 | environment:
7 | - POSTGRES_USER={{project_name}}
8 | - POSTGRES_PASSWORD=password
9 | - POSTGRES_DB={{project_name}}
10 | ports:
11 | - "5432"
12 | volumes:
13 | - dbdata:/var/lib/postgresql/data:delegated
14 |
15 | broker:
16 | image: "rabbitmq:alpine"
17 |
18 | result:
19 | image: "redis:alpine"
20 |
21 | frontend:
22 | build:
23 | dockerfile: frontend/Dockerfile
24 | context: .
25 | volumes:
26 | - .:/app/
27 | - /app/node_modules
28 | ports:
29 | - "3000:3000"
30 |
31 | backend:
32 | build:
33 | dockerfile: backend/Dockerfile
34 | context: .
35 | ports:
36 | - "8000:8000"
37 | volumes:
38 | - ./:/home/user/app/
39 | env_file: backend/.env
40 | depends_on:
41 | - db
42 | - broker
43 | - result
44 | - frontend
45 |
46 | celery:
47 | build:
48 | dockerfile: backend/Dockerfile
49 | context: .
50 | command: python manage.py celery
51 | volumes:
52 | - ./backend/:/home/user/app/
53 | env_file: backend/.env
54 | depends_on:
55 | - db
56 | - broker
57 | - result
58 |
59 | mailhog: # service for faking a SMTP server
60 | image: mailhog/mailhog
61 | ports:
62 | - '1025:1025' # smtp server
63 | - '8025:8025' # web ui
64 |
65 | volumes:
66 | dbdata:
67 | external:
68 | name: {{project_name}}_dbdata
--------------------------------------------------------------------------------
/frontend/js/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Button from 'react-bootstrap/Button';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | import DjangoImgSrc from '../../assets/images/django-logo-negative.png';
6 | import { creators } from '../store/rest_check';
7 |
8 | const Home = () => {
9 | const dispatch = useDispatch();
10 | const restCheck = useSelector((state) => state.restCheck);
11 | useEffect(() => {
12 | const action = creators.fetchRestCheck();
13 | dispatch(action);
14 | }, [dispatch]);
15 |
16 | const [showBugComponent, setShowBugComponent] = useState(false);
17 |
18 | return (
19 | <>
20 |
21 | If you are seeing the green Django logo on a white background and this text color is
22 | #092e20, frontend static files serving is working
23 |
24 |
25 |
26 | Below this text, you should see an img tag with the white Django logo on a green
27 | background
28 |
29 |

30 |
31 | {restCheck.result}
32 |
35 | {showBugComponent && showBugComponent.field.notexist}
36 | >
37 | );
38 | };
39 |
40 | export default Home;
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Pull requests
4 |
5 | Read [this checklist](http://pullrequests.devchecklists.com) for a more detailed guide on best practices for opening pull requests.
6 |
7 | ## Testing your changes
8 |
9 | ### Testing `django-admin startproject`
10 |
11 | If you made changes to this boilerplate and want to test them, do as follows:
12 |
13 | - [Make sure you have pre-commit installed](https://github.com/vintasoftware/django-react-boilerplate#pre-commit-hooks)
14 | - Commit your changes
15 | - Run `git archive -o boilerplate.zip HEAD` to create the template zip file
16 | - Run the following:
17 | ```bash
18 | cd .. && django-admin startproject theprojectname --extension py,yml,json --name Procfile,README.md,.env.example,Dockerfile --template=django-react-boilerplate/boilerplate.zip
19 | ```
20 | - A new folder called `theprojectname` will be created and now you can test your changes
21 | - Make sure that the project is still running fine with and without docker
22 |
23 | ### Testing Heroku deployment
24 |
25 | Push your changes to a branch and visit the link below
26 |
27 | https://dashboard.heroku.com/new?template=https://github.com/fill-org-or-user/fill-project-repo-name/tree/fill-branch
28 |
29 | > Make sure to replace all `fill-*`
30 |
31 | ## How to add a "Deploy to Heroku" button
32 |
33 | Read [this](https://devcenter.heroku.com/articles/heroku-button#adding-the-heroku-button).
34 |
35 | P.S. if you want to deploy in a different way, please check the `app.json` file for what needs to be configured.
36 |
37 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | fail_fast: true
2 | repos:
3 | - repo: git://github.com/pre-commit/pre-commit-hooks
4 | rev: v0.9.2
5 | hooks:
6 | - id: check-added-large-files
7 | args: ['--maxkb=500']
8 | - id: check-byte-order-marker
9 | - id: check-case-conflict
10 | - id: check-merge-conflict
11 | - id: check-symlinks
12 | - id: debug-statements
13 | - id: detect-private-key
14 |
15 | - repo: local
16 | hooks:
17 | - id: isort
18 | name: isort-local
19 | entry : isort
20 | language: python
21 | types: [python]
22 | exclude: .+/(settings|migrations)/.+
23 | pass_filenames: true
24 | - id: black
25 | name: black-local
26 | entry: black
27 | language: python
28 | types: [python]
29 | exclude: .+/(settings|migrations)/.+
30 | pass_filenames: true
31 | - id: eslint
32 | name: eslint-local
33 | entry: npm run lint
34 | language: system
35 | types: [javascript]
36 | exclude: >
37 | (?x)^(
38 | .+\.config\.js|
39 | server\.js|
40 | \.eslintrc\.js
41 | )$
42 | pass_filenames: true
43 | - id: missing-migrations
44 | name: missing-migrations-local
45 | entry: python manage.py makemigrations --check --dry-run
46 | language: system
47 | always_run: true
48 | pass_filenames: false
49 | - id: prospector
50 | name: prospector-local
51 | entry: prospector --messages-only
52 | language: python
53 | types: [python]
54 | exclude: .+/(settings|migrations)/.+
55 | pass_filenames: true
56 |
--------------------------------------------------------------------------------
/frontend/js/utils/SentryBoundary.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/browser';
2 | import PropTypes from 'prop-types';
3 | import React, { Component } from 'react';
4 |
5 | const FallbackUI = ({ eventId }) => (
6 | <>
7 | Check if there is an error on your Sentry app
8 |
11 | >
12 | );
13 |
14 | FallbackUI.propTypes = {
15 | eventId: PropTypes.string,
16 | };
17 |
18 | FallbackUI.defaultProps = {
19 | eventId: '',
20 | };
21 |
22 | class ExampleBoundary extends Component {
23 | static getDerivedStateFromError() {
24 | return { hasError: true };
25 | }
26 |
27 | constructor(props) {
28 | super(props);
29 | this.state = { eventId: null };
30 | }
31 |
32 | componentDidCatch(error, errorInfo) {
33 | Sentry.withScope((scope) => {
34 | scope.setExtras(errorInfo);
35 | const eventId = Sentry.captureException(error);
36 | this.setState({ eventId });
37 | });
38 | }
39 |
40 | render() {
41 | const { eventId, hasError } = this.state;
42 | const { children } = this.props;
43 |
44 | // render fallback UI
45 | if (hasError) {
46 | return ;
47 | }
48 |
49 | // when there's not an error, render children untouched
50 | return children;
51 | }
52 | }
53 |
54 | ExampleBoundary.propTypes = {
55 | children: PropTypes.node,
56 | };
57 |
58 | ExampleBoundary.defaultProps = {
59 | children: null,
60 | };
61 |
62 | export default ExampleBoundary;
63 |
--------------------------------------------------------------------------------
/frontend/sass/vendor/_bootstrap-includes.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | @import "~bootstrap/scss/functions";
3 | @import "~bootstrap/scss/variables";
4 | @import "~bootstrap/scss/mixins";
5 |
6 | /* Customizations */
7 | @import "custom-bootstrap";
8 |
9 | /* Reset and dependencies */
10 | @import "~bootstrap/scss/print";
11 |
12 | /* Core CSS */
13 | @import "~bootstrap/scss/buttons";
14 | @import "~bootstrap/scss/code";
15 | @import "~bootstrap/scss/forms";
16 | @import "~bootstrap/scss/grid";
17 | @import "~bootstrap/scss/images";
18 | @import "~bootstrap/scss/reboot";
19 | @import "~bootstrap/scss/tables";
20 | @import "~bootstrap/scss/type";
21 |
22 | /* Components */
23 | // @import "~bootstrap/scss/alert";
24 | // @import "~bootstrap/scss/badge";
25 | // @import "~bootstrap/scss/breadcrumb";
26 | // @import "~bootstrap/scss/button-group";
27 | @import "~bootstrap/scss/card";
28 | // @import "~bootstrap/scss/close";
29 | // @import "~bootstrap/scss/custom-forms";
30 | // @import "~bootstrap/scss/dropdown";
31 | // @import "~bootstrap/scss/input-group";
32 | @import "~bootstrap/scss/jumbotron";
33 | // @import "~bootstrap/scss/list-group";
34 | // @import "~bootstrap/scss/media";
35 | @import "~bootstrap/scss/nav";
36 | @import "~bootstrap/scss/navbar";
37 | // @import "~bootstrap/scss/pagination";
38 | // @import "~bootstrap/scss/progress";
39 | @import "~bootstrap/scss/transitions";
40 |
41 | /* Components with JS */
42 | // @import "~bootstrap/scss/carousel";
43 | // @import "~bootstrap/scss/modal";
44 | // @import "~bootstrap/scss/popover";
45 | // @import "~bootstrap/scss/tooltip";
46 | // @import "~bootstrap/scss/utilities";
47 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorChanger/ColorChanger.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import ColorDisplay from '../ColorDisplay';
5 |
6 | import './style.scss';
7 |
8 | class ColorChanger extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | color: 'black',
14 | };
15 |
16 | this.handleChangeColor = this.handleChangeColor.bind(this);
17 | }
18 |
19 | handleChangeColor(e) {
20 | const color = e.target.value;
21 | this.setState({ color });
22 | }
23 |
24 | render() {
25 | const { color } = this.state;
26 | const { title } = this.props;
27 |
28 | return (
29 |
30 |
{title}
31 |
Color Changer App
32 |
Check this example app: change the color to see it reflected in the text next to it.
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | ColorChanger.defaultProps = {
51 | title: 'React App Loaded!',
52 | };
53 |
54 | ColorChanger.propTypes = {
55 | title: PropTypes.string,
56 | };
57 |
58 | export default ColorChanger;
59 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{project_name}}",
3 | "description": "{{project_name|title}} Heroku app.",
4 | "env": {
5 | "ALLOWED_HOSTS": {
6 | "description": "Django ALLOWED_HOSTS setting, e.g.: .appname.herokuapp.com"
7 | },
8 | "DISABLE_COLLECTSTATIC": {
9 | "description": "Disables Heroku collectstatic",
10 | "value": "1"
11 | },
12 | "ENABLE_DJANGO_COLLECTSTATIC": {
13 | "description": "Enables post-compile collectstatic (it is run by bin/post_compile)",
14 | "value": "1"
15 | },
16 | "AUTO_MIGRATE": {
17 | "description": "Heroku setting to run Django migrate automatically (it is run by bin/post_compile)",
18 | "value": "1"
19 | },
20 | "DJANGO_SETTINGS_MODULE": {
21 | "description": "Django settings Python import path",
22 | "value": "{{project_name}}.settings.production"
23 | },
24 | "SECRET_KEY": {
25 | "description": "Django SECRET_KEY setting",
26 | "generator": "secret"
27 | }
28 | },
29 | "formation": {
30 | "web": {
31 | "quantity": 1,
32 | "size": "free"
33 | },
34 | "worker": {
35 | "quantity": 1,
36 | "size": "free"
37 | }
38 | },
39 | "addons": [
40 | {
41 | "plan": "heroku-postgresql:hobby-dev",
42 | "options": {
43 | "version": "13"
44 | },
45 | "as": "DATABASE"
46 | },
47 | {
48 | "plan": "heroku-redis:hobby-dev",
49 | "options": {
50 | "version": "6"
51 | },
52 | "as": "REDIS"
53 | },
54 | {
55 | "plan": "sendgrid:starter"
56 | },
57 | {
58 | "plan": "papertrail:choklad"
59 | }
60 | ],
61 | "buildpacks": [
62 | {
63 | "url": "heroku/nodejs"
64 | },
65 | {
66 | "url": "heroku/python"
67 | }
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | const autoprefixer = require('autoprefixer');
2 | const webpack = require('webpack');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | const BundleTracker = require('webpack-bundle-tracker');
5 | const path = require('path');
6 |
7 | const baseConfig = require('./webpack.base.config');
8 |
9 | const nodeModulesDir = path.resolve(__dirname, 'node_modules');
10 |
11 | baseConfig.mode = 'production';
12 | baseConfig.devtool = 'source-map';
13 |
14 | baseConfig.entry = ['whatwg-fetch', '@babel/polyfill', './frontend/js/index.js'];
15 |
16 | baseConfig.output = {
17 | path: path.resolve('./frontend/webpack_bundles/'),
18 | publicPath: '/static/webpack_bundles/',
19 | filename: '[name]-[hash].js',
20 | };
21 |
22 | baseConfig.module.rules.push(
23 | {
24 | test: /\.jsx?$/,
25 | exclude: [nodeModulesDir],
26 | use: {
27 | loader: 'babel-loader',
28 | options: {
29 | presets: ['@babel/preset-env', '@babel/preset-react'],
30 | },
31 | },
32 | },
33 | {
34 | test: /\.(woff(2)?|eot|ttf)(\?v=\d+\.\d+\.\d+)?$/,
35 | loader: 'file-loader?name=fonts/[name].[ext]',
36 | }
37 | );
38 |
39 | baseConfig.optimization = {
40 | minimize: true,
41 | splitChunks: {
42 | chunks: 'all',
43 | },
44 | };
45 |
46 | baseConfig.plugins = [
47 | new webpack.DefinePlugin({
48 | // removes React warnings
49 | 'process.env': {
50 | NODE_ENV: JSON.stringify('production'),
51 | },
52 | }),
53 | new MiniCssExtractPlugin({ filename: '[name]-[hash].css', disable: false, allChunks: true }),
54 | new BundleTracker({
55 | filename: './webpack-stats.json',
56 | }),
57 | new webpack.LoaderOptionsPlugin({
58 | options: {
59 | context: __dirname,
60 | postcss: [autoprefixer],
61 | },
62 | }),
63 | ];
64 |
65 | module.exports = baseConfig;
66 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash # Use bash syntax
2 | ARG := $(word 2, $(MAKECMDGOALS) )
3 |
4 | clean:
5 | @find . -name "*.pyc" -exec rm -rf {} \;
6 | @find . -name "__pycache__" -delete
7 |
8 | test:
9 | python backend/manage.py test backend/ $(ARG) --parallel --keepdb
10 |
11 | test_reset:
12 | python backend/manage.py test backend/ $(ARG) --parallel
13 |
14 | backend_format:
15 | black backend
16 |
17 | upgrade: ## update the *requirements.txt files with the latest packages satisfying *requirements.in
18 | pip install -U -q pip-tools
19 | pip-compile --upgrade -o dev-requirements.txt dev-requirements.in
20 | pip-compile --upgrade -o requirements.txt requirements.in
21 | # Make everything =>, not ==
22 | sed 's/==/>=/g' requirements.txt > requirements.tmp
23 | mv requirements.tmp requirements.txt
24 |
25 | compile_install_requirements:
26 | @echo 'Installing pip-tools...'
27 | export PIP_REQUIRE_VIRTUALENV=true; \
28 | pip install pip-tools
29 | @echo 'Compiling requirements...'
30 | pip-compile requirements.in > requirements.txt
31 | pip-compile dev-requirements.in > dev-requirements.txt
32 | @echo 'Installing requirements...'
33 | pip install -r requirements.txt && pip install -r dev-requirements.txt
34 |
35 | # Commands for Docker version
36 | docker_setup:
37 | docker volume create {{project_name}}_dbdata
38 | docker-compose build --no-cache backend
39 | docker-compose run frontend npm install
40 |
41 | docker_test:
42 | docker-compose run backend python manage.py test $(ARG) --parallel --keepdb
43 |
44 | docker_test_reset:
45 | docker-compose run backend python manage.py test $(ARG) --parallel
46 |
47 | docker_up:
48 | docker-compose up -d
49 |
50 | docker_update_dependencies:
51 | docker-compose down
52 | docker-compose up -d --build
53 |
54 | docker_down:
55 | docker-compose down
56 |
57 | docker_logs:
58 | docker-compose logs -f $(ARG)
59 |
60 | docker_makemigrations:
61 | docker-compose run --rm backend python manage.py makemigrations
62 |
63 | docker_migrate:
64 | docker-compose run --rm backend python manage.py migrate
65 |
--------------------------------------------------------------------------------
/webpack.local.config.js:
--------------------------------------------------------------------------------
1 | const autoprefixer = require('autoprefixer');
2 | const webpack = require('webpack');
3 | const BundleTracker = require('webpack-bundle-tracker');
4 | const CircularDependencyPlugin = require('circular-dependency-plugin');
5 | const path = require('path');
6 |
7 | const baseConfig = require('./webpack.base.config');
8 |
9 | const nodeModulesDir = path.resolve(__dirname, 'node_modules');
10 |
11 | baseConfig.mode = 'development';
12 |
13 | baseConfig.entry = [
14 | 'react-hot-loader/patch',
15 | 'whatwg-fetch',
16 | '@babel/polyfill',
17 | './frontend/js/index.js',
18 | ];
19 |
20 | baseConfig.optimization = {
21 | splitChunks: {
22 | chunks: 'all',
23 | },
24 | };
25 |
26 | baseConfig.output = {
27 | path: path.resolve('./frontend/bundles/'),
28 | publicPath: 'http://localhost:3000/frontend/bundles/',
29 | filename: '[name].js',
30 | };
31 |
32 | baseConfig.module.rules.push(
33 | {
34 | test: /\.jsx?$/,
35 | exclude: [nodeModulesDir],
36 | loader: require.resolve('babel-loader'),
37 | },
38 | {
39 | test: /\.(woff(2)?|eot|ttf)(\?v=\d+\.\d+\.\d+)?$/,
40 | loader: 'url-loader?limit=100000',
41 | }
42 | );
43 |
44 | baseConfig.plugins = [
45 | new webpack.EvalSourceMapDevToolPlugin({
46 | exclude: /node_modules/
47 | }),
48 | new webpack.NamedModulesPlugin(),
49 | new webpack.NoEmitOnErrorsPlugin(), // don't reload if there is an error
50 | new BundleTracker({
51 | filename: './webpack-stats.json',
52 | }),
53 | new webpack.LoaderOptionsPlugin({
54 | options: {
55 | context: __dirname,
56 | postcss: [autoprefixer],
57 | },
58 | }),
59 | new CircularDependencyPlugin({
60 | // exclude detection of files based on a RegExp
61 | exclude: /a\.js|node_modules/,
62 | // add errors to webpack instead of warnings
63 | failOnError: true,
64 | // set the current working directory for displaying module paths
65 | cwd: process.cwd(),
66 | }),
67 | ];
68 |
69 | baseConfig.resolve.alias = {
70 | 'react-dom': '@hot-loader/react-dom',
71 | };
72 |
73 | module.exports = baseConfig;
74 |
--------------------------------------------------------------------------------
/frontend/js/app/example-app/components/ColorChanger/__tests__/__snapshots__/ColorChanger.snaps.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ColorChanger no title (should use default) 1`] = `
4 |
7 |
8 | React App Loaded!
9 |
10 |
13 | Color Changer App
14 |
15 |
16 | Check this example app: change the color to see it reflected in the text next to it.
17 |
18 |
21 |
51 |
54 |
55 |
56 | `;
57 |
58 | exports[`ColorChanger some title 1`] = `
59 |
62 |
63 | This is a test title
64 |
65 |
68 | Color Changer App
69 |
70 |
71 | Check this example app: change the color to see it reflected in the text next to it.
72 |
73 |
76 |
106 |
109 |
110 |
111 | `;
112 |
--------------------------------------------------------------------------------
/backend/common/utils/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import Client, TestCase
2 | from django.urls import reverse
3 |
4 | from model_bakery import baker
5 |
6 |
7 | class TestCaseUtils(TestCase):
8 | def setUp(self):
9 | self._user_password = "123456"
10 | self.user = baker.prepare("users.User", email="user@email.com")
11 | self.user.set_password(self._user_password)
12 | self.user.save()
13 |
14 | self.auth_client = Client()
15 | self.auth_client.login(email=self.user.email, password=self._user_password)
16 |
17 | def reverse(self, name, *args, **kwargs):
18 | """ Reverse a url, convenience to avoid having to import reverse in tests """
19 | return reverse(name, args=args, kwargs=kwargs)
20 |
21 | def assertResponse200(self, response):
22 | """ Given response has status_code 200 OK"""
23 | self.assertEqual(response.status_code, 200)
24 |
25 | def assertResponse201(self, response):
26 | """ Given response has status_code 201 CREATED"""
27 | self.assertEqual(response.status_code, 201)
28 |
29 | def assertResponse301(self, response):
30 | """ Given response has status_code 301 MOVED PERMANENTLY"""
31 | self.assertEqual(response.status_code, 301)
32 |
33 | def assertResponse302(self, response):
34 | """ Given response has status_code 302 FOUND"""
35 | self.assertEqual(response.status_code, 302)
36 |
37 | def assertResponse400(self, response):
38 | """ Given response has status_code 400 BAD REQUEST"""
39 | self.assertEqual(response.status_code, 400)
40 |
41 | def assertResponse401(self, response):
42 | """ Given response has status_code 401 UNAUTHORIZED"""
43 | self.assertEqual(response.status_code, 401)
44 |
45 | def assertResponse403(self, response):
46 | """ Given response has status_code 403 FORBIDDEN"""
47 | self.assertEqual(response.status_code, 403)
48 |
49 | def assertResponse404(self, response):
50 | """ Given response has status_code 404 NOT FOUND"""
51 | self.assertEqual(response.status_code, 404)
52 |
53 |
54 | class TestGetRequiresAuthenticatedUser:
55 | def test_get_requires_authenticated_user(self):
56 | response = self.client.get(self.view_url)
57 | self.assertResponse403(response)
58 |
59 |
60 | class TestAuthGetRequestSuccess:
61 | def test_auth_get_success(self):
62 | response = self.auth_client.get(self.view_url)
63 | self.assertResponse200(response)
64 |
--------------------------------------------------------------------------------
/.bootstraprc:
--------------------------------------------------------------------------------
1 | ---
2 | # Output debugging info
3 | # loglevel: debug
4 |
5 | # Major version of Bootstrap: 3 or 4
6 | bootstrapVersion: 4
7 |
8 | # Webpack loaders, order matters
9 | styleLoaders:
10 | - style-loader
11 | - css-loader
12 | - sass-loader
13 | - postcss-loader
14 |
15 | # Extract styles to stand-alone css file
16 | # Different settings for different environments can be used,
17 | # It depends on value of NODE_ENV environment variable
18 | # This param can also be set in webpack config:
19 | # entry: 'bootstrap-loader/extractStyles'
20 | # env:
21 | # development:
22 | # extractStyles: false
23 | # production:
24 | # extractStyles: true
25 |
26 |
27 | # Customize Bootstrap variables that get imported before the original Bootstrap variables.
28 | # Thus, derived Bootstrap variables can depend on values from here.
29 | # See the Bootstrap _variables.scss file for examples of derived Bootstrap variables.
30 | #
31 | # preBootstrapCustomizations: ./path/to/bootstrap/pre-customizations.scss
32 |
33 |
34 | # This gets loaded after bootstrap/variables is loaded
35 | # Thus, you may customize Bootstrap variables
36 | # based on the values established in the Bootstrap _variables.scss file
37 | #
38 | bootstrapCustomizations: ./frontend/sass/vendor/custom-bootstrap.scss
39 |
40 |
41 | # Import your custom styles here
42 | # Usually this endpoint-file contains list of @imports of your application styles
43 | #
44 | # appStyles: ./path/to/your/app/styles/endpoint.scss
45 | appStyles: ./frontend/sass/style.scss
46 |
47 |
48 | ### Bootstrap styles
49 | styles:
50 |
51 | # Mixins
52 | mixins: true
53 |
54 | # Reset and dependencies
55 | print: true
56 |
57 | # Core CSS
58 | buttons: true
59 | code: true
60 | forms: true
61 | grid: true
62 | images: true
63 | reboot: true
64 | tables: true
65 | type: true
66 |
67 | # Components
68 | alert: false
69 | badge: false
70 | breadcrumb: false
71 | button-group: false
72 | card: true
73 | close: false
74 | custom-forms: false
75 | dropdown: false
76 | input-group: false
77 | jumbotron: true
78 | list-group: false
79 | media: false
80 | nav: true
81 | navbar: true
82 | pagination: false
83 | progress: false
84 | transitions: true
85 |
86 | # Components w/ JavaScript
87 | carousel: false
88 | modal: false
89 | popover: false
90 | tooltip: false
91 |
92 | # Utility classes
93 | utilities: true
94 |
95 | ### Bootstrap scripts
96 | scripts:
97 | alert: false
98 | button: true
99 | carousel: false
100 | collapse: true
101 | dropdown: false
102 | modal: false
103 | popover: false
104 | scrollspy: false
105 | tab: false
106 | tooltip: false
107 | util: true
108 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{project_name}}-frontend",
3 | "version": "0.0.1",
4 | "private": true,
5 | "description": "{{project_name}} frontend.",
6 | "main": "index.js",
7 | "prettier": "eslint-config-vinta/prettier",
8 | "scripts": {
9 | "test": "jest",
10 | "test:watch": "npm test -- --watch",
11 | "test:update": "npm test -- --u",
12 | "start": "babel-node server.js",
13 | "build": "NODE_ENV=production webpack -p --progress --colors --config webpack.prod.config.js --bail",
14 | "lint": "eslint frontend",
15 | "coverage": "jest --coverage"
16 | },
17 | "dependencies": {
18 | "@babel/cli": "~7.13.10",
19 | "@babel/polyfill": "~7.8.3",
20 | "@babel/preset-env": "~7.13.10",
21 | "@babel/preset-react": "~7.12.13",
22 | "@sentry/browser": "~6.2.2",
23 | "@types/react": "~16.14.5",
24 | "autoprefixer": "~9.8.6",
25 | "axios": "~0.21.1",
26 | "babel-loader": "~8.2.2",
27 | "bootstrap": "~4.6.0",
28 | "classnames": "~2.2.6",
29 | "connected-react-router": "~6.9.1",
30 | "cookie": "~0.4.0",
31 | "css-loader": "~5.1.3",
32 | "expose-loader": "~0.7.5",
33 | "file-loader": "~6.2.0",
34 | "history": "~4.10.1",
35 | "imports-loader": "~0.8.0",
36 | "lodash": "~4.17.21",
37 | "marked": "~2.0.1",
38 | "node-sass": "~4.14.1",
39 | "postcss": "~8.2.8",
40 | "postcss-loader": "~2.1.6",
41 | "prop-types": "~15.7.2",
42 | "react": "~16.14.0",
43 | "react-bootstrap": "~1.5.2",
44 | "react-dom": "~16.14.0",
45 | "react-hot-loader": "~4.13.0",
46 | "react-redux": "~7.2.3",
47 | "react-router": "~5.2.0",
48 | "redux": "~4.0.5",
49 | "redux-devtools-extension": "~2.13.9",
50 | "redux-thunk": "~2.3.0",
51 | "resolve-url-loader": "~3.1.2",
52 | "sass-loader": "~8.0.2",
53 | "script-loader": "~0.7.2",
54 | "style-loader": "~2.0.0",
55 | "url-loader": "~4.1.1",
56 | "webpack": "~4.46.0",
57 | "webpack-bundle-tracker": "~1.0.0",
58 | "webpack-dev-server": "~3.11.2",
59 | "whatwg-fetch": "~3.6.2"
60 | },
61 | "devDependencies": {
62 | "@babel/core": "~7.13.10",
63 | "@babel/node": "~7.13.10",
64 | "@hot-loader/react-dom": "~16.14.0",
65 | "ajv": "~6.12.6",
66 | "babel-core": "~7.0.0-bridge.0",
67 | "babel-eslint": "~10.1.0",
68 | "babel-jest": "~26.6.3",
69 | "circular-dependency-plugin": "~5.2.2",
70 | "enzyme": "~3.11.0",
71 | "enzyme-adapter-react-16": "~1.15.6",
72 | "enzyme-to-json": "~3.6.1",
73 | "eslint": "~6.8.0",
74 | "eslint-config-prettier": "~6.15.0",
75 | "eslint-config-vinta": "~1.4.5",
76 | "eslint-import-resolver-webpack": "~0.13.0",
77 | "eslint-plugin-babel": "~5.3.1",
78 | "eslint-plugin-import": "~2.22.1",
79 | "eslint-plugin-jest": "~22.21.0",
80 | "eslint-plugin-jsx-a11y": "~6.4.1",
81 | "eslint-plugin-prettier": "~3.3.1",
82 | "eslint-plugin-promise": "~4.3.1",
83 | "eslint-plugin-react": "~7.22.0",
84 | "eslint-plugin-react-hooks": "~2.5.1",
85 | "eslint-plugin-sonarjs": "~0.5.0",
86 | "eslint-plugin-unicorn": "~15.0.1",
87 | "identity-obj-proxy": "~3.0.0",
88 | "jest": "~26.6.3",
89 | "jest-css-modules": "~2.1.0",
90 | "mini-css-extract-plugin": "~0.12.0",
91 | "prettier": "~1.19.1",
92 | "redux-mock-store": "~1.5.4",
93 | "webpack-cli": "~3.3.12"
94 | },
95 | "resolutions": {
96 | "babel-core": "7.0.0-bridge.0"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/backend/project_name/settings/production.py:
--------------------------------------------------------------------------------
1 | import sentry_sdk
2 | from decouple import Csv, config
3 | from sentry_sdk.integrations.django import DjangoIntegration
4 |
5 | from .base import * # noqa
6 |
7 |
8 | DEBUG = False
9 |
10 | SECRET_KEY = config("SECRET_KEY")
11 |
12 | DATABASES["default"]["ATOMIC_REQUESTS"] = True
13 |
14 | ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=Csv())
15 |
16 | STATIC_ROOT = base_dir_join("staticfiles")
17 | STATIC_URL = "/static/"
18 |
19 | MEDIA_ROOT = base_dir_join("mediafiles")
20 | MEDIA_URL = "/media/"
21 |
22 | SERVER_EMAIL = "foo@example.com"
23 |
24 | EMAIL_HOST = "smtp.sendgrid.net"
25 | EMAIL_HOST_USER = config("SENDGRID_USERNAME")
26 | EMAIL_HOST_PASSWORD = config("SENDGRID_PASSWORD")
27 | EMAIL_PORT = 587
28 | EMAIL_USE_TLS = True
29 |
30 | # Security
31 | SECURE_HSTS_PRELOAD = True
32 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
33 | SECURE_SSL_REDIRECT = True
34 | SESSION_COOKIE_SECURE = True
35 | CSRF_COOKIE_SECURE = True
36 | SECURE_HSTS_SECONDS = config("SECURE_HSTS_SECONDS", default=3600, cast=int)
37 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True
38 |
39 | SECURE_CONTENT_TYPE_NOSNIFF = True
40 | SECURE_BROWSER_XSS_FILTER = True
41 | X_FRAME_OPTIONS = "DENY"
42 |
43 | # Webpack
44 | WEBPACK_LOADER["DEFAULT"]["CACHE"] = True
45 |
46 | # Celery
47 | CELERY_BROKER_URL = config("REDIS_URL")
48 | CELERY_RESULT_BACKEND = config("REDIS_URL")
49 | CELERY_SEND_TASK_ERROR_EMAILS = True
50 |
51 | # Redbeat https://redbeat.readthedocs.io/en/latest/config.html#redbeat-redis-url
52 | redbeat_redis_url = config("REDBEAT_REDIS_URL", default="")
53 |
54 | # Whitenoise
55 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
56 |
57 | # django-log-request-id
58 | MIDDLEWARE.insert( # insert RequestIDMiddleware on the top
59 | 0, "log_request_id.middleware.RequestIDMiddleware"
60 | )
61 |
62 | LOG_REQUEST_ID_HEADER = "HTTP_X_REQUEST_ID"
63 | LOG_REQUESTS = True
64 |
65 | LOGGING = {
66 | "version": 1,
67 | "disable_existing_loggers": False,
68 | "filters": {
69 | "require_debug_false": {"()": "django.utils.log.RequireDebugFalse"},
70 | "request_id": {"()": "log_request_id.filters.RequestIDFilter"},
71 | },
72 | "formatters": {
73 | "standard": {
74 | "format": "%(levelname)-8s [%(asctime)s] [%(request_id)s] %(name)s: %(message)s"
75 | },
76 | },
77 | "handlers": {
78 | "null": {"class": "logging.NullHandler",},
79 | "mail_admins": {
80 | "level": "ERROR",
81 | "class": "django.utils.log.AdminEmailHandler",
82 | "filters": ["require_debug_false"],
83 | },
84 | "console": {
85 | "level": "DEBUG",
86 | "class": "logging.StreamHandler",
87 | "filters": ["request_id"],
88 | "formatter": "standard",
89 | },
90 | },
91 | "loggers": {
92 | "": {"handlers": ["console"], "level": "INFO"},
93 | "django.security.DisallowedHost": {"handlers": ["null"], "propagate": False,},
94 | "django.request": {"handlers": ["mail_admins"], "level": "ERROR", "propagate": True,},
95 | "log_request_id.middleware": {
96 | "handlers": ["console"],
97 | "level": "DEBUG",
98 | "propagate": False,
99 | },
100 | },
101 | }
102 |
103 | JS_REVERSE_EXCLUDE_NAMESPACES = ["admin"]
104 |
105 | # Sentry
106 | sentry_sdk.init(dsn=SENTRY_DSN, integrations=[DjangoIntegration()], release=COMMIT_SHA)
107 |
--------------------------------------------------------------------------------
/backend/project_name/settings/base.py:
--------------------------------------------------------------------------------
1 | # https://docs.djangoproject.com/en/1.10/ref/settings/
2 |
3 | import os
4 |
5 | from decouple import config # noqa
6 | from dj_database_url import parse as db_url # noqa
7 |
8 |
9 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10 |
11 |
12 | def base_dir_join(*args):
13 | return os.path.join(BASE_DIR, *args)
14 |
15 |
16 | SITE_ID = 1
17 |
18 | DEBUG = True
19 |
20 | ADMINS = (("Admin", "foo@example.com"),)
21 |
22 | AUTH_USER_MODEL = "users.User"
23 |
24 | ALLOWED_HOSTS = []
25 |
26 | DATABASES = {
27 | "default": config("DATABASE_URL", cast=db_url),
28 | }
29 |
30 | INSTALLED_APPS = [
31 | "django.contrib.admin",
32 | "django.contrib.auth",
33 | "django.contrib.contenttypes",
34 | "django.contrib.sessions",
35 | "django.contrib.messages",
36 | "django.contrib.staticfiles",
37 | "django_js_reverse",
38 | "webpack_loader",
39 | "import_export",
40 | "rest_framework",
41 | "common",
42 | "users",
43 | ]
44 |
45 | MIDDLEWARE = [
46 | "debreach.middleware.RandomCommentMiddleware",
47 | "django.middleware.security.SecurityMiddleware",
48 | "whitenoise.middleware.WhiteNoiseMiddleware",
49 | "django.contrib.sessions.middleware.SessionMiddleware",
50 | "django.middleware.common.CommonMiddleware",
51 | "django.middleware.csrf.CsrfViewMiddleware",
52 | "django.contrib.auth.middleware.AuthenticationMiddleware",
53 | "django.contrib.messages.middleware.MessageMiddleware",
54 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
55 | ]
56 |
57 | ROOT_URLCONF = "{{project_name}}.urls"
58 |
59 | TEMPLATES = [
60 | {
61 | "BACKEND": "django.template.backends.django.DjangoTemplates",
62 | "DIRS": [base_dir_join("templates")],
63 | "OPTIONS": {
64 | "context_processors": [
65 | "django.template.context_processors.debug",
66 | "django.template.context_processors.request",
67 | "django.contrib.auth.context_processors.auth",
68 | "django.contrib.messages.context_processors.messages",
69 | "common.context_processors.sentry_dsn",
70 | "common.context_processors.commit_sha",
71 | ],
72 | "loaders": [
73 | (
74 | "django.template.loaders.cached.Loader",
75 | [
76 | "django.template.loaders.filesystem.Loader",
77 | "django.template.loaders.app_directories.Loader",
78 | ],
79 | ),
80 | ],
81 | },
82 | },
83 | ]
84 |
85 | WSGI_APPLICATION = "{{project_name}}.wsgi.application"
86 |
87 | AUTH_PASSWORD_VALIDATORS = [
88 | {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",},
89 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
90 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
91 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
92 | ]
93 |
94 | REST_FRAMEWORK = {
95 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
96 | 'PAGE_SIZE': 10,
97 | 'DEFAULT_AUTHENTICATION_CLASSES': [
98 | 'rest_framework.authentication.SessionAuthentication',
99 | ],
100 | 'DEFAULT_PERMISSION_CLASSES': [
101 | 'rest_framework.permissions.IsAuthenticated',
102 | ],
103 | }
104 |
105 | LANGUAGE_CODE = "en-us"
106 |
107 | TIME_ZONE = "UTC"
108 |
109 | USE_I18N = True
110 |
111 | USE_L10N = True
112 |
113 | USE_TZ = True
114 |
115 | STATICFILES_DIRS = (base_dir_join("../frontend"),)
116 |
117 | # Webpack
118 | WEBPACK_LOADER = {
119 | "DEFAULT": {
120 | "CACHE": False, # on DEBUG should be False
121 | "STATS_FILE": base_dir_join("../webpack-stats.json"),
122 | "POLL_INTERVAL": 0.1,
123 | "IGNORE": [".+\.hot-update.js", ".+\.map"],
124 | }
125 | }
126 |
127 | # Celery
128 | CELERY_ACCEPT_CONTENT = ["json"]
129 | CELERY_TASK_SERIALIZER = "json"
130 | CELERY_RESULT_SERIALIZER = "json"
131 | CELERY_ACKS_LATE = True
132 | CELERY_TIMEZONE = TIME_ZONE
133 |
134 | # Sentry
135 | SENTRY_DSN = config("SENTRY_DSN", default="")
136 | COMMIT_SHA = config("HEROKU_SLUG_COMMIT", default="")
137 |
138 | # Fix for Safari 12 compatibility issues, please check:
139 | # https://github.com/vintasoftware/safari-samesite-cookie-issue
140 | CSRF_COOKIE_SAMESITE = None
141 | SESSION_COOKIE_SAMESITE = None
142 |
143 | # Default primary key field type
144 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
145 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
146 |
--------------------------------------------------------------------------------
/.github/workflows/shared-build/action.yml:
--------------------------------------------------------------------------------
1 | name: "Shared Build Steps"
2 | description: "Shared build steps for main and nightly"
3 |
4 | runs:
5 | using: "composite"
6 | steps:
7 | - name: Store branch and latest SHA
8 | id: vars
9 | shell: bash
10 | run: |
11 | echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
12 | echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
13 | - name: Setup Python
14 | uses: actions/setup-python@v2
15 | with:
16 | python-version: "3.8"
17 | - name: Setup Node
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: "14.5"
21 | - name: Cache node modules
22 | uses: actions/cache@v2
23 | env:
24 | cache-name: node-modules-cache
25 | with:
26 | path: ~/.npm
27 | key: build-${{ env.cache-name }}-${{ steps.vars.outputs.branch }}-${{ steps.vars.outputs.sha_short }}
28 | restore-keys: |
29 | build-${{ env.cache-name }}-${{ steps.vars.outputs.branch }}-${{ steps.vars.outputs.sha_short }}
30 | build-${{ env.cache-name }}-${{ steps.vars.outputs.branch }}
31 | build-${{ env.cache-name }}
32 | - name: Cache pip
33 | uses: actions/cache@v2
34 | env:
35 | cache-name: pip-cache
36 | with:
37 | path: ~/.cache/pip
38 | key: build-${{ env.cache-name }}-${{ steps.vars.outputs.branch }}-${{ steps.vars.outputs.sha_short }}
39 | restore-keys: |
40 | build-${{ env.cache-name }}-${{ steps.vars.outputs.branch }}-${{ steps.vars.outputs.sha_short }}
41 | build-${{ env.cache-name }}-${{ steps.vars.outputs.branch }}
42 | build-${{ env.cache-name }}
43 | - run: python -m pip install --upgrade pip
44 | shell: bash
45 | - name: Install Django
46 | run: pip install "django>=3,<4"
47 | shell: bash
48 | - name: Setup testproject
49 | run: django-admin startproject testproject --extension py,yml,json,example --name Procfile,README.md --template=.
50 | shell: bash
51 | - run: |
52 | npm update --save
53 | npm update --save-dev
54 | shell: bash
55 | working-directory: testproject
56 | - run: npm install --no-optional
57 | working-directory: testproject
58 | shell: bash
59 | - run: npm dedupe
60 | working-directory: testproject
61 | shell: bash
62 | - run: npm run test
63 | working-directory: testproject
64 | shell: bash
65 | - run: npm run lint
66 | working-directory: testproject
67 | shell: bash
68 | - run: npm run build
69 | working-directory: testproject
70 | shell: bash
71 | - run: pip install requests pip-tools --upgrade
72 | working-directory: testproject
73 | shell: bash
74 | - run: |
75 | pip-compile requirements.in > requirements.txt
76 | pip-compile dev-requirements.in > dev-requirements.txt
77 | working-directory: testproject
78 | shell: bash
79 | - run: |
80 | pip install --user -r requirements.txt
81 | pip install --user -r dev-requirements.txt
82 | working-directory: testproject
83 | shell: bash
84 | - run: cp testproject/settings/local.py.example testproject/settings/local.py
85 | working-directory: testproject/backend
86 | shell: bash
87 | - run: cp .env.example .env
88 | working-directory: testproject/backend
89 | shell: bash
90 | - run: python manage.py makemigrations
91 | working-directory: testproject/backend
92 | env:
93 | DATABASE_URL: 'sqlite:///'
94 | shell: bash
95 | - run: python manage.py migrate
96 | working-directory: testproject/backend
97 | env:
98 | DATABASE_URL: 'sqlite:///'
99 | shell: bash
100 | - run: python manage.py test
101 | working-directory: testproject/backend
102 | env:
103 | DATABASE_URL: 'sqlite:///'
104 | shell: bash
105 | - name: Generate secret key
106 | run: echo '::set-output name=SECRET_KEY::`python -c "import uuid; print(uuid.uuid4().hex + uuid.uuid4().hex)"`'
107 | id: secret-id-generator
108 | shell: bash
109 | - run: prospector -X
110 | working-directory: testproject/backend
111 | env:
112 | SECRET_KEY: ${{ steps.secret-id-generator.outputs.SECRET_KEY }}
113 | SENDGRID_USERNAME: foo
114 | SENDGRID_PASSWORD: password
115 | DJANGO_SETTINGS_MODULE: 'testproject.settings.local'
116 | ALLOWED_HOSTS: '.example.org'
117 | REDIS_URL: 'redis://'
118 | DATABASE_URL: 'sqlite:///'
119 | shell: bash
120 | - run: python manage.py makemigrations --check --dry-run
121 | working-directory: testproject/backend
122 | env:
123 | DATABASE_URL: 'sqlite:///'
124 | shell: bash
125 | - run: python manage.py check --deploy --fail-level WARNING
126 | working-directory: testproject/backend
127 | env:
128 | SECRET_KEY: ${{ steps.secret-id-generator.outputs.SECRET_KEY }}
129 | SENDGRID_USERNAME: foo
130 | SENDGRID_PASSWORD: password
131 | DJANGO_SETTINGS_MODULE: 'testproject.settings.production'
132 | ALLOWED_HOSTS: '.example.org'
133 | REDIS_URL: 'redis://'
134 | DATABASE_URL: 'sqlite:///'
135 | shell: bash
136 |
--------------------------------------------------------------------------------
/proj_main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | build:
6 | name: Build {{project_name}}
7 | strategy:
8 | matrix:
9 | python: [3.8]
10 | node: [14.5]
11 | env:
12 | DATABASE_URL: 'sqlite:///'
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v2
17 | - name: Store branch and latest SHA
18 | run: |
19 | echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
20 | echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
21 | id: git
22 | - name: Setup Python ${% templatetag openvariable %} matrix.python {% templatetag closevariable %}
23 | uses: actions/setup-python@v2
24 | with:
25 | python-version: ${% templatetag openvariable %} matrix.python {% templatetag closevariable %}
26 | - name: Setup Node ${% templatetag openvariable %} matrix.node {% templatetag closevariable %}
27 | uses: actions/setup-node@v2
28 | with:
29 | node-version: ${% templatetag openvariable %} matrix.node {% templatetag closevariable %}
30 | - name: Cache node modules
31 | uses: actions/cache@v2
32 | env:
33 | cache_name: node-modules-cache
34 | with:
35 | path: ~/.npm
36 | key: build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.branch {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.sha_short {% templatetag closevariable %}
37 | restore-keys: |
38 | build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.branch {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.sha_short {% templatetag closevariable %}
39 | build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.branch {% templatetag closevariable %}
40 | build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}
41 | - name: Cache pip
42 | uses: actions/cache@v2
43 | env:
44 | cache_name: pip-cache
45 | with:
46 | path: ~/.cache/pip
47 | key: build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.branch {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.sha_short {% templatetag closevariable %}
48 | restore-keys: |
49 | build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.branch {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.sha_short {% templatetag closevariable %}
50 | build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}-${% templatetag openvariable%} steps.git.outputs.branch {% templatetag closevariable %}
51 | build-${% templatetag openvariable%} env.cache_name {% templatetag closevariable %}
52 | - run: python -m pip install --upgrade pip
53 | - run: curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
54 | - run: sudo apt-get install git-lfs --upgrade
55 | - run: pip install requests pip-tools --upgrade
56 | - run: |
57 | pip-compile requirements.in > requirements.txt
58 | pip-compile dev-requirements.in > dev-requirements.txt
59 | pip install --user -r requirements.txt
60 | pip install --user -r dev-requirements.txt
61 | - run: npm install
62 | - run: npm run build
63 | - run: npm run lint
64 | - name: Generate secret key
65 | run: echo '::set-output name=SECRET_KEY::`python -c "import uuid; print(uuid.uuid4().hex + uuid.uuid4().hex)"`'
66 | id: secret_id_generator
67 | - name: Style check
68 | run: prospector --messages-only
69 | env:
70 | DJANGO_SETTINGS_MODULE: '{{project_name}}.settings.local_base'
71 | SECRET_KEY: ${% templatetag openvariable %} steps.secret_id_generator.outputs.SECRET_KEY {% templatetag closevariable %}
72 | DATABASE_URL: 'sqlite:///'
73 | ALLOWED_HOSTS: '.example.org'
74 | SENDGRID_USERNAME: 'test'
75 | SENDGRID_PASSWORD: 'test'
76 | REDIS_URL: 'redis://'
77 | - name: Security checks
78 | run: bandit -r .
79 | - name: Imports check
80 | run: isort **/*.py --check-only
81 | - run: pre-commit run --all-files
82 | env:
83 | SKIP: prospector,isort,eslint,missing-migrations
84 | - run: python manage.py makemigrations --check --dry-run --ignore authtools
85 | working-directory: backend
86 | - run: python manage.py check --deploy
87 | env:
88 | DJANGO_SETTINGS_MODULE: '{{project_name}}.settings.production'
89 | SECRET_KEY: ${% templatetag openvariable %} steps.secret_id_generator.outputs.SECRET_KEY {% templatetag closevariable %}
90 | DATABASE_URL: 'sqlite:///'
91 | ALLOWED_HOSTS: '.example.org'
92 | SENDGRID_USERNAME: 'test'
93 | SENDGRID_PASSWORD: 'test'
94 | REDIS_URL: 'redis://'
95 | working-directory: backend
96 | - run: python manage.py test
97 | working-directory: backend
98 | - run: npm run test
99 | - run: |
100 | mkdir -p junit
101 | coverage xml -o junit/test-results.xml
102 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
7 |
8 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
9 |
10 | ## Our Standards
11 |
12 | Examples of behavior that contributes to a positive environment for our community include:
13 |
14 | * Demonstrating empathy and kindness toward other people
15 | * Being respectful of differing opinions, viewpoints, and experiences
16 | * Giving and gracefully accepting constructive feedback
17 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
18 | * Focusing on what is best not just for us as individuals, but for the overall community
19 |
20 | Examples of unacceptable behavior include:
21 |
22 | * The use of sexualized language or imagery, and sexual attention or advances of any kind
23 | * Trolling, insulting or derogatory comments, and personal or political attacks
24 | * Public or private harassment
25 | * Publishing others' private information, such as a physical or email address, without their explicit permission
26 | * Other conduct which could reasonably be considered inappropriate in a professional setting
27 |
28 | ## Enforcement Responsibilities
29 |
30 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
31 |
32 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
33 |
34 | ## Scope
35 |
36 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
37 |
38 | ## Enforcement
39 |
40 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [flavio@vinta.com.br](flavio@vinta.com.br). All complaints will be reviewed and investigated promptly and fairly.
41 |
42 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
43 |
44 | ## Enforcement Guidelines
45 |
46 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
47 |
48 | ### 1. Correction
49 |
50 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
51 |
52 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
53 |
54 | ### 2. Warning
55 |
56 | **Community Impact**: A violation through a single incident or series of actions.
57 |
58 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
59 |
60 | ### 3. Temporary Ban
61 |
62 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
63 |
64 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
65 |
66 | ### 4. Permanent Ban
67 |
68 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
69 |
70 | **Consequence**: A permanent ban from any sort of public interaction within the community.
71 |
72 | ## Attribution
73 |
74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
75 |
76 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
77 |
78 | For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
79 |
80 | [homepage]: https://www.contributor-covenant.org
81 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
82 | [Mozilla CoC]: https://github.com/mozilla/diversity
83 | [FAQ]: https://www.contributor-covenant.org/faq
84 | [translations]: https://www.contributor-covenant.org/translations
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Django React Boilerplate
2 |
3 | [](code_of_conduct.md)
4 | [](LICENSE.txt)
5 |
6 | ## About
7 | A [Django](https://www.djangoproject.com/) project boilerplate/template with lots of state of the art libraries and tools like:
8 | - [React](https://facebook.github.io/react/), for building interactive UIs
9 | - [django-js-reverse](https://github.com/ierror/django-js-reverse), for generating URLs on JS
10 | - [React Bootstrap](https://react-bootstrap.github.io/), for responsive styling
11 | - [Webpack](https://webpack.js.org/), for bundling static assets
12 | - [Celery](https://docs.celeryproject.org/), for background worker tasks
13 | - [WhiteNoise](http://whitenoise.evans.io/en/stable/) with [brotlipy](https://github.com/python-hyper/brotlipy), for efficient static files serving
14 | - [prospector](https://prospector.landscape.io/en/master/) and [ESLint](https://eslint.org/) with [pre-commit](https://pre-commit.com/) for automated quality assurance (does not replace proper testing!)
15 |
16 | For continuous integration, a [Github Action](https://github.com/features/actions) configuration `.github/workflows/main.yml` is included.
17 |
18 | Also, includes a Heroku `app.json` and a working Django `production.py` settings, enabling easy deployments with ['Deploy to Heroku' button](https://devcenter.heroku.com/articles/heroku-button). Those Heroku plugins are included in `app.json`:
19 | - PostgreSQL, for DB
20 | - Redis, for Celery
21 | - Sendgrid, for e-mail sending
22 | - Papertrail, for logs and platform errors alerts (must set them manually)
23 |
24 | This is a good starting point for modern Python/JavaScript web projects.
25 |
26 | ## Project bootstrap [](https://github.com/chocoelho/django-react-boilerplate/actions/workflows/main.yml) [](https://greenkeeper.io/)
27 | - [ ] Make sure you have Python 3.8 installed
28 | - [ ] Install Django with `pip install django`, to have the `django-admin` command available.
29 | - [ ] Open the command line and go to the directory you want to start your project in.
30 | - [ ] Start your project using:
31 | ```
32 | django-admin startproject theprojectname --extension py,yml,json --name Procfile,Dockerfile,README.md,.env.example,.gitignore,Makefile --template=https://github.com/vintasoftware/django-react-boilerplate/archive/boilerplate-release.zip
33 | ```
34 | Alternatively, you may start the project in the current directory by placing a `.` right after the project name, using the following command:
35 | ```
36 | django-admin startproject theprojectname . --extension py,yml,json --name Procfile,Dockerfile,README.md,.env.example,.gitignore,Makefile --template=https://github.com/vintasoftware/django-react-boilerplate/archive/boilerplate-release.zip
37 | ```
38 | In the next steps, always remember to replace theprojectname with your project's name
39 | - [ ] Above: don't forget the `--extension` and `--name` params!
40 | - [ ] Change the first line of README to the name of the project
41 | - [ ] Add an email address to the `ADMINS` settings variable in `{{project_name}}/backend/{{project_name}}/settings/base.py`
42 | - [ ] Change the `SERVER_EMAIL` to the email address used to send e-mails in `{{project_name}}/backend/{{project_name}}/settings/production.py`
43 | - [ ] Rename the folder `github` to `.github` with the command `mv github .github`
44 |
45 | After completing ALL of the above, remove this `Project bootstrap` section from the project README. Then follow `Running` below.
46 |
47 | ## Running
48 | ### Tools
49 | - Setup [editorconfig](http://editorconfig.org/), [prospector](https://prospector.landscape.io/en/master/) and [ESLint](http://eslint.org/) in the text editor you will use to develop.
50 |
51 | ### Setup
52 | - Inside the `backend` folder, do the following:
53 | - Create a copy of `{{project_name}}/settings/local.py.example`:
54 | `cp {{project_name}}/settings/local.py.example {{project_name}}/settings/local.py`
55 | - Create a copy of `.env.example`:
56 | `cp .env.example .env`
57 |
58 | ### If you are using Docker:
59 | - Open the `/backend/.env` file on a text editor and uncomment the line `DATABASE_URL=postgres://{{project_name}}:password@db:5432/{{project_name}}`
60 | - Open a new command line window and go to the project's directory
61 | - Run the initial setup:
62 | `make docker_setup`
63 | - Create the migrations for `users` app:
64 | `make docker_makemigrations`
65 | - Run the migrations:
66 | `make docker_migrate`
67 | - Run the project:
68 | `make docker_up`
69 | - Access `http://localhost:8000` on your browser and the project should be running there
70 | - When you run `make docker_up`, some containers are spinned up (frontend, backend, database, etc) and each one will be running on a different port
71 | - The container with the React app uses port 3000. However, if you try accessing it on your browser, the app won't appear there and you'll probably see a blank page with the "Cannot GET /" error
72 | - This happens because the container responsible for displaying the whole application is the Django app one (running on port 8000). The frontend container is responsible for providing a bundle with its assets for [django-webpack-loader](https://github.com/django-webpack/django-webpack-loader) to consume and render them on a Django template
73 | - To access the logs for each service, run:
74 | `make docker_logs ` (either `backend`, `frontend`, etc)
75 | - To stop the project, run:
76 | `make docker_down`
77 |
78 | #### Adding new dependencies
79 | - Open a new command line window and go to the project's directory
80 | - Update the dependencies management files by performing any number of the following steps:
81 | - To add a new **frontend** dependency, run `npm install --save`
82 | > The above command will update your `package.json`, but won't make the change effective inside the container yet
83 | - To add a new **backend** dependency, update `requirements.in` or `dev-requirements.in` with the newest requirements
84 | - After updating the desired file(s), run `make docker_update_dependencies` to update the containers with the new dependencies
85 | > The above command will stop and re-build the containers in order to make the new dependencies effective
86 |
87 | ### If you are not using Docker:
88 | #### Setup and run the frontend app
89 | - Open a new command line window and go to the project's directory
90 | - `npm install`
91 | - `npm run start`
92 | - This is used to serve the frontend assets to be consumed by [django-webpack-loader](https://github.com/django-webpack/django-webpack-loader) and not to run the React application as usual, so don't worry if you try to check what's running on port 3000 and see an error on your browser
93 |
94 | #### Setup the backend app
95 | - Open the `/backend/.env` file on a text editor and do one of the following:
96 | - If you wish to use SQLite locally, uncomment the line `DATABASE_URL=sqlite:///backend/db.sqlite3`
97 | - If you wish to use PostgreSQL locally, uncomment and edit the line `DATABASE_URL=postgres://{{project_name}}:password@db:5432/{{project_name}}` in order to make it correctly point to your database URL
98 | - The url format is the following: `postgres://USER:PASSWORD@HOST:PORT/NAME`
99 | - If you wish to use another database engine locally, add a new `DATABASE_URL` setting for the database you wish to use
100 | - Please refer to [dj-database-url](https://github.com/jacobian/dj-database-url#url-schema) on how to configure `DATABASE_URL` for commonly used engines
101 | - Open a new command line window and go to the project's directory
102 | - Create a new virtualenv with either [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/) or only virtualenv: `mkvirtualenv {{project_name}}` or `python -m venv {{project_name}}-venv`
103 | > If you're using Python's virtualenv (the latter option), make sure to create the environment with the suggested name, otherwise it will be added to version control.
104 | - Make sure the virtualenv is activated `workon {{project_name}}` or `source {{project_name}}-venv/bin/activate`
105 | - Run `make compile_install_requirements` to install the requirements
106 | > Please make sure you have already setup PostgreSQL on your environment before installing the requirements
107 |
108 | > In case you wish to use a Conda virtual environment, please remove the line `export PIP_REQUIRE_VIRTUALENV=true; \` from `Makefile`
109 |
110 | #### Run the backend app
111 | - With the virtualenv enabled, go to the `backend` directory
112 | - Create the migrations for `users` app:
113 | `python manage.py makemigrations`
114 | - Run the migrations:
115 | `python manage.py migrate`
116 | - Run the project:
117 | `python manage.py runserver`
118 | - Open a browser and go to `http://localhost:8000` to see the project running
119 |
120 | #### Setup Celery
121 | - Open a command line window and go to the project's directory
122 | - `workon {{project_name}}` or `source {{project_name}}-venv/bin/activate` depending on if you are using virtualenvwrapper or just virtualenv.
123 | - `python manage.py celery`
124 |
125 | #### Mailhog
126 | - For development, we use Mailhog to test our e-mail workflows, since it allows us to inspect the messages to validate they're correctly built
127 | - Docker users already have it setup and running once they start the project
128 | - For non-Docker users, please have a look [here](https://github.com/mailhog/MailHog#installation) for instructions on how to setup Mailhog on specific environments
129 | > The project expects Mailhog SMTP server to be running on port 1025, you may alter that by changing `EMAIL_PORT` on settings
130 |
131 |
132 | ### Testing
133 | `make test`
134 |
135 | Will run django tests using `--keepdb` and `--parallel`. You may pass a path to the desired test module in the make command. E.g.:
136 |
137 | `make test someapp.tests.test_views`
138 |
139 | ### Adding new pypi libs
140 | Add the libname to either `requirements.in` or `dev-requirements.in`, then either upgrade the libs with `make upgrade` or manually compile it and then, install.
141 | `pip-compile requirements.in > requirements.txt` or `make upgrade`
142 | `pip install -r requirements.txt`
143 |
144 | ## Deployment
145 | ### Setup
146 | This project comes with an `app.json` file, which can be used to create an app on Heroku from a GitHub repository.
147 |
148 | Before deploying, please make sure you've generated an up-to-date `requirements.txt` file containing the Python dependencies. This is necessary even if you've used Docker for local runs. Do so by following [these instructions](#setup-the-backend-app).
149 |
150 | After setting up the project, you can init a repository and push it on GitHub. If your repository is public, you can use the following button:
151 |
152 | [](https://heroku.com/deploy)
153 |
154 | If you are in a private repository, access the following link replacing `$YOUR_REPOSITORY_LINK$` with your repository link.
155 |
156 | - `https://heroku.com/deploy?template=$YOUR_REPOSITORY_LINK$`
157 |
158 | Remember to fill the `ALLOWED_HOSTS` with the URL of your app, the default on heroku is `appname.herokuapp.com`. Replace `appname` with your heroku app name.
159 |
160 | ### Sentry
161 |
162 | [Sentry](https://sentry.io) is already set up on the project. For production, add `SENTRY_DSN` environment variable on Heroku, with your Sentry DSN as the value.
163 |
164 | You can test your Sentry configuration by deploying the boilerplate with the sample page and clicking on the corresponding button.
165 |
166 | ### Sentry source maps for JS files
167 |
168 | The `bin/post_compile` script has a step to push Javascript source maps to Sentry, however some environment variables need to be set on Heroku.
169 |
170 | You need to enable Heroku dyno metadata on your Heroku App. Use the following command on Heroku CLI:
171 |
172 | - `heroku labs:enable runtime-dyno-metadata -a `
173 |
174 | The environment variables that need to be set are:
175 |
176 | - `SENTRY_ORG` - Name of the Sentry Organization that owns your Sentry Project.
177 | - `SENTRY_PROJECT_NAME` - Name of the Sentry Project.
178 | - `SENTRY_API_KEY` - Sentry API key that needs to be generated on Sentry. [You can find or create authentication tokens within Sentry](https://sentry.io/api/).
179 |
180 | After enabling dyno metadata and setting the environment variables, your next Heroku Deploys will create a release on Sentry where the release name is the commit SHA, and it will push the source maps to it.
181 |
182 | ## Linting
183 | - Manually with `prospector` and `npm run lint` on project root.
184 | - During development with an editor compatible with prospector and ESLint.
185 |
186 | ## Pre-commit hooks
187 | - Run `pre-commit install` to enable the hook into your git repo. The hook will run automatically for each commit.
188 | - Run `git commit -m "Your message" -n` to skip the hook if you need.
189 |
190 | ## Opinionated Settings
191 | Some settings defaults were decided based on Vinta's experiences. Here's the rationale behind them:
192 |
193 | ### `CELERY_ACKS_LATE = True`
194 | We believe Celery tasks should be idempotent. So for us it's safe to set `CELERY_ACKS_LATE = True` to ensure tasks will be re-queued after a worker failure. Check Celery docs on ["Should I use retry or acks_late?"](https://docs.celeryproject.org/en/latest/faq.html#should-i-use-retry-or-acks-late) for more info.
195 |
196 | ## Features Catalogue
197 |
198 | ### Frontend
199 | - `react` for building interactive UIs
200 | - `react-dom` for rendering the UI
201 | - `react-router` for page navigation
202 | - `webpack` for bundling static assets
203 | - `webpack-bundle-tracker` for providing the bundled assets to Django
204 | - Styling
205 | - `bootstrap` for providing responsive stylesheets
206 | - `react-bootstrap` for providing components built on top of Bootstrap CSS without using plugins
207 | - `node-sass` for providing compatibility with SCSS files
208 | - State management and backend integration
209 | - `axios` for performing asynchronous calls
210 | - `cookie` for easy integration with Django using the `csrftoken` cookie
211 | - `redux` for easy state management across the application
212 | - `connected-react-router` for integrating Redux with React Router
213 | - `history` for providing browser history to Connected React Router
214 | - `react-redux` for integrating React with Redux
215 | - `redux-devtools-extension` for inspecting and debugging Redux via browser
216 | - `redux-thunk` for interacting with the Redux store through asynchronous logic
217 | - Utilities
218 | - `lodash` for general utility functions
219 | - `classnames` for easy working with complex CSS class names on components
220 | - `prop-types` for improving QoL while developing providing basic type-checking for React props
221 | - `react-hot-loader` for improving QoL while developing through automatic browser refreshing
222 |
223 | ### Backend
224 | - `django` for building backend logic using Python
225 | - `djangorestframework` for building a REST API on top of Django
226 | - `django-webpack-loader` for rendering the bundled frontend assets
227 | - `django-js-reverse` for easy handling of Django URLs on JS
228 | - `psycopg2` for using PostgreSQL database
229 | - `sentry-sdk` for error monitoring
230 | - `python-decouple` for reading environment variables on settings files
231 | - `celery` for background worker tasks
232 | - `django-debreach` for additional protection against BREACH attack
233 | - `whitenoise` and `brotlipy` for serving static assets
234 |
235 | ## Contributing
236 |
237 | If you wish to contribute to this project, please first discuss the change you wish to make via an [issue](https://github.com/vintasoftware/django-react-boilerplate/issues).
238 |
239 | Check our [contributing guide](https://github.com/vintasoftware/django-react-boilerplate/blob/master/CONTRIBUTING.md) to learn more about our development process and how you can test your changes to the boilerplate.
240 |
241 | ## Commercial Support
242 | [](https://www.vinta.com.br/)
243 |
244 | This project is maintained by [Vinta Software](https://www.vinta.com.br/) and is used in products of Vinta's clients. We are always looking for exciting work, so if you need any commercial support, feel free to get in touch: contact@vinta.com.br
245 |
--------------------------------------------------------------------------------