├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── backend ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── diagram.epgz ├── diagram.svg ├── frontend ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── LoginView.js │ ├── ToDoView.js │ ├── config.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ └── setupTests.js ├── main_view.PNG ├── manage.py ├── react_container ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── storage.py ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── sign_in_view.PNG └── static └── empty.file /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '19 15 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript', 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Django ### 2 | *.log 3 | *.pot 4 | *.pyc 5 | __pycache__/ 6 | local_settings.py 7 | db.sqlite3 8 | db.sqlite3-journal 9 | media 10 | 11 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 12 | # in your Git repository. Update and uncomment the following line accordingly. 13 | # /staticfiles/ 14 | 15 | ### Django.Python Stack ### 16 | # Byte-compiled / optimized / DLL files 17 | *.py[cod] 18 | *$py.class 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | pip-wheel-metadata/ 38 | share/python-wheels/ 39 | *.egg-info/ 40 | .installed.cfg 41 | *.egg 42 | MANIFEST 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .nox/ 58 | .coverage 59 | .coverage.* 60 | .cache 61 | nosetests.xml 62 | coverage.xml 63 | *.cover 64 | .hypothesis/ 65 | .pytest_cache/ 66 | 67 | # Translations 68 | *.mo 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # pipenv 83 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 84 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 85 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 86 | # install all needed dependencies. 87 | #Pipfile.lock 88 | 89 | # celery beat schedule file 90 | celerybeat-schedule 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # Mr Developer 103 | .mr.developer.cfg 104 | .project 105 | .pydevproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | .dmypy.json 113 | dmypy.json 114 | 115 | # Pyre type checker 116 | .pyre/ 117 | 118 | # VS Code 119 | .vscode 120 | *.code-workspace -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Parijat Khan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django-React Boilerplate Template 2 | 3 | > This repository will not be updated for newer versions of Django and React or any other libraries. However, this approach can still be used while bootstraping a new project, with same architecture. 4 | 5 | This is a boilerplate/template project that can be used to build any websites (portfolio, e-commerce etc.) with Django backend and React frontend. It follows the Hybrid Model, which means it can be deployed as a single unit into the same server. It is set up to use session-based authentication and includes an example of how to use them. It also provides safety from CSRF attacks. This template does not require you to spin up different servers for backend and frontend. The Django server itself acts as a container to run the React application. 6 | 7 | > The frontend part of the code is given as an example and is not regularly updated. It is highly advisable to discard the frontend part only for reference and roll your own frontend. 8 | 9 | ## Installation 10 | Run following commands where you want to create the project 11 | ```cmd 12 | django-admin.py startproject --template=https://github.com/Parijat29/django-react-boilerplate/archive/master.zip 13 | cd 14 | pip install requirements.txt 15 | cd frontend 16 | npm install 17 | ``` 18 | 19 | ## Build/Run 20 | This repo has been configured with Dev/Prod parity in mind. Use the following commands to build/run the project in appropriate mode 21 | 22 | ### Development mode 23 | Run following commands at the root of the project directory 24 | ```cmd 25 | cd frontend 26 | npm run build 27 | cd .. 28 | SET DJANGO_ENV=development 29 | python manage.py makemigrations 30 | python manage.py migrate 31 | python manage.py createsuperuser 32 | python manage.py runserver 33 | cd frontend 34 | npm start 35 | ``` 36 | 37 | ### Production mode 38 | Run following commands at the root of the project directory 39 | ```cmd 40 | cd frontend 41 | npm run build 42 | cd .. 43 | SET DJANGO_ENV=production 44 | python manage.py collectstatic 45 | python manage.py makemigrations 46 | python manage.py migrate 47 | python manage.py createsuperuser 48 | waitress-serve backend.wsgi:application 49 | ``` 50 | 51 | ## Dependencies 52 | ### Python 53 | * Django==2.2.18 54 | * requests==2.22.0 55 | * whitenoise==4.1.4 56 | * djangorestframework==3.11.2 57 | * waitress==1.4.3 58 | * django-cors-headers==3.0.2 (Only used in Development/DEBUG mode) 59 | 60 | ### React 61 | * fetch==^1.1.0 62 | * react-router-dom==^5.1.2 63 | * @material-ui/core==^4.9.9 64 | * @material-ui/icons==^4.9.1 65 | 66 | 67 | ## What is Hybrid Model? 68 | Usually for Single Page Application (SPA)s, the straight forward approach is to serve the frontend and backend in different servers and communicate via REST APIs between the two servers. While this approach might make sense for big teams and enterprise level applications, for small teams or projects it might become an extra overhead to manage and deploy two different applications in two different server instances. 69 | The **“Hybrid Application”** model is usually implemented by building the frontend, injecting it into the backend’s build process, and deploying the result as a single unit. 70 | This template project follows the above logic to give you a boilerplate so that you can start coding your application right away without worrying about the complicated setup. 71 | 72 | ### Any Additional Benifits? ([^ref1]) 73 | * Good SEO. 74 | * The application can be treated as single unit 75 | * Good for trying out Modern JavaScript frameworks on existing Django projects 76 | 77 | ### How does the Authetication work? 78 | Since both the forntend and backend is served by same server, all the API calls will be of **same-origin**. This simplifies the authentication process. This boilerplate utilizes Django's built-in **Session-Based** authentication. Django has a very powerful middleware that helps against **CSRF** attacks. This boilerplate/template project utilizes the same middleware. Different permission based access control can also be used as per Django's core features. 79 | 80 | ### Why is django-cors-headers dependecy needed? 81 | **django-cors-headers** is used only in development or DEBUG mode. This project's **settings.py** is preconfigured to conditionally add it **django-cors-headers** and its related parameters only if the application is running in DEBUG mode. 82 | 83 | ### Are there any examples? 84 | The boilerplate project already consists of an example To-Do application. This example project showcases most of the important aspects. The example project uses [@material-ui](https://material-ui.com/) to bootstrap the UI. Please feel free to remove the dependencies if they are not required. 85 | 86 | ### High-level Architecture 87 | ![Alt text](./diagram.svg) 88 | 89 | ## How it looks? 90 | 91 | | ![Sign in view](./sign_in_view.PNG) | 92 | |:--:| 93 | | *Sign-in page* | 94 | 95 | | ![Main view](./main_view.PNG) | 96 | |:--:| 97 | | *Main page* | 98 | 99 | ## License 100 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 101 | 102 | ## Acknowledgments 103 | This boilerplate template is inspired by the article [Making React and Django play well together - the “hybrid app” model](https://medium.com/fractal-ideas/making-react-and-django-play-well-together-the-hybrid-app-model-215991793cf6) by [Aymeric Augustin](https://myks.org/en/) 104 | 105 | [^ref1]: https://medium.com/fractal-ideas/making-react-and-django-play-well-together-the-hybrid-app-model-215991793cf6 106 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/api/__init__.py -------------------------------------------------------------------------------- /api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import ToDo 3 | # Register your models here. 4 | 5 | admin.site.register(ToDo) -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.8 on 2020-04-05 07:10 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='ToDo', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('task', models.CharField(db_column='task', max_length=255)), 22 | ('is_complete', models.BooleanField(db_column='is_complete', default=False)), 23 | ('created_datetime', models.DateTimeField(auto_now_add=True)), 24 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | options={ 27 | 'verbose_name': 'To-Do', 28 | 'verbose_name_plural': 'To-Do List', 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | # Create your models here. 5 | 6 | # Dummy model 7 | class ToDo(models.Model): 8 | task = models.CharField(db_column='task', max_length=255) 9 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 10 | is_complete = models.BooleanField(db_column='is_complete', default=False) 11 | created_datetime = models.DateTimeField(auto_now_add=True, blank=False) 12 | 13 | class Meta: 14 | verbose_name = 'To-Do' 15 | verbose_name_plural = 'To-Do List' -------------------------------------------------------------------------------- /api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import ToDo 3 | 4 | class ToDoSerializer(serializers.ModelSerializer): 5 | class Meta: 6 | model = ToDo 7 | fields = ['task', 'is_complete', 'created_datetime'] 8 | -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('todo', views.ToDoListCreate.as_view()), 6 | ] -------------------------------------------------------------------------------- /api/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from rest_framework import serializers 3 | from rest_framework import generics, filters 4 | from .models import ToDo 5 | from .serializers import ToDoSerializer 6 | # Create your views here. 7 | 8 | class ToDoListCreate(generics.ListCreateAPIView): 9 | 10 | serializer_class = ToDoSerializer 11 | filter_backends = [filters.SearchFilter, filters.OrderingFilter] 12 | search_fields = ['task'] 13 | ordering_fields = ['created_datetime', 'is_complete'] 14 | 15 | 16 | def get_queryset(self): 17 | if self.request.user.is_anonymous: 18 | return ToDo.objects.none() 19 | return ToDo.objects.filter(created_by=self.request.user) 20 | 21 | def perform_create(self, serializer): 22 | if self.request.user.is_anonymous: 23 | raise serializers.ValidationError("Anonymous users not allowed to create records") 24 | serializer.save(created_by=self.request.user) -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/backend/__init__.py -------------------------------------------------------------------------------- /backend/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for backend project. 3 | Generated by 'django-admin startproject' using Django 2.1.8. 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/2.1/topics/settings/ 6 | For the full list of settings and their values, see 7 | https://docs.djangoproject.com/en/2.1/ref/settings/ 8 | """ 9 | 10 | import os 11 | 12 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 13 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 14 | 15 | BACKEND_DIR = BASE_DIR 16 | FRONTEND_DIR = os.path.abspath(os.path.join(BACKEND_DIR, 'frontend')) 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = '{{ secret_key }}' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = os.environ.get('DJANGO_ENV') == 'development' 26 | 27 | ALLOWED_HOSTS = ['localhost', '127.0.0.1'] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = [ 33 | 'react_container.apps.ReactContainerConfig', 34 | 'api.apps.ApiConfig', 35 | 'rest_framework'] 36 | 37 | # Add CORS only for Development setup 38 | if DEBUG: 39 | INSTALLED_APPS.append('corsheaders') 40 | 41 | INSTALLED_APPS.extend([ 42 | 'django.contrib.admin', 43 | 'django.contrib.auth', 44 | 'django.contrib.contenttypes', 45 | 'django.contrib.sessions', 46 | 'django.contrib.messages', 47 | 'whitenoise.runserver_nostatic', 48 | 'django.contrib.staticfiles', 49 | ]) 50 | 51 | # CORS allowed only in DEBUG mode 52 | if DEBUG: 53 | CORS_ORIGIN_ALLOW_ALL = True 54 | CORS_ALLOW_CREDENTIALS = True 55 | 56 | MIDDLEWARE = [ 57 | 'django.middleware.security.SecurityMiddleware', 58 | 'whitenoise.middleware.WhiteNoiseMiddleware', 59 | 'django.contrib.sessions.middleware.SessionMiddleware'] 60 | 61 | if DEBUG: 62 | MIDDLEWARE.append('corsheaders.middleware.CorsMiddleware') 63 | 64 | MIDDLEWARE.extend([ 65 | 'django.middleware.common.CommonMiddleware', 66 | 'django.middleware.csrf.CsrfViewMiddleware', 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 68 | 'django.contrib.messages.middleware.MessageMiddleware', 69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 70 | ]) 71 | 72 | ROOT_URLCONF = 'backend.urls' 73 | 74 | TEMPLATES = [ 75 | { 76 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 77 | 'DIRS': [os.path.join(FRONTEND_DIR, 'build')], # Use react frontend 78 | 'APP_DIRS': True, 79 | 'OPTIONS': { 80 | 'context_processors': [ 81 | 'django.template.context_processors.debug', 82 | 'django.template.context_processors.request', 83 | 'django.contrib.auth.context_processors.auth', 84 | 'django.contrib.messages.context_processors.messages', 85 | ], 86 | }, 87 | }, 88 | ] 89 | 90 | # Restrict APIs to authenticated users only in Production 91 | # Restrict APIs to authenticated users only in Production 92 | DEFAULT_PERMISSION = 'rest_framework.permissions.AllowAny' if DEBUG else 'rest_framework.permissions.IsAuthenticated' 93 | 94 | #Other settings based on DEBUG mode 95 | DEFAULT_RENDERER = ['rest_framework.renderers.JSONRenderer'] 96 | if DEBUG: 97 | DEFAULT_RENDERER.append('rest_framework.renderers.BrowsableAPIRenderer') 98 | 99 | REST_FRAMEWORK = { 100 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 101 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 102 | 'rest_framework.authentication.SessionAuthentication', 103 | ], 104 | 'DEFAULT_PERMISSION_CLASSES': [ 105 | DEFAULT_PERMISSION, 106 | ], 107 | 'DEFAULT_RENDERER_CLASSES': DEFAULT_RENDERER, 108 | 109 | 'DEFAULT_PARSER_CLASSES': [ 110 | 'rest_framework.parsers.JSONParser', 111 | ] 112 | } 113 | 114 | WSGI_APPLICATION = 'backend.wsgi.application' 115 | 116 | 117 | # Database 118 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 119 | 120 | DATABASES = { 121 | 'default': { 122 | 'ENGINE': 'django.db.backends.sqlite3', 123 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 124 | } 125 | } 126 | 127 | 128 | # Password validation 129 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 130 | 131 | AUTH_PASSWORD_VALIDATORS = [ 132 | { 133 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 134 | }, 135 | { 136 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 137 | }, 138 | { 139 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 140 | }, 141 | { 142 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 143 | }, 144 | ] 145 | 146 | 147 | # Internationalization 148 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 149 | 150 | LANGUAGE_CODE = 'en-us' 151 | 152 | TIME_ZONE = 'UTC' 153 | 154 | USE_I18N = True 155 | 156 | USE_L10N = True 157 | 158 | USE_TZ = True 159 | 160 | 161 | # Static files (CSS, JavaScript, Images) 162 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 163 | 164 | STATICFILES_DIRS = [os.path.join(FRONTEND_DIR, 'build', 'static')] 165 | STATICFILES_STORAGE = ('react_container.storage.StaticFilesStorage') 166 | STATIC_ROOT = os.path.join(BACKEND_DIR, 'static') 167 | 168 | STATIC_URL = '/static/' 169 | 170 | WHITENOISE_ROOT = os.path.join(FRONTEND_DIR, 'build') 171 | -------------------------------------------------------------------------------- /backend/urls.py: -------------------------------------------------------------------------------- 1 | """backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('api/', include('api.urls')), 22 | path('', include('react_container.urls')), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/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', 'backend.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /diagram.epgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/diagram.epgz -------------------------------------------------------------------------------- /diagram.svg: -------------------------------------------------------------------------------- 1 | 740,310false0,00,38.75#FFFFFFFF#999999FF2|Django Project ContainerArial|normal|normal|16px|none#666666FF1,2 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
Django Project Container
12 |
230,115false0,00,14.375000000000002#FFFFFFFF#666666FF2|<span xmlns="http://www.w3.org/1999/xhtml" style="font-size: 1em;">Serves the React Application<br />+<br />Also Handles Sign-In<br /><br /><span style="font-weight: bold;">React Container Application</span><br /></span>Arial|normal|normal|13px|none#000000FF1,1 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
Serves the React Application
+
Also Handles Sign-In

React Container Application
23 |
230,115false0,00,14.375000000000002#FFFFFFFF#666666FF2|<span xmlns="http://www.w3.org/1999/xhtml" style="font-size: 1em;">Serves the Data Required for the React Application<br />(<span style="font-style: italic;">Example API included for To-Do Application</span>)<br /><br /><span style="font-weight: bold;">API Application</span><br /></span>Arial|normal|normal|13px|none#000000FF1,1 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
Serves the Data Required for the React Application
(Example API included for To-Do Application)

API Application
34 |
1 35 | 36 | 412,1560,30.389610389610386Browser#FFFFFFFFLiberation Sans|bold|normal|13px|none1,1#FFFFFFFFtrue 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Browser 51 | Browser 52 | 53 | 200,80false0,00,10#FFFFFFFF#666666FF2|Handles User Interaction<br xmlns="http://www.w3.org/1999/xhtml" /><br xmlns="http://www.w3.org/1999/xhtml" /><span xmlns="http://www.w3.org/1999/xhtml" style="font-weight: bold;">React Application</span>"Liberation Sans",Arial,sans-serif|normal|normal|13px|none#000000FF1,1 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Handles User Interaction

React Application
64 |
-145,-300,235.5truetruemulti-straightfalse#666666FF2|"Liberation Sans",Arial,sans-serif|normal|normal|13px|none#000000FF1,1 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -70,79218,80truetruemulti-straightfalse#666666FF2|"Liberation Sans",Arial,sans-serif|normal|normal|13px|none#000000FF1,1 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 50,-6950,99truetruemulti-straightfalse#666666FF2|"Liberation Sans",Arial,sans-serif|normal|normal|13px|none#000000FF1,1 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | false100,0falseUser Interaction on the website#000000FF"Liberation Sans",Arial,sans-serif|normal|normal|13px|none0,0 98 | 99 | User Interaction on the website 100 | 200,0falseInitial Request to the server.<br xmlns="http://www.w3.org/1999/xhtml" />When User hits the URL"Liberation Sans",Arial,sans-serif|normal|normal|13px|none#000000FF 101 | 102 | 103 | 104 |
Initial Request to the server.
When User hits the URL
105 |
106 |
false100,0falseServer Responds with the React Application#000000FF"Liberation Sans",Arial,sans-serif|normal|normal|13px|none0,0 107 | 108 | Server Responds with the React Application 109 | 200,0false<div xmlns="http://www.w3.org/1999/xhtml" style="text-align: center;">Same-Origin Request/Response</div><div xmlns="http://www.w3.org/1999/xhtml" style="text-align: center;">For Data Exchange</div>"Liberation Sans",Arial,sans-serif|normal|normal|13px|none#000000FF 110 | 111 | 112 | 113 |
Same-Origin Request/Response
For Data Exchange
114 |
115 |
-------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.9.9", 7 | "@material-ui/icons": "^4.9.1", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.5.0", 10 | "@testing-library/user-event": "^7.2.1", 11 | "fetch": "^1.1.0", 12 | "react": "^16.14.0", 13 | "react-dom": "^16.14.0", 14 | "react-router-dom": "^5.1.2", 15 | "react-scripts": "4.0.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App Test 2 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './App.css'; 3 | import { 4 | BrowserRouter as Router, 5 | Switch, 6 | Route, 7 | Redirect 8 | } from "react-router-dom"; 9 | 10 | import SignIn from './LoginView'; 11 | import ToDoView from './ToDoView'; 12 | 13 | function App() { 14 | return ( 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | function AppEntry() { 22 | const [user, setUser] = useState(JSON.parse(sessionStorage.getItem('user') || '{}')); 23 | 24 | function updateUserInfo(userDetail) { 25 | setUser(userDetail); 26 | sessionStorage.setItem("user", JSON.stringify(userDetail)); 27 | sessionStorage.setItem('loggedin', true); 28 | } 29 | 30 | // A wrapper for that redirects to the login 31 | // screen if you're not yet authenticated. 32 | function PrivateRoute({ children, ...rest }) { 33 | return ( 34 | 37 | (sessionStorage.getItem('loggedin') != null && sessionStorage.getItem('loggedin')) ? ( 38 | children 39 | ) : ( 40 | 46 | ) 47 | } 48 | /> 49 | ); 50 | } 51 | 52 | return ( 53 |
54 | 55 | 56 | {/* 57 | Sign In control - Feel free to customize 58 | */} 59 | 60 | 61 | 62 | {/* 63 | Main Application 64 | -- Feel free to switch with your own Application 65 | */} 66 | 67 | 68 | 69 |
70 | ); 71 | } 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/LoginView.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import { 5 | Avatar, 6 | Button, 7 | Box, 8 | CssBaseline, 9 | Link, 10 | Container, 11 | Typography, 12 | TextField, 13 | CircularProgress 14 | } from '@material-ui/core'; 15 | 16 | import { useHistory, useLocation } from "react-router-dom"; 17 | import { config } from "./config" 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | paper: { 21 | marginTop: theme.spacing(8), 22 | display: 'flex', 23 | flexDirection: 'column', 24 | alignItems: 'center', 25 | }, 26 | wrapper: { 27 | margin: theme.spacing(1), 28 | position: 'relative', 29 | }, 30 | avatar: { 31 | margin: theme.spacing(1), 32 | backgroundColor: theme.palette.secondary.main, 33 | }, 34 | form: { 35 | width: '100%', // Fix IE 11 issue. 36 | marginTop: theme.spacing(1), 37 | }, 38 | submit: { 39 | margin: theme.spacing(3, 0, 2), 40 | }, 41 | fabProgress: { 42 | position: 'absolute', 43 | top: '40%', 44 | zIndex: 1, 45 | }, 46 | })); 47 | 48 | function Copyright() { 49 | return ( 50 | 51 | {'Copyright © '} 52 | 53 | Your Website 54 | {' '} 55 | {new Date().getFullYear()} 56 | {'.'} 57 | 58 | ); 59 | } 60 | 61 | export default function SignIn({ updateUserInfo }) { 62 | const classes = useStyles(); 63 | 64 | const [submitting, setSubmitting] = useState(false); 65 | const [errorMsg, setErrorMsg] = useState(""); 66 | const [loading, setLoading] = useState(true); 67 | const [csrfmiddlewaretoken, setCsrfmiddlewaretoken] = useState(""); 68 | 69 | let history = useHistory(); 70 | let location = useLocation(); 71 | 72 | useEffect(() => { 73 | var url = `${config.base_url}/auth/login`; 74 | (loading) && fetch(url, { 75 | method: 'GET', 76 | credentials: config.credentials, 77 | headers: { 78 | 'Accept': 'application/json', 79 | 'Content-Type': 'application/json' 80 | }, 81 | }) 82 | .then(response => { 83 | if (!response.ok) { 84 | return {}; 85 | } else { 86 | return response.json(); 87 | } 88 | }).then(res => { 89 | if ('csrfmiddlewaretoken' in res) { 90 | setCsrfmiddlewaretoken(res['csrfmiddlewaretoken']) 91 | } 92 | setLoading(false); 93 | }).catch((error) => { 94 | console.error('Something went wrong with connection!:', error); 95 | setLoading(false); 96 | }); 97 | }, [loading]); 98 | 99 | function handleSubmit(event) { 100 | event.preventDefault(); 101 | setSubmitting(true); 102 | setErrorMsg(""); 103 | 104 | const formData = new FormData(event.target); 105 | formData.append('csrfmiddlewaretoken', csrfmiddlewaretoken); 106 | 107 | var loginSuccess = false; 108 | var submitFailedTemp = false; 109 | var url = `${config.base_url}/auth/login`; 110 | fetch(url, { 111 | headers: { 112 | 'Accept': 'application/json' 113 | }, 114 | method: 'POST', 115 | credentials: config.credentials, 116 | body: formData 117 | }).then(response => { 118 | if (!response.ok) { 119 | submitFailedTemp = true; 120 | return {}; 121 | } 122 | return response.json(); 123 | }).then(data => { 124 | var errorMsg = ""; 125 | if (submitFailedTemp) { 126 | errorMsg = "Failed to authenticate, try again!"; 127 | } 128 | if ("success" in data) { 129 | if (!data["success"]) { 130 | errorMsg = data["error"]; 131 | } else { 132 | loginSuccess = true; 133 | updateUserInfo(data["user_detail"]); 134 | } 135 | } 136 | else { 137 | errorMsg = "Login attempt failed, try again!"; 138 | } 139 | setErrorMsg(errorMsg); 140 | setSubmitting(false); 141 | if (loginSuccess) { 142 | let { from } = location.state || { from: { pathname: "/" } }; 143 | history.push(from); 144 | } 145 | }); 146 | } 147 | 148 | return ( 149 |
150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | Sign in 158 | 159 |
160 | 172 | 184 | 185 | {errorMsg} 186 | 187 | 196 | 197 | 198 | {(loading || submitting) && } 199 |
200 | 201 | 202 | 203 | 204 |
205 |
206 | ); 207 | } -------------------------------------------------------------------------------- /frontend/src/ToDoView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { fade, makeStyles } from '@material-ui/core/styles'; 3 | import SearchIcon from '@material-ui/icons/Search'; 4 | import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew'; 5 | import DeleteIcon from '@material-ui/icons/Delete'; 6 | import { 7 | AppBar, 8 | Container, 9 | CssBaseline, 10 | InputBase, 11 | IconButton, 12 | Toolbar, 13 | Typography, 14 | Link, 15 | List, 16 | ListItem, 17 | TextField, 18 | Button, 19 | Checkbox, 20 | Grid, 21 | ListItemText, 22 | ListItemIcon, 23 | ListItemSecondaryAction 24 | } from '@material-ui/core'; 25 | import { useHistory } from "react-router-dom"; 26 | import { config } from './config'; 27 | 28 | const useStyles = makeStyles((theme) => ({ 29 | root: { 30 | display: 'flex', 31 | flexDirection: 'column', 32 | minHeight: '100vh', 33 | }, 34 | main: { 35 | marginTop: theme.spacing(8), 36 | marginBottom: theme.spacing(2), 37 | }, 38 | footer: { 39 | padding: theme.spacing(3, 2), 40 | marginTop: 'auto', 41 | backgroundColor: 42 | theme.palette.type === 'light' ? theme.palette.grey[200] : theme.palette.grey[800], 43 | }, 44 | menuButton: { 45 | marginRight: theme.spacing(2), 46 | }, 47 | title: { 48 | flexGrow: 1, 49 | display: 'none', 50 | [theme.breakpoints.up('sm')]: { 51 | display: 'block', 52 | }, 53 | }, 54 | search: { 55 | position: 'relative', 56 | borderRadius: theme.shape.borderRadius, 57 | backgroundColor: fade(theme.palette.common.white, 0.15), 58 | '&:hover': { 59 | backgroundColor: fade(theme.palette.common.white, 0.25), 60 | }, 61 | marginRight: theme.spacing(2), 62 | marginLeft: 0, 63 | width: '100%', 64 | [theme.breakpoints.up('sm')]: { 65 | marginLeft: theme.spacing(1), 66 | width: 'auto', 67 | }, 68 | }, 69 | searchIcon: { 70 | padding: theme.spacing(0, 2), 71 | height: '100%', 72 | position: 'absolute', 73 | pointerEvents: 'none', 74 | display: 'flex', 75 | alignItems: 'center', 76 | justifyContent: 'center', 77 | }, 78 | inputRoot: { 79 | color: 'inherit', 80 | }, 81 | inputInput: { 82 | padding: theme.spacing(1, 1, 1, 0), 83 | // vertical padding + font size from searchIcon 84 | paddingLeft: `calc(1em + ${theme.spacing(4)}px)`, 85 | transition: theme.transitions.create('width'), 86 | width: '100%', 87 | [theme.breakpoints.up('sm')]: { 88 | width: '12ch', 89 | '&:focus': { 90 | width: '20ch', 91 | }, 92 | }, 93 | }, 94 | })); 95 | 96 | function Copyright() { 97 | return ( 98 | 99 | {'Copyright © '} 100 | 101 | Your Website 102 | {' '} 103 | {new Date().getFullYear()} 104 | {'.'} 105 | 106 | ); 107 | } 108 | 109 | function ToDoView({ userdetails }) { 110 | const classes = useStyles(); 111 | const [todos, setTodos] = React.useState([]); 112 | const [loading, setLoading] = React.useState(true); 113 | 114 | let history = useHistory(); 115 | 116 | function signoff() { 117 | var url = `${config.base_url}/auth/logout`; 118 | fetch(url, { 119 | method: 'GET', 120 | credentials: config.credentials 121 | }) 122 | .then(response => { 123 | if (!response.ok) { 124 | return { 'success': false }; 125 | } else { 126 | return { 'success': true }; 127 | } 128 | }) 129 | .then(res => { 130 | if ('success' in res && res['success']) { 131 | sessionStorage.clear(); 132 | history.push('/'); 133 | } 134 | }) 135 | .catch((error) => { 136 | console.error('Something went wrong with connection!:', error); 137 | }); 138 | } 139 | 140 | const getData = React.useCallback(() => { 141 | var url = `${config.base_url}/api/todo`; 142 | (loading) && fetch(url, { 143 | method: 'GET', 144 | credentials: config.credentials 145 | }) 146 | .then(response => { 147 | const contentType = response.headers.get("content-type"); 148 | if (!response.ok) { 149 | return []; 150 | } else if (contentType && contentType.indexOf("application/json") !== -1) { 151 | return response.json(); 152 | } else { 153 | return []; 154 | } 155 | }) 156 | .then(res => { 157 | setTodos(res); 158 | setLoading(false); 159 | }) 160 | .catch((error) => { 161 | setLoading(false); 162 | console.error('Something went wrong with connection!:', error); 163 | }); 164 | }, [loading]); 165 | 166 | React.useEffect(getData, [loading]); 167 | 168 | function handleRemove(id) { 169 | 170 | } 171 | 172 | function handleCheck(id, checkStatus) { 173 | todos[id].is_complete = checkStatus; 174 | } 175 | 176 | function handleAdd(todo) { 177 | 178 | } 179 | 180 | return ( 181 |
182 | 183 | {/* Application Header Bar */} 184 | 185 | 186 | 187 | {`${userdetails.first_name}'s To-Do list`} 188 | 189 |
190 |
191 | 192 |
193 | 201 |
202 |
203 | 210 | 211 | 212 | 213 |
214 |
215 |
216 | 217 | {/* Main Body */} 218 | 219 | 220 | To-Do list 221 | 222 | 227 |
228 | 229 |
230 | 231 | {/* Sticky Footer */} 232 |
233 | 234 | 235 | 236 |
237 | 238 |
239 | ); 240 | 241 | } 242 | 243 | function TodoList({ todos, handleRemove, handleCheck }) { 244 | var todoNode = todos.map((todo, index) => { 245 | return ( 246 | 254 | ) 255 | }); 256 | 257 | return ( 258 | 259 | {todoNode} 260 | 261 | ) 262 | } 263 | 264 | const listElementStyles = { 265 | fontSize: 18, 266 | lineHeight: '24px', 267 | } 268 | 269 | const listElementCheckedStyles = { 270 | ...listElementStyles, 271 | textDecoration: 'line-through', 272 | } 273 | 274 | function Todo({ todo, id, checked, handleRemove, handleCheck }) { 275 | 276 | const [selected, setSelected] = React.useState(checked); 277 | const listStyles = !selected ? listElementStyles : listElementCheckedStyles; 278 | 279 | function onClick(event) { 280 | handleRemove(id) 281 | } 282 | 283 | function onCheck(event) { 284 | setSelected(event.target.checked); 285 | handleCheck(id, selected); 286 | } 287 | 288 | return ( 289 | 290 | 291 | 292 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ) 309 | } 310 | 311 | function AddTodo({ handleAdd }) { 312 | 313 | const [inputValue, setInputValue] = React.useState(""); 314 | 315 | function onClick(event) { 316 | event.preventDefault(); 317 | var todo = inputValue; 318 | if (todo === '') return 319 | else { 320 | var form = document.getElementById("myForm"); 321 | form.reset() 322 | handleAdd(todo); 323 | setInputValue(""); 324 | } 325 | } 326 | 327 | return ( 328 | 329 |
330 | 331 | 332 | setInputValue(e.target.value)} 335 | > 336 | 337 | 338 | 339 | 342 | 343 | 344 | 345 |
346 | 347 |
348 | ); 349 | 350 | } 351 | 352 | export default ToDoView; -------------------------------------------------------------------------------- /frontend/src/config.js: -------------------------------------------------------------------------------- 1 | const prod = { 2 | base_url: "", 3 | credentials: "same-origin" 4 | } 5 | const dev = { 6 | base_url: "http://localhost:8000", 7 | credentials: "include" 8 | }; 9 | 10 | export const config = process.env.NODE_ENV === "development" ? dev : prod; -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /main_view.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/main_view.PNG -------------------------------------------------------------------------------- /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', 'backend.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /react_container/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/react_container/__init__.py -------------------------------------------------------------------------------- /react_container/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /react_container/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReactContainerConfig(AppConfig): 5 | name = 'react_container' 6 | -------------------------------------------------------------------------------- /react_container/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/react_container/migrations/__init__.py -------------------------------------------------------------------------------- /react_container/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /react_container/storage.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import re 3 | 4 | from django.contrib.staticfiles.storage import ( 5 | ManifestStaticFilesStorage) 6 | from whitenoise.storage import ( 7 | CompressedStaticFilesMixin, HelpfulExceptionMixin) 8 | 9 | 10 | class SkipHashedFilesMixin: 11 | 12 | _already_hashed_pattern = re.compile(r'\.[0-9a-f]{8}\.') 13 | 14 | def is_already_hashed(self, path): 15 | """ 16 | Determine if a file is already hashed by webpack. 17 | 18 | The current implementation is quite lax. Adapt as needed. 19 | 20 | """ 21 | return self._already_hashed_pattern.search(path) 22 | 23 | def post_process(self, paths, dry_run=False, **options): 24 | """ 25 | Skip files already hashed by webpack. 26 | 27 | """ 28 | if dry_run: 29 | return 30 | 31 | unhashed_paths = collections.OrderedDict() 32 | for path, path_info in paths.items(): 33 | if self.is_already_hashed(path): 34 | yield path, None, False 35 | else: 36 | unhashed_paths[path] = path_info 37 | 38 | yield from super().post_process( 39 | unhashed_paths, dry_run=dry_run, **options) 40 | 41 | def stored_name(self, name): 42 | if self.is_already_hashed(name): 43 | return name 44 | else: 45 | return super().stored_name(name) 46 | 47 | 48 | class StaticFilesStorage( 49 | HelpfulExceptionMixin, CompressedStaticFilesMixin, 50 | SkipHashedFilesMixin, ManifestStaticFilesStorage): 51 | """ 52 | Similar to whitenoise.storage.CompressedManifestStaticFilesStorage. 53 | 54 | Add a mixin to avoid hashing files that are already hashed by webpack. 55 | 56 | """ -------------------------------------------------------------------------------- /react_container/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /react_container/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, re_path 2 | from django.contrib.auth import views as auth_views 3 | 4 | from . import views 5 | 6 | urlpatterns = [ 7 | path('auth/login', views.login), 8 | path('auth/logout', views.logout), 9 | re_path(r'', views.catchall), 10 | ] -------------------------------------------------------------------------------- /react_container/views.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from itertools import chain 4 | from django import http 5 | from django.conf import settings 6 | from django.template import engines 7 | from django.views.generic import TemplateView 8 | from django.utils.decorators import method_decorator 9 | from django.views.decorators.cache import never_cache 10 | from django.views.decorators.debug import sensitive_post_parameters 11 | from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie, csrf_protect 12 | from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout 13 | from django.middleware.csrf import get_token 14 | 15 | # Create your views here. 16 | 17 | @sensitive_post_parameters('password') 18 | @never_cache 19 | @ensure_csrf_cookie 20 | def login(request): 21 | if request.method == 'POST': 22 | username = request.POST.get('username', '').strip() 23 | password = request.POST.get('password', '').strip() 24 | if username and password: 25 | # Test username/password combination 26 | user = authenticate(username=username, password=password) 27 | # Found a match 28 | if user is not None: 29 | # User is active 30 | if user.is_active: 31 | # Officially log the user in 32 | auth_login(request, user) 33 | groups = user.groups.all() 34 | group_permissions = list(chain(*[group.permissions.all() for group in groups])) 35 | 36 | data = { 37 | 'success': True, 38 | 'user_detail' : { 39 | 'first_name' : user.first_name, 40 | 'last_name' : user.last_name, 41 | 'email' : user.email, 42 | 'groups' : [group.name for group in groups], 43 | 'permissions' : [permission.codename for permission in group_permissions] + [permission.codename for permission in user.user_permissions.all()] 44 | } 45 | } 46 | else: 47 | data = {'success': False, 'error': 'User is not active'} 48 | else: 49 | data = {'success': False, 'error': 'Wrong username and/or password'} 50 | 51 | return http.HttpResponse(json.dumps(data)) 52 | elif request.method == 'GET': 53 | return http.HttpResponse(json.dumps({'csrfmiddlewaretoken': get_token(request)})) 54 | # Request method is not POST or one of username or password is missing 55 | return http.HttpResponseBadRequest() 56 | 57 | @never_cache 58 | @ensure_csrf_cookie 59 | def logout(request): 60 | if request.method == 'GET': 61 | auth_logout(request); 62 | return http.HttpResponse() 63 | 64 | return http.HttpResponseBadRequest() 65 | 66 | @ensure_csrf_cookie 67 | @csrf_exempt 68 | def catchall_dev(request, upstream='http://localhost:3000'): 69 | """ 70 | Proxy HTTP requests to the frontend dev server in development. 71 | 72 | The implementation is very basic e.g. it doesn't handle HTTP headers. 73 | 74 | """ 75 | print(request) 76 | upstream_url = upstream + request.path 77 | method = request.META['REQUEST_METHOD'].lower() 78 | response = getattr(requests, method)(upstream_url, stream=True) 79 | content_type = response.headers.get('Content-Type') 80 | 81 | if request.META.get('HTTP_UPGRADE', '').lower() == 'websocket': 82 | return http.HttpResponse( 83 | content="WebSocket connections aren't supported", 84 | status=501, 85 | reason="Not Implemented" 86 | ) 87 | 88 | elif content_type == 'text/html; charset=UTF-8': 89 | return http.HttpResponse( 90 | content=engines['django'].from_string(response.text).render(), 91 | status=response.status_code, 92 | reason=response.reason, 93 | ) 94 | 95 | else: 96 | return http.StreamingHttpResponse( 97 | streaming_content=response.iter_content(2 ** 12), 98 | content_type=content_type, 99 | status=response.status_code, 100 | reason=response.reason, 101 | ) 102 | 103 | class CSRFTemplateView(TemplateView): 104 | @method_decorator(ensure_csrf_cookie) 105 | def get(self, request, *args, **kwargs): 106 | return super().get(request, *args, **kwargs) 107 | 108 | catchall_prod = CSRFTemplateView.as_view(template_name='index.html') 109 | 110 | catchall = catchall_dev if settings.DEBUG else catchall_prod -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.22.0 2 | Django==2.2.28 3 | whitenoise==4.1.4 4 | djangorestframework==3.11.2 5 | django-cors-headers==3.0.2 6 | waitress==2.1.2 7 | urllib3>=1.26.5 # not directly required, pinned by Snyk to avoid a vulnerability 8 | -------------------------------------------------------------------------------- /sign_in_view.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/sign_in_view.PNG -------------------------------------------------------------------------------- /static/empty.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parijat-K/django-react-boilerplate-template/1247f8ed42c58c240f9d4178009ea1753cb0b5cf/static/empty.file --------------------------------------------------------------------------------