├── .dockerignore ├── .editorconfig ├── .eslintrc ├── .gitignore ├── Makefile ├── README.md ├── django_apps ├── __init__.py └── templates │ ├── base.html │ └── index.html ├── django_config ├── __init__.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── local.py │ └── production.py ├── urls.py └── wsgi.py ├── docker-compose-development.yml ├── docker-compose.yml ├── docker_compose ├── django │ ├── development │ │ ├── Dockerfile │ │ └── start.sh │ ├── entrypoint.sh │ └── production │ │ ├── Dockerfile │ │ └── start.sh ├── nginx │ ├── Dockerfile │ └── nginx.conf ├── node │ └── development │ │ ├── Dockerfile │ │ └── start.sh └── postgres │ └── Dockerfile ├── manage.py ├── package.json ├── requirements ├── base.txt ├── local.txt └── production.txt ├── static └── js │ └── src │ ├── example.spec.js │ ├── main │ ├── components │ │ └── hello-world.jsx │ ├── containers │ │ ├── DevTools │ │ │ └── index.jsx │ │ └── Root │ │ │ ├── Root.dev.jsx │ │ │ ├── Root.prod.jsx │ │ │ └── index.jsx │ ├── index.jsx │ ├── reducers │ │ └── index.jsx │ ├── routes.jsx │ └── store │ │ ├── configureStore.dev.jsx │ │ ├── configureStore.prod.jsx │ │ └── index.jsx │ ├── test_index.js │ └── utilities │ ├── api.js │ └── cookie.js └── webpack ├── karma.config.js ├── webpack.base.config.js ├── webpack.local.config.js └── webpack.production.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | # virtual environments 2 | venv 3 | 4 | # Python files 5 | *.pyc 6 | __pycache__ 7 | 8 | # Python Notebook dot files 9 | .ipynb* 10 | 11 | # Secret environment settings 12 | .env 13 | env 14 | 15 | # Node and Webpack files 16 | node_modules 17 | npm-debug.log 18 | webpack-stats* 19 | 20 | # Static assests 21 | staticfiles 22 | static_cdn 23 | media_cdn 24 | 25 | # Ignore sqlite 26 | db.sqlite3 27 | 28 | # Apple files 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | # Python settings 11 | [*.py] 12 | line_length=120 13 | indent_style = space 14 | indent_size = 4 15 | 16 | # JavaScript, JSX, CSS, SCSS, HTML settings 17 | [*.{js,jsx,css,scss,html}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Markdown settings 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | # Config files 26 | [*.conf] 27 | indent_style = space 28 | indent_size = 2 29 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "ecmaFeatures": { 4 | "jsx": true, 5 | "modules": true 6 | }, 7 | "env": { 8 | "browser": true, 9 | "es6": true, 10 | "node": true 11 | }, 12 | "parser": "babel-eslint", 13 | "rules": { 14 | "react/jsx-uses-react": 2, 15 | "react/jsx-uses-vars": 2, 16 | "react/react-in-jsx-scope": 2, 17 | "no-var": 0, 18 | "vars-on-top": 0, 19 | "comma-dangle": 0, 20 | "arrow-body-style": ["error", "always"], 21 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], 22 | "max-len": ["error", { "comments": 120, "code": 120 }], 23 | "no-console": ["error", { "allow": ["warn", "error"] }], 24 | "no-unused-expressions": ["error", { "allowShortCircuit": true }], 25 | "arrow-body-style": ["error", "as-needed"] 26 | }, 27 | "plugins": [ 28 | "react" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # virtual environments 2 | venv 3 | 4 | # Python files 5 | *.pyc 6 | __pycache__ 7 | 8 | # Python Notebook dot files 9 | .ipynb* 10 | 11 | # Secret environment settings 12 | .env 13 | env 14 | 15 | # Node and Webpack files 16 | node_modules 17 | npm-debug.log 18 | webpack-stats* 19 | 20 | # Static assests 21 | staticfiles 22 | static_cdn 23 | media_cdn 24 | 25 | # Ignore sqlite 26 | db.sqlite3 27 | 28 | # Apple files 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | docker-build: 2 | docker-compose -f docker-compose.yml build 3 | 4 | docker-build-dev: 5 | docker-compose -f docker-compose-development.yml build 6 | 7 | docker-up-background: 8 | docker-compose -f docker-compose.yml up -d 9 | 10 | docker-up-background-dev: 11 | docker-compose -f docker-compose-development.yml up -d 12 | 13 | docker-up: 14 | docker-compose -f docker-compose.yml up 15 | 16 | docker-up-dev: 17 | docker-compose -f docker-compose-development.yml up 18 | 19 | ssh-django: 20 | docker exec -ti django_server bash 21 | 22 | ssh-node: 23 | docker exec -ti node_server bash 24 | 25 | ssh-psql: 26 | docker exec -ti psql_server bash 27 | 28 | ssh-nginx: 29 | docker exec -ti nginx_server bash 30 | 31 | django-dev: 32 | python manage.py runserver --settings=django_config.settings.local 33 | 34 | # docker-clean-up: 35 | # docker rm -f $(docker ps -a -q) -v 36 | # docker rmi -f $(docker images -a -q) 37 | # docker volume rm $(docker volume ls -q) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE: THIS HAS BEEN DEPRECATED. Use [this](https://github.com/chopdgd/cookiecutter-django-reactjs) instead. Integrates Facebooks create react app with Django :)** 2 | 3 | # An example project for a Dockerized Django/ReactJS web application 4 | 5 | I set this up because it was __SUCH__ a pain in the a** to set up a web application with all of the latest technologies. I created this as a boilerplate after much research and reading of blogs. For a detailed step by step for creating this project look [here](https://gist.github.com/genomics-geek/98929a9e7ba9602fed7bfa4a5a1c5c4e/). Or you can just clone [this repo](https://github.com/genomics-geek/django_reactjs_boilerplate) and follow the [local deployment instructions](https://github.com/genomics-geek/django_reactjs_boilerplate#setup-local-environment). 6 | 7 | This is an example of a project that uses the following technologies: 8 | + Docker with Docker Compose 9 | + Django REST APIs 10 | + ReactJS with Redux 11 | + Webpack module bundler and Hot Reloading (Uses React Hot Reloader 3!) 12 | + Karma with Mocha for JavaScript unit tests 13 | 14 | ## System Requirements 15 | 16 | + [Docker](https://www.docker.com/) 17 | + [Docker Compose](https://docs.docker.com/compose/) 18 | + [PostgreSQL](http://www.postgresql.org/) 19 | + [pyenv](https://github.com/yyuu/pyenv) 20 | + [pyenv-virtualenv](https://github.com/yyuu/pyenv-virtualenv) 21 | + [git](https://git-scm.com/) 22 | + [NodeJS 6+](https://nodejs.org/en/) 23 | + [NPM 3+](https://www.npmjs.com/) 24 | 25 | ## Setup Local Environment 26 | 27 | ### First things, first. Clone the repo 28 | 29 | ``` 30 | git clone https://github.com/genomics-geek/django_reactjs_boilerplate.git 31 | ``` 32 | 33 | ### To setup a Dockerized development environment 34 | 35 | In my opinion, this is the easiest way to get up and running. 36 | 37 | #### 1. Create `.env` file 38 | 39 | [Example .env file](https://gist.github.com/genomics-geek/98929a9e7ba9602fed7bfa4a5a1c5c4e#create-environment-variables-file---env) 40 | 41 | #### 2. Build docker container 42 | 43 | ``` 44 | make docker-build-dev 45 | make docker-up-dev 46 | ``` 47 | 48 | Visit [http://localhost:8000](http://localhost:8000). You should see __Hello World!__. You should now see the application running locally! Also this project is set up to use hot reloading, so you can see your changes to React components live ;) 49 | 50 | ### To setup a local development environment 51 | 52 | If you want to a non dockerized development environment 53 | 54 | #### 1. Set up a virtual environment - [Quick Guide to Virtual Environments](https://gist.github.com/genomics-geek/98929a9e7ba9602fed7bfa4a5a1c5c4e#file-step05-md) 55 | 56 | ``` 57 | pyenv virtualenv [name_of_venv] 58 | ``` 59 | 60 | #### 2. Install requirements 61 | 62 | ``` 63 | npm install 64 | pip install -r requirements/local.txt 65 | ``` 66 | 67 | #### 3. Create .env file 68 | 69 | [Example .env file](https://gist.github.com/genomics-geek/98929a9e7ba9602fed7bfa4a5a1c5c4e#create-environment-variables-file---env) 70 | 71 | #### 4. Create the PostgreSQL database 72 | 73 | [Quick Guide for installing PostgreSQL on MacOSX](https://gist.github.com/genomics-geek/98929a9e7ba9602fed7bfa4a5a1c5c4e#set-up-postgresql-database) 74 | 75 | #### 5. Start the node server 76 | 77 | Open a terminal window and run: 78 | 79 | ``` 80 | npm run watch 81 | ``` 82 | 83 | #### 7. Start the django server 84 | 85 | Before you run python manage.py run: 86 | 87 | ``` 88 | export DJANGO_SETTINGS_MODULE=django_config.settings.local 89 | ``` 90 | 91 | Or alternatively, you can just add `--settings=django_config.settings.local` to any `python manage.py` command you run. 92 | 93 | Open another terminal window and run: 94 | 95 | ``` 96 | python manage.py migrate 97 | python manage.py runserver 98 | ``` 99 | 100 | Visit [http://localhost:8000](http://localhost:8000). You should see __Hello World!__. You should now see the application running locally! Also this project is set up to use hot reloading, so you can see your changes to React components live ;) 101 | 102 | ## Languages used 103 | + [Python](https://www.python.org/) 104 | + [JavaScript](https://www.javascript.com/) 105 | + [JSX](https://jsx.github.io/) 106 | 107 | ## Tools used 108 | + [Docker 1.12.1](https://www.docker.com/) 109 | + [Docker compose 1.8.0](https://docs.docker.com/compose/) 110 | + [Django 1.10.2](https://www.djangoproject.com/) 111 | + [Django REST Framework 3.4.7](http://www.django-rest-framework.org/) 112 | + [ReactJS 15.3.2](https://facebook.github.io/react/) 113 | + [Redux 3.6.0](http://redux.js.org/docs/introduction/) 114 | + [PostgreSQL 9.6.0](http://www.postgresql.org/) 115 | + [NodeJS 6.7.0](https://nodejs.org/en/) 116 | + [NPM 3.10.7](https://www.npmjs.com/) 117 | + [Webpack 1.13.2](https://webpack.github.io/) 118 | 119 | ## Helpful resources 120 | + [Gist on setting up Django + ReactJS project](https://gist.github.com/genomics-geek/49ae04fb3d0147bc8340edf228759c36) 121 | + [Learn Redux from the Creator!](https://egghead.io/courses/getting-started-with-redux) 122 | + [More from Dan](https://egghead.io/courses/building-react-applications-with-idiomatic-redux) 123 | + [Dockerizing Django projects](http://ruddra.com/2016/08/14/docker-django-nginx-postgres/) 124 | + [Owais Lane Blog Post on Django + ReactJS with Webpack](http://owaislone.org/blog/webpack-plus-reactjs-and-django/) 125 | + [Extension of Owais Lane's blog post](http://geezhawk.github.io/using-react-with-django-rest-framework) 126 | + [YouTube Playlist on Django Rest Framework](https://www.youtube.com/playlist?list=PLEsfXFp6DpzTOcOVdZF-th7BS_GYGguAS) 127 | + [YouTube Playlist on ReactJS/Redux](https://www.youtube.com/playlist?list=PLoYCgNOIyGABj2GQSlDRjgvXtqfDxKm5b) 128 | + [fullstackpython](http://www.fullstackpython.com/) 129 | + [Write your first Django App](https://docs.djangoproject.com/en/1.10/intro/tutorial01/) 130 | + [Quick Django tutorial](http://drksephy.github.io/2015/07/16/django/) 131 | + [Django REST framework](http://www.django-rest-framework.org/tutorial/1-serialization/) 132 | + [Using React with Django, with a little help from Webpack](http://geezhawk.github.io/using-react-with-django-rest-framework) 133 | + [Hot Module Replacement with Webpack](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html) 134 | + [Example of React Hot Reloader 3](https://github.com/gaearon/redux-devtools/tree/master/examples/todomvc) 135 | -------------------------------------------------------------------------------- /django_apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genomics-geek/django_reactjs_boilerplate/7668038f182c1600324a2c62c29f490f8203c9dc/django_apps/__init__.py -------------------------------------------------------------------------------- /django_apps/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | {% block head %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% block head_title %}{% endblock head_title %} 17 | 18 | 19 | {% block stylesheets %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% endblock stylesheets %} 36 | 37 | 38 | 39 | 40 | {% endblock head %} 41 | 42 | 43 | 44 | 45 | 46 | {% block body %} 47 | {% endblock body %} 48 | 49 | 50 | 51 | {% block javascript %} 52 | {% endblock javascript %} 53 | 54 | 55 | {% block google_analytics %} 56 | {% endblock google_analytics %} 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /django_apps/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | {% load render_bundle from webpack_loader %} 4 | 5 | {% block body %} 6 |
7 | {% endblock body %} 8 | 9 | {% block javascript %} 10 | {% render_bundle 'main' %} 11 | {% endblock javascript %} 12 | -------------------------------------------------------------------------------- /django_config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genomics-geek/django_reactjs_boilerplate/7668038f182c1600324a2c62c29f490f8203c9dc/django_config/__init__.py -------------------------------------------------------------------------------- /django_config/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genomics-geek/django_reactjs_boilerplate/7668038f182c1600324a2c62c29f490f8203c9dc/django_config/settings/__init__.py -------------------------------------------------------------------------------- /django_config/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_config project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import dj_database_url 14 | import os 15 | 16 | from decouple import config 17 | 18 | 19 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 20 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 21 | APPS_DIR = os.path.join(BASE_DIR, 'django_apps') 22 | 23 | 24 | # SECRET KEY 25 | # ------------------------------------------------------------------------------ 26 | # https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-SECRET_KEY 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | 29 | SECRET_KEY = config('SECRET_KEY') 30 | 31 | 32 | # DEBUG 33 | # ------------------------------------------------------------------------------ 34 | # https://docs.djangoproject.com/en/1.10/ref/settings/#debug 35 | # SECURITY WARNING: don't run with debug turned on in production! 36 | 37 | DEBUG = config('DEBUG', cast=bool) 38 | 39 | 40 | # MANAGER CONFIGURATION 41 | # ------------------------------------------------------------------------------ 42 | # https://docs.djangoproject.com/en/1.10/ref/settings/#admins 43 | # https://docs.djangoproject.com/en/1.10/ref/settings/#managers 44 | 45 | ADMINS = ( 46 | ("""Michael A. Gonzalez""", 'GonzalezMA.CHOP@gmail.com'), 47 | ) 48 | 49 | MANAGERS = ADMINS 50 | 51 | 52 | # APP CONFIGURATION 53 | # ------------------------------------------------------------------------------ 54 | # https://docs.djangoproject.com/en/1.10/ref/settings/#installed-apps 55 | 56 | DJANGO_APPS = [ 57 | 'django.contrib.admin', 58 | 'django.contrib.auth', 59 | 'django.contrib.contenttypes', 60 | 'django.contrib.sessions', 61 | 'django.contrib.sites', 62 | 'django.contrib.messages', 63 | 'django.contrib.staticfiles', 64 | ] 65 | 66 | THIRD_PARTY_APPS = [ 67 | 'allauth', 68 | 'allauth.account', 69 | 'rest_framework', 70 | 'rest_framework.authtoken', 71 | 'rest_auth', 72 | 'webpack_loader', 73 | ] 74 | 75 | LOCAL_APPS = [] 76 | 77 | INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS 78 | 79 | 80 | # MIDDLEWARE CONFIGURATION 81 | # ------------------------------------------------------------------------------ 82 | # https://docs.djangoproject.com/en/1.10/topics/http/middleware/ 83 | 84 | DJANGO_SECURITY_MIDDLEWARE = [ 85 | 'django.middleware.security.SecurityMiddleware', 86 | ] 87 | 88 | DJANGO_MIDDLEWARE = [ 89 | 'django.contrib.sessions.middleware.SessionMiddleware', 90 | 'django.middleware.common.CommonMiddleware', 91 | 'django.middleware.csrf.CsrfViewMiddleware', 92 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 93 | 'django.contrib.messages.middleware.MessageMiddleware', 94 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 95 | ] 96 | 97 | MIDDLEWARE = DJANGO_SECURITY_MIDDLEWARE + DJANGO_MIDDLEWARE 98 | 99 | 100 | # URL Configuration 101 | # ------------------------------------------------------------------------------ 102 | # https://docs.djangoproject.com/en/1.10/ref/settings/#root-urlconf 103 | 104 | ROOT_URLCONF = 'django_config.urls' 105 | 106 | 107 | # TEMPLATE CONFIGURATION 108 | # ------------------------------------------------------------------------------ 109 | # https://docs.djangoproject.com/en/1.10/topics/templates/ 110 | 111 | TEMPLATES = [ 112 | { 113 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 114 | 'DIRS': [os.path.join(APPS_DIR, 'templates')], 115 | 'APP_DIRS': True, 116 | 'OPTIONS': { 117 | 'context_processors': [ 118 | 'django.template.context_processors.debug', 119 | 'django.template.context_processors.request', 120 | 'django.contrib.auth.context_processors.auth', 121 | 'django.contrib.messages.context_processors.messages', 122 | ], 123 | }, 124 | }, 125 | ] 126 | 127 | 128 | # WGSI CONFIGURATION 129 | # ------------------------------------------------------------------------------ 130 | # https://docs.djangoproject.com/en/1.10/ref/settings/#wsgi-application 131 | 132 | WSGI_APPLICATION = 'django_config.wsgi.application' 133 | 134 | 135 | # DATABASE CONFIGURATION 136 | # ------------------------------------------------------------------------------ 137 | # See: https://docs.djangoproject.com/en/1.10/ref/settings/#databases 138 | 139 | DATABASES = { 140 | 'default': dj_database_url.parse(config('DATABASE_URL')), 141 | } 142 | DATABASES['default']['ATOMIC_REQUESTS'] = True 143 | 144 | # Added this to support deployment on Heroku 145 | # https://devcenter.heroku.com/articles/django-app-configuration 146 | db_from_env = dj_database_url.config(conn_max_age=500) 147 | DATABASES['default'].update(db_from_env) 148 | 149 | 150 | # PASSWORD VALIDATION 151 | # ------------------------------------------------------------------------------ 152 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 153 | 154 | AUTH_PASSWORD_VALIDATORS = [ 155 | { 156 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 157 | }, 158 | { 159 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 160 | }, 161 | { 162 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 163 | }, 164 | { 165 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 166 | }, 167 | ] 168 | 169 | 170 | # GENERAL CONFIGURATION 171 | # ------------------------------------------------------------------------------ 172 | # Local time zone for this installation. Choices can be found here: 173 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 174 | # although not all choices may be available on all operating systems. 175 | # In a Windows environment this must be set to your system time zone. 176 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 177 | 178 | LANGUAGE_CODE = 'en-us' 179 | 180 | TIME_ZONE = 'UTC' 181 | 182 | USE_I18N = True 183 | 184 | USE_L10N = True 185 | 186 | USE_TZ = True 187 | 188 | SITE_ID = 1 189 | 190 | 191 | # STATIC FILE CONFIGURATION 192 | # ------------------------------------------------------------------------------ 193 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 194 | 195 | STATIC_URL = '/static/' 196 | 197 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ] 198 | 199 | STATIC_ROOT = os.path.join(BASE_DIR, 'static_cdn') 200 | 201 | 202 | # MEDIA CONFIGURATION 203 | # ------------------------------------------------------------------------------ 204 | # https://docs.djangoproject.com/en/1.10/ref/settings/#media-root 205 | # https://docs.djangoproject.com/en/1.10/ref/settings/#media-url 206 | 207 | MEDIA_URL = "/media/" 208 | 209 | MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media_cdn") 210 | 211 | 212 | # Django REST framework 213 | # ------------------------------------------------------------------------------ 214 | # http://www.django-rest-framework.org/ 215 | 216 | REST_FRAMEWORK = { 217 | 'DEFAULT_PERMISSION_CLASSES': ( 218 | 'rest_framework.permissions.IsAuthenticated', 219 | ), 220 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 221 | 'rest_framework.authentication.SessionAuthentication', 222 | 'rest_framework.authentication.BasicAuthentication', 223 | 'rest_framework.authentication.TokenAuthentication', 224 | 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 225 | ), 226 | } 227 | 228 | 229 | # Django REST-AUTH framework 230 | # ------------------------------------------------------------------------------ 231 | # https://github.com/Tivix/django-rest-auth/ 232 | 233 | REST_USE_JWT = True 234 | 235 | 236 | # EMAIL CONFIGURATION 237 | # ------------------------------------------------------------------------------ 238 | # https://docs.djangoproject.com/en/1.10/topics/email/ 239 | 240 | # EMAIL CONFIGURATION 241 | # ------------------------------------------------------------------------------ 242 | # https://docs.djangoproject.com/en/1.10/topics/email/ 243 | 244 | EMAIL_PORT = config('EMAIL_PORT') 245 | 246 | EMAIL_HOST = config('EMAIL_HOST') 247 | 248 | EMAIL_BACKEND = config('EMAIL_BACKEND') 249 | 250 | DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL') 251 | -------------------------------------------------------------------------------- /django_config/settings/local.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from .base import * 4 | 5 | 6 | # Webpack Loader by Owais Lane 7 | # ------------------------------------------------------------------------------ 8 | # https://github.com/owais/django-webpack-loader 9 | 10 | WEBPACK_LOADER = { 11 | 'DEFAULT': { 12 | 'BUNDLE_DIR_NAME': 'js/builds-dev/', 13 | 'STATS_FILE': os.path.join(BASE_DIR, 'webpack', 'webpack-stats.dev.json') 14 | } 15 | } 16 | 17 | # Django Debug Toolbar 18 | # ------------------------------------------------------------------------------ 19 | # https://github.com/jazzband/django-debug-toolbar 20 | 21 | MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) 22 | 23 | INSTALLED_APPS += ('debug_toolbar', ) 24 | 25 | INTERNAL_IPS = ['127.0.0.1', '10.0.2.2', ] 26 | 27 | # Hack to have debug toolbar when developing with docker 28 | ip = socket.gethostbyname(socket.gethostname()) 29 | INTERNAL_IPS += [ip[:-1] + "1"] 30 | -------------------------------------------------------------------------------- /django_config/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | 4 | # Webpack Loader by Owais Lane 5 | # ------------------------------------------------------------------------------ 6 | # https://github.com/owais/django-webpack-loader 7 | 8 | WEBPACK_LOADER = { 9 | 'DEFAULT': { 10 | 'BUNDLE_DIR_NAME': 'js/builds/', 11 | 'STATS_FILE': os.path.join(BASE_DIR, 'webpack', 'webpack-stats.production.json') 12 | } 13 | } 14 | 15 | 16 | # Use Whitenoise to serve static files 17 | # ------------------------------------------------------------------------------ 18 | # https://whitenoise.readthedocs.io/ 19 | 20 | MIDDLEWARE = DJANGO_SECURITY_MIDDLEWARE + ['whitenoise.middleware.WhiteNoiseMiddleware'] + DJANGO_MIDDLEWARE 21 | 22 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 23 | 24 | 25 | # Use Gunicorn as WSGI HTTP server 26 | # ------------------------------------------------------------------------------ 27 | # http://gunicorn.org/ 28 | 29 | INSTALLED_APPS += ('gunicorn', ) 30 | 31 | 32 | # SITE CONFIGURATION 33 | # ------------------------------------------------------------------------------ 34 | # Hosts/domain names that are valid for this site 35 | # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts 36 | 37 | ALLOWED_HOSTS = config( 38 | 'ALLOWED_HOSTS', 39 | cast=lambda v: [d for d in [s.strip() for s in v.split(' ')] if d], 40 | default='', 41 | ) 42 | 43 | 44 | # EMAIL CONFIGURATION - Anymail with Mailgun 45 | # ------------------------------------------------------------------------------ 46 | # https://docs.djangoproject.com/en/1.10/topics/email/ 47 | # https://github.com/anymail/django-anymail 48 | 49 | INSTALLED_APPS += ('anymail', ) 50 | 51 | ANYMAIL = { 52 | 'MAILGUN_API_KEY': config('MAILGUN_API_KEY'), 53 | } 54 | 55 | 56 | # LOGGING CONFIGURATION 57 | # ------------------------------------------------------------------------------ 58 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging 59 | # A sample logging configuration. The only tangible logging 60 | # performed by this configuration is to send an email to 61 | # the site admins on every HTTP 500 error when DEBUG=False. 62 | # See http://docs.djangoproject.com/en/dev/topics/logging for 63 | # more details on how to customize your logging configuration. 64 | 65 | LOGGING = { 66 | 'version': 1, 67 | 'disable_existing_loggers': False, 68 | 'filters': { 69 | 'require_debug_false': { 70 | '()': 'django.utils.log.RequireDebugFalse' 71 | } 72 | }, 73 | 'formatters': { 74 | 'verbose': { 75 | 'format': '%(levelname)s %(asctime)s %(module)s ' 76 | '%(process)d %(thread)d %(message)s' 77 | }, 78 | }, 79 | 'handlers': { 80 | 'mail_admins': { 81 | 'level': 'ERROR', 82 | 'filters': ['require_debug_false'], 83 | 'class': 'django.utils.log.AdminEmailHandler' 84 | }, 85 | 'console': { 86 | 'level': 'DEBUG', 87 | 'class': 'logging.StreamHandler', 88 | 'formatter': 'verbose', 89 | }, 90 | }, 91 | 'loggers': { 92 | 'django.request': { 93 | 'handlers': ['mail_admins'], 94 | 'level': 'ERROR', 95 | 'propagate': True 96 | }, 97 | 'django.security.DisallowedHost': { 98 | 'level': 'ERROR', 99 | 'handlers': ['console', 'mail_admins'], 100 | 'propagate': True 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /django_config/urls.py: -------------------------------------------------------------------------------- 1 | """django_config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls import include, url 18 | from django.contrib import admin 19 | from django.views.generic import TemplateView 20 | 21 | urlpatterns = [ 22 | url(r'^$', TemplateView.as_view(template_name="index.html")), 23 | url(r'^rest-auth/', include('rest_auth.urls')), 24 | url(r'^rest-auth/registration/', include('rest_auth.registration.urls')), 25 | url(r'^admin/', admin.site.urls), 26 | ] 27 | 28 | if settings.DEBUG: 29 | import debug_toolbar 30 | urlpatterns += [ 31 | url(r'^__debug__/', include(debug_toolbar.urls)), 32 | ] 33 | -------------------------------------------------------------------------------- /django_config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_config 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 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /docker-compose-development.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | postgres_data_dev: {} 5 | postgres_backup_dev: {} 6 | 7 | services: 8 | postgres: 9 | container_name: postgres_server 10 | build: ./docker_compose/postgres 11 | volumes: 12 | - postgres_data_dev:/var/lib/postgresql/data 13 | - postgres_backup_dev:/backups 14 | env_file: .env 15 | 16 | node: 17 | container_name: node_server 18 | build: 19 | context: . 20 | dockerfile: ./docker_compose/node/development/Dockerfile 21 | command: /start.sh 22 | volumes: 23 | - .:/app 24 | ports: 25 | - "3000:3000" 26 | 27 | django: 28 | container_name: django_server 29 | build: 30 | context: . 31 | dockerfile: ./docker_compose/django/development/Dockerfile 32 | command: /start.sh 33 | depends_on: 34 | - postgres 35 | - node 36 | env_file: .env 37 | volumes: 38 | - .:/app 39 | ports: 40 | - "8000:8000" 41 | links: 42 | - postgres 43 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | postgres_data: {} 5 | postgres_backup: {} 6 | 7 | services: 8 | postgres: 9 | container_name: postgres_server 10 | build: ./docker_compose/postgres 11 | volumes: 12 | - postgres_data:/var/lib/postgresql/data 13 | - postgres_backup:/backups 14 | env_file: .env 15 | 16 | django: 17 | container_name: django_server 18 | build: 19 | context: . 20 | dockerfile: ./docker_compose/django/production/Dockerfile 21 | user: django 22 | depends_on: 23 | - postgres 24 | command: /start.sh 25 | env_file: .env 26 | 27 | nginx: 28 | container_name: nginx_server 29 | build: ./docker_compose/nginx 30 | depends_on: 31 | - django 32 | ports: 33 | - "0.0.0.0:80:80" 34 | -------------------------------------------------------------------------------- /docker_compose/django/development/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | # Setup Debian linux 6 | RUN export DEBIAN_FRONTEND=noninteractive 7 | RUN apt-get update && apt-get -y install build-essential curl 8 | RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - 9 | RUN apt-get install -y nodejs 10 | RUN nodejs -v && npm -v 11 | 12 | # Requirements have to be pulled and installed here, otherwise caching won't work 13 | COPY ./requirements /requirements 14 | COPY package.json /app/ 15 | 16 | WORKDIR /app 17 | COPY . /app 18 | 19 | RUN pip install -r /requirements/local.txt && npm install && npm run build-development 20 | 21 | COPY ./docker_compose/django/development/start.sh /start.sh 22 | COPY ./docker_compose/django/entrypoint.sh /entrypoint.sh 23 | RUN sed -i 's/\r//' /entrypoint.sh \ 24 | && sed -i 's/\r//' /start.sh \ 25 | && chmod +x /entrypoint.sh \ 26 | && chmod +x /start.sh 27 | 28 | ENTRYPOINT ["/entrypoint.sh"] 29 | -------------------------------------------------------------------------------- /docker_compose/django/development/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python manage.py migrate 3 | python manage.py runserver 0.0.0.0:8000 4 | -------------------------------------------------------------------------------- /docker_compose/django/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cmd="$@" 4 | 5 | # This entrypoint is used to play nicely with the current cookiecutter configuration. 6 | # Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple 7 | # environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint 8 | # does all this for us. 9 | 10 | # the official postgres image uses 'postgres' as default user if not set explictly. 11 | if [ -z "$POSTGRES_USER" ]; then 12 | export POSTGRES_USER=postgres 13 | fi 14 | 15 | # If not DB is set, then use USER by default 16 | if [ -z "$POSTGRES_DB" ]; then 17 | export POSTGRES_DB=$POSTGRES_USER 18 | fi 19 | 20 | # Need to update the DATABASE_URL if using DOCKER 21 | export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_DB 22 | 23 | 24 | function postgres_ready(){ 25 | python << END 26 | import sys 27 | import psycopg2 28 | try: 29 | conn = psycopg2.connect(dbname="$POSTGRES_DB", user="$POSTGRES_USER", password="$POSTGRES_PASSWORD", host="postgres") 30 | except psycopg2.OperationalError: 31 | sys.exit(-1) 32 | sys.exit(0) 33 | END 34 | } 35 | 36 | until postgres_ready; do 37 | >&2 echo "Postgres is unavailable - sleeping" 38 | sleep 1 39 | done 40 | 41 | >&2 echo "Postgres is up - continuing..." 42 | exec $cmd 43 | -------------------------------------------------------------------------------- /docker_compose/django/production/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | # Setup Debian linux 6 | RUN export DEBIAN_FRONTEND=noninteractive 7 | RUN apt-get update && apt-get -y install build-essential curl 8 | RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - 9 | RUN apt-get install -y nodejs 10 | RUN nodejs -v && npm -v 11 | 12 | # Requirements have to be pulled and installed here, otherwise caching won't work 13 | COPY ./requirements /requirements 14 | COPY package.json /app/ 15 | 16 | WORKDIR /app 17 | COPY . /app 18 | 19 | RUN pip install -r /requirements/production.txt \ 20 | && groupadd -r django \ 21 | && useradd -r -g django django \ 22 | && npm install && npm run build-production 23 | 24 | RUN chown -R django /app 25 | 26 | COPY ./docker_compose/django/production/start.sh /start.sh 27 | COPY ./docker_compose/django/entrypoint.sh /entrypoint.sh 28 | RUN sed -i 's/\r//' /entrypoint.sh \ 29 | && sed -i 's/\r//' /start.sh \ 30 | && chmod +x /entrypoint.sh \ 31 | && chown django /entrypoint.sh \ 32 | && chmod +x /start.sh \ 33 | && chown django /start.sh 34 | 35 | ENTRYPOINT ["/entrypoint.sh"] 36 | -------------------------------------------------------------------------------- /docker_compose/django/production/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python /app/manage.py collectstatic --noinput 3 | /usr/local/bin/gunicorn django_config.wsgi -w 4 -b 0.0.0.0:5000 --chdir=/app 4 | -------------------------------------------------------------------------------- /docker_compose/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | ADD nginx.conf /etc/nginx/nginx.conf 3 | -------------------------------------------------------------------------------- /docker_compose/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | include /etc/nginx/mime.types; 13 | default_type application/octet-stream; 14 | 15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 16 | '$status $body_bytes_sent "$http_referer" ' 17 | '"$http_user_agent" "$http_x_forwarded_for"'; 18 | 19 | access_log /var/log/nginx/access.log main; 20 | 21 | sendfile on; 22 | #tcp_nopush on; 23 | 24 | keepalive_timeout 65; 25 | 26 | #gzip on; 27 | 28 | upstream app { 29 | server django:5000; 30 | } 31 | 32 | server { 33 | listen 80; 34 | charset utf-8; 35 | 36 | 37 | 38 | location / { 39 | # checks for static file, if not found proxy to app 40 | try_files $uri @proxy_to_app; 41 | } 42 | 43 | # django app 44 | location @proxy_to_app { 45 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 46 | proxy_set_header Host $http_host; 47 | proxy_redirect off; 48 | proxy_pass http://app; 49 | 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docker_compose/node/development/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | COPY . /app 4 | 5 | WORKDIR /app 6 | RUN npm install 7 | 8 | COPY ./docker_compose/node/development/start.sh /start.sh 9 | RUN sed -i 's/\r//' /start.sh 10 | RUN chmod +x /start.sh 11 | -------------------------------------------------------------------------------- /docker_compose/node/development/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npm run watch 3 | -------------------------------------------------------------------------------- /docker_compose/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:latest 2 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_config.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "django_reactjs_boilerplate", 3 | "version": "0.1.0", 4 | "description": "A boilerplate for a Django REST/ReactJS project using Redux, Webpack, Hot Reloading, React Hot Reloader 3, Karma, and Mocha", 5 | "main": "index.jsx", 6 | "scripts": { 7 | "build-development": "webpack --config webpack/webpack.local.config.js --progress --colors", 8 | "build-production": "webpack --config webpack/webpack.production.config.js --progress --colors", 9 | "watch": "webpack-dev-server --config webpack/webpack.local.config.js", 10 | "test": "./node_modules/karma/bin/karma start webpack/karma.config.js --log-level debug" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/genomics-geek/django_reactjs_boilerplate.git" 15 | }, 16 | "keywords": [ 17 | "Django", 18 | "ReactJS", 19 | "Redux", 20 | "React-Hot-Reloader-3", 21 | "Webpack", 22 | "Karma", 23 | "Mocha" 24 | ], 25 | "author": "Michael A. Gonzalez", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/genomics-geek/django_reactjs_boilerplate/issues" 29 | }, 30 | "homepage": "https://github.com/genomics-geek/django_reactjs_boilerplate#readme", 31 | "devDependencies": { 32 | "axios": "^0.15.2", 33 | "babel-cli": "^6.18.0", 34 | "babel-core": "^6.18.0", 35 | "babel-eslint": "^7.1.0", 36 | "babel-loader": "^6.2.6", 37 | "babel-preset-es2015": "^6.18.0", 38 | "babel-preset-react": "^6.16.0", 39 | "babel-preset-stage-2": "^6.18.0", 40 | "css-loader": "^0.25.0", 41 | "deepfreeze": "^2.0.0", 42 | "enzyme": "^2.5.1", 43 | "eslint": "^3.8.1", 44 | "eslint-config-airbnb": "^12.0.0", 45 | "eslint-plugin-import": "^1.16.0", 46 | "eslint-plugin-jsx-a11y": "^2.2.3", 47 | "eslint-plugin-react": "^6.4.1", 48 | "expect": "^1.20.2", 49 | "karma": "^1.3.0", 50 | "karma-babel-preprocessor": "^6.0.1", 51 | "karma-chrome-launcher": "^2.0.0", 52 | "karma-mocha": "^1.2.0", 53 | "karma-sourcemap-loader": "^0.3.7", 54 | "karma-webpack": "^1.8.0", 55 | "lodash": "^4.16.4", 56 | "mocha": "^3.1.2", 57 | "react": "^15.3.2", 58 | "react-bootstrap": "^0.30.5", 59 | "react-cookie": "^0.4.8", 60 | "react-dom": "^15.3.2", 61 | "react-fontawesome": "^1.2.0", 62 | "react-hot-loader": "^3.0.0-beta.6", 63 | "react-redux": "^4.4.5", 64 | "react-router": "^3.0.0", 65 | "react-router-redux": "^4.0.6", 66 | "redux": "^3.6.0", 67 | "redux-devtools": "^3.3.1", 68 | "redux-devtools-dock-monitor": "^1.1.1", 69 | "redux-devtools-log-monitor": "^1.1.0", 70 | "redux-thunk": "^2.1.0", 71 | "style-loader": "^0.13.1", 72 | "webpack": "^1.13.3", 73 | "webpack-bundle-tracker": "0.0.93", 74 | "webpack-dev-server": "^1.16.2" 75 | }, 76 | "dependencies": { 77 | "react": "^15.3.2", 78 | "react-dom": "^15.3.2" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | # Django 2 | django==1.10.2 3 | 4 | # Configuration 5 | dj-database-url==0.4.1 6 | python-decouple==3.0 7 | whitenoise==3.2.2 8 | 9 | # Django 3rd party modules 10 | django-allauth==0.28.0 11 | django-model-utils==2.6 12 | django-rest-auth==0.8.1 13 | django-webpack-loader==0.3.3 14 | djangorestframework==3.5.1 15 | djangorestframework-jwt==1.8.0 16 | 17 | # Python-PostgreSQL Database Adapter 18 | psycopg2==2.6.2 19 | 20 | # Time zones support 21 | pytz==2016.7 22 | -------------------------------------------------------------------------------- /requirements/local.txt: -------------------------------------------------------------------------------- 1 | # Import all base requirements 2 | -r base.txt 3 | 4 | # Django debug toolbar 5 | django-debug-toolbar==1.6 6 | 7 | # Code linter 8 | pep8==1.7.0 9 | -------------------------------------------------------------------------------- /requirements/production.txt: -------------------------------------------------------------------------------- 1 | # Import all base requirements 2 | # Pro-tip: Try not to put anything here. Avoid dependencies in production that aren't in development. 3 | -r base.txt 4 | 5 | # WSGI Handler 6 | gunicorn==19.6.0 7 | 8 | # Email backend 9 | django-anymail==0.5 10 | -------------------------------------------------------------------------------- /static/js/src/example.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | describe('Something abstract', () => { 4 | it('works', () => { 5 | expect(1).toEqual(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /static/js/src/main/components/hello-world.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const HelloWorld = () => { 4 | return ( 5 |

Hello World!

6 | ); 7 | }; 8 | 9 | export default HelloWorld; 10 | -------------------------------------------------------------------------------- /static/js/src/main/containers/DevTools/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | 6 | 7 | const DevTools = createDevTools( 8 | 9 | 10 | 11 | ); 12 | 13 | 14 | export default DevTools; 15 | -------------------------------------------------------------------------------- /static/js/src/main/containers/Root/Root.dev.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | 5 | import DevTools from '../DevTools'; 6 | import routes from '../../routes'; 7 | 8 | 9 | const Root = ({ store, history }) => { 10 | return ( 11 | 12 |
13 | 14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | Root.propTypes = { 21 | store: React.PropTypes.object.isRequired, 22 | history: React.PropTypes.object.isRequired 23 | }; 24 | 25 | 26 | export default Root; 27 | -------------------------------------------------------------------------------- /static/js/src/main/containers/Root/Root.prod.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | 5 | import routes from '../../routes'; 6 | 7 | 8 | const Root = ({ store, history }) => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | Root.propTypes = { 17 | store: React.PropTypes.object.isRequired, 18 | history: React.PropTypes.object.isRequired 19 | }; 20 | 21 | 22 | export default Root; 23 | -------------------------------------------------------------------------------- /static/js/src/main/containers/Root/index.jsx: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./Root.prod'); 3 | } else { 4 | module.exports = require('./Root.dev') 5 | } 6 | -------------------------------------------------------------------------------- /static/js/src/main/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import { browserHistory } from 'react-router'; 5 | import { syncHistoryWithStore } from 'react-router-redux'; 6 | 7 | import Root from './containers/Root'; 8 | import configureStore from './store'; 9 | 10 | 11 | const store = configureStore(); 12 | const history = syncHistoryWithStore(browserHistory, store); 13 | 14 | 15 | ReactDOM.render( 16 | 17 | 18 | , 19 | document.getElementById('react-root') 20 | ); 21 | 22 | if (module.hot) { 23 | module.hot.accept('./containers/Root', () => { 24 | const Root = require('./containers/Root').default; 25 | ReactDOM.render( 26 | 27 | 28 | , 29 | document.getElementById('react-root') 30 | ); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /static/js/src/main/reducers/index.jsx: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | 5 | // Import reducers here 6 | 7 | const reducers = {}; 8 | 9 | const rootReducer = combineReducers({ 10 | ...reducers, 11 | routing: routerReducer 12 | }); 13 | 14 | 15 | export default rootReducer; 16 | -------------------------------------------------------------------------------- /static/js/src/main/routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router'; 3 | 4 | import HelloWorld from './components/hello-world'; 5 | 6 | 7 | const routes = ( 8 | 9 | ); 10 | 11 | 12 | export default routes; 13 | -------------------------------------------------------------------------------- /static/js/src/main/store/configureStore.dev.jsx: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | 4 | import DevTools from '../containers/DevTools'; 5 | import rootReducer from '../reducers'; 6 | 7 | 8 | const enhancer = compose( 9 | // Middleware you want to use in development 10 | applyMiddleware(thunk), 11 | // Required! Enable Redux DevTools with the monitors you chose 12 | DevTools.instrument() 13 | ); 14 | 15 | 16 | const configureStore = (initialState) => { 17 | // Note: only Redux >= 3.1.0 supports passing enhancer as third argument 18 | // See: https://github.com/rackt/redux/releases/tag/v3.1.0 19 | const store = createStore(rootReducer, initialState, enhancer); 20 | 21 | // Hot Reload reducers 22 | // Note: Requires Webpack or Browserify HMR to be enabled 23 | if (module.hot) { 24 | module.hot.accept('../reducers', () => 25 | store.replaceReducer(require('../reducers').default) 26 | ); 27 | } 28 | return store; 29 | }; 30 | 31 | 32 | export default configureStore; 33 | -------------------------------------------------------------------------------- /static/js/src/main/store/configureStore.prod.jsx: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | 4 | import rootReducer from '../reducers'; 5 | 6 | 7 | // Middleware you want to use in production 8 | const enhancer = applyMiddleware(thunk); 9 | 10 | const configureStore = (initialState) => { 11 | // Note: only Redux >= 3.1.0 supports passing enhancer as third argument 12 | // See: https://github.com/rackt/redux/releases/tag/v3.1.0 13 | return createStore(rootReducer, initialState, enhancer); 14 | }; 15 | 16 | 17 | export default configureStore; 18 | -------------------------------------------------------------------------------- /static/js/src/main/store/index.jsx: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.prod'); 3 | } else { 4 | module.exports = require('./configureStore.dev') 5 | } 6 | -------------------------------------------------------------------------------- /static/js/src/test_index.js: -------------------------------------------------------------------------------- 1 | var testsContext = require.context('.', true, /.spec$/); 2 | testsContext.keys().forEach(testsContext); 3 | -------------------------------------------------------------------------------- /static/js/src/utilities/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as cookie from './cookie'; 3 | 4 | 5 | const api = axios.create({ 6 | baseURL: process.env.BASE_URL, 7 | timeout: 1000, 8 | }); 9 | 10 | // CRSF token is needed in all requests that can make a change server side 11 | api.defaults.headers.post['X-CSRFToken'] = cookie.csrftoken; 12 | api.defaults.headers.put['X-CSRFToken'] = cookie.csrftoken; 13 | api.defaults.headers.patch['X-CSRFToken'] = cookie.csrftoken; 14 | // api.defaults.headers.delete['X-CSRFToken'] = cookie.csrftoken; // Currently axios can't set headers for DELETE 15 | 16 | // Since we will only be using JSON APIs, add Content-Type: application/json to header as default 17 | api.defaults.headers.post['Content-Type'] = 'application/json'; 18 | api.defaults.headers.put['Content-Type'] = 'application/json'; 19 | api.defaults.headers.patch['Content-Type'] = 'application/json'; 20 | 21 | // Since we will only be using JSON APIs, add Accept: application/json to header as default 22 | api.defaults.headers.get.Accept = 'application/json'; 23 | api.defaults.headers.post.Accept = 'application/json'; 24 | api.defaults.headers.put.Accept = 'application/json'; 25 | api.defaults.headers.patch.Accept = 'application/json'; 26 | 27 | // JWT is going to be saved into cookie 28 | // cookies.save('jwt', response.data.token, { secure: true, httpOnly: true }); 29 | // Therefore it will automatically be sent in the header of all API requests 30 | // JWT will not be accessible to JavaScript because it is httpOnly :) 31 | 32 | export default api; 33 | -------------------------------------------------------------------------------- /static/js/src/utilities/cookie.js: -------------------------------------------------------------------------------- 1 | import cookies from 'react-cookie'; 2 | 3 | 4 | // This is the place where we can load elements from a cookie to be used in our app 5 | 6 | // Django CRSF Token is stored in a cookie 7 | const csrftoken = cookies.load('csrftoken'); 8 | 9 | // JWT is going to be saved into cookie 10 | // cookies.save('jwt', response.data.token, { secure: true, httpOnly: true }); 11 | // Therefore it will automatically be sent in the header of all API requests 12 | // JWT will NOT be accessible to JavaScript because it is httpOnly :) 13 | 14 | export { csrftoken }; 15 | -------------------------------------------------------------------------------- /webpack/karma.config.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('./webpack.local.config.js'); 2 | 3 | webpackConfig.entry = {}; 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: '', 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha'], 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | '../static/js/src/test_index.js' 18 | ], 19 | 20 | // list of files to exclude 21 | exclude: [], 22 | 23 | // preprocess matching files before serving them to the browser 24 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 25 | preprocessors: { 26 | '../static/js/src/test_index.js': ['webpack', 'sourcemap'], 27 | }, 28 | 29 | // test results reporter to use 30 | // possible values: 'dots', 'progress' 31 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 32 | reporters: ['progress'], 33 | 34 | // web server port 35 | port: 9876, 36 | 37 | // enable / disable colors in the output (reporters and logs) 38 | colors: true, 39 | 40 | // level of logging 41 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 42 | logLevel: config.LOG_INFO, 43 | 44 | // enable / disable watching file and executing tests whenever any file changes 45 | autoWatch: true, 46 | autoWatchBatchDelay: 300, 47 | 48 | // start these browsers 49 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 50 | browsers: ['Chrome'], 51 | 52 | // Continuous Integration mode 53 | // if true, Karma captures browsers, runs the tests and exits 54 | singleRun: false, 55 | 56 | // Concurrency level 57 | // how many browser should be started simultaneous 58 | concurrency: Infinity, 59 | 60 | // Webpack 61 | webpack: webpackConfig, 62 | webpackServer: { 63 | noInfo: true 64 | } 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /webpack/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | module: { 4 | loaders: [{ 5 | test: /\.(js|jsx)$/, 6 | exclude: /node_modules/, 7 | loader: 'babel-loader', 8 | query: { 9 | presets: ['es2015', 'stage-2', 'react'] 10 | } 11 | }, { 12 | test: /\.css$/, 13 | loader: 'style-loader!css-loader' 14 | }] 15 | }, 16 | 17 | resolve: { 18 | modulesDirectories: ['node_modules'], 19 | extensions: ['', '.js', '.jsx'] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /webpack/webpack.local.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var BundleTracker = require('webpack-bundle-tracker'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.base.config.js'); 5 | 6 | config.entry = { 7 | main: [ 8 | 'webpack-dev-server/client?http://0.0.0.0:3000', 9 | 'webpack/hot/only-dev-server', 10 | 'react-hot-loader/patch', 11 | path.join(__dirname, '../static/js/src/main/index') 12 | ] 13 | }; 14 | 15 | config.devtool = 'inline-sourcemap'; 16 | config.output = { 17 | path: path.join(__dirname, '../static/js/builds-development/'), 18 | filename: '[name]-[hash].js', 19 | publicPath: 'http://0.0.0.0:3000/static/builds/', 20 | }; 21 | 22 | config.plugins = [ 23 | new webpack.HotModuleReplacementPlugin(), 24 | new BundleTracker({ filename: './webpack/webpack-stats.dev.json' }), 25 | new webpack.DefinePlugin({ 26 | 'process.env': { 27 | NODE_ENV: JSON.stringify('development'), 28 | BASE_URL: JSON.stringify('http://0.0.0.0:8000/'), 29 | } 30 | }) 31 | ]; 32 | 33 | config.module.loaders[0].query.plugins = ['react-hot-loader/babel']; 34 | 35 | config.devServer = { 36 | inline: true, 37 | progress: true, 38 | hot: true, 39 | historyApiFallback: true, 40 | host: '0.0.0.0', 41 | port: 3000 42 | }; 43 | 44 | module.exports = config; 45 | -------------------------------------------------------------------------------- /webpack/webpack.production.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var BundleTracker = require('webpack-bundle-tracker'); 4 | var config = require('./webpack.base.config.js'); 5 | 6 | config.entry = { 7 | main: [ 8 | path.join(__dirname, '../static/js/src/main/index') 9 | ] 10 | }; 11 | 12 | config.output = { 13 | path: path.join(__dirname, '../static/js/builds/'), 14 | filename: '[name]-[hash].min.js', 15 | publicPath: '/static/js/builds/' 16 | }; 17 | 18 | config.plugins = [ 19 | new BundleTracker({ filename: './webpack/webpack-stats.production.json' }), 20 | new webpack.optimize.DedupePlugin(), 21 | new webpack.optimize.OccurenceOrderPlugin(), 22 | new webpack.DefinePlugin({ 23 | 'process.env': { 24 | NODE_ENV: JSON.stringify('production'), 25 | BASE_URL: JSON.stringify('http://0.0.0.0/'), 26 | } 27 | }), 28 | new webpack.optimize.UglifyJsPlugin({ 29 | mangle: false, 30 | sourcemap: false 31 | }) 32 | ]; 33 | 34 | module.exports = config; 35 | --------------------------------------------------------------------------------