├── backend ├── .gitignore ├── db.sqlite3 ├── manage.py ├── todo │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_todo_done.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py └── todo_backend │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── Todo │ │ ├── Todo.css │ │ └── Todo.js │ └── TodoDetail │ │ ├── TodoDetail.css │ │ └── TodoDetail.js ├── containers │ └── TodoList │ │ ├── NewTodo │ │ ├── NewTodo.css │ │ └── NewTodo.js │ │ ├── TodoList.css │ │ └── TodoList.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js └── yarn.lock /backend/.gitignore: -------------------------------------------------------------------------------- 1 | ### Python ### 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # Environments 83 | .env 84 | .venv 85 | env/ 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | .spyproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | # mkdocs documentation 97 | /site 98 | 99 | # mypy 100 | .mypy_cache/ 101 | -------------------------------------------------------------------------------- /backend/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/backend/db.sqlite3 -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo_backend.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /backend/todo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/backend/todo/__init__.py -------------------------------------------------------------------------------- /backend/todo/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /backend/todo/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TodoConfig(AppConfig): 5 | name = 'todo' 6 | -------------------------------------------------------------------------------- /backend/todo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-08 06:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Todo', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=120)), 19 | ('content', models.TextField()), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/todo/migrations/0002_todo_done.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.4 on 2019-09-24 14:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('todo', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='todo', 15 | name='done', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/todo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/backend/todo/migrations/__init__.py -------------------------------------------------------------------------------- /backend/todo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Todo(models.Model): 4 | title = models.CharField(max_length=120) 5 | content = models.TextField() 6 | done = models.BooleanField(default=False) 7 | -------------------------------------------------------------------------------- /backend/todo/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/todo/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path('', views.index), 7 | path('/', views.index), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/todo/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest, JsonResponse 3 | from django.views.decorators.csrf import csrf_exempt 4 | 5 | import json 6 | 7 | from .models import Todo 8 | 9 | @csrf_exempt 10 | def index(request, id=None): 11 | if request.method == 'GET': 12 | if id is None: 13 | # get all 14 | todo_all_list = list(Todo.objects.all().values()) 15 | return JsonResponse(todo_all_list, safe=False) 16 | else: 17 | try: 18 | todo = Todo.objects.get(id=id) 19 | response_dict = { 20 | 'id': todo.id, 21 | 'title': todo.title, 22 | 'content': todo.content, 23 | 'done': todo.done, 24 | } 25 | return JsonResponse(response_dict, safe=False) 26 | except KeyError as e: 27 | return HttpResponseBadRequest('TodoID does not exist: {}'.format(id)) 28 | if request.method == 'POST': 29 | try: 30 | body = request.body.decode() 31 | title = json.loads(body)['title'] 32 | content = json.loads(body)['content'] 33 | except (KeyError, JSONDecodeError) as e: 34 | return HttpResponseBadRequest() 35 | todo = Todo(title=title, content=content, done=False) 36 | todo.save() 37 | response_dict = { 38 | 'id': todo.id, 39 | 'title': todo.title, 40 | 'content': todo.content, 41 | 'done': todo.done, 42 | } 43 | return HttpResponse(json.dumps(response_dict), status=201) 44 | elif request.method == 'DELETE': 45 | if id is None: 46 | return HttpResponseBadRequest('TodoID is not specified.') 47 | try: 48 | todo = Todo.objects.get(id=id) 49 | todo.delete() 50 | except KeyError as e: 51 | return HttpResponseBadRequest('TodoID does not exist: {}'.format(id)) 52 | return HttpResponse(status=204) 53 | elif request.method == 'PUT': 54 | if id is None: 55 | return HttpResponseBadRequest('TodoID is not specified.') 56 | try: 57 | todo = Todo.objects.get(id=id) 58 | todo.done = not todo.done 59 | todo.save() 60 | return HttpResponse(status=204) 61 | except KeyError as e: 62 | return HttpResponseBadRequest('TodoID does not exist: {}'.format(id)) 63 | else: 64 | return HttpResponseNotAllowed(['GET', 'POST', 'DELETE', 'PUT']) 65 | -------------------------------------------------------------------------------- /backend/todo_backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/backend/todo_backend/__init__.py -------------------------------------------------------------------------------- /backend/todo_backend/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for todo_backend project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'e-e=pwi4$d#vwneo=a0_&s=wa^7cot#xoeurja(&!ns@-kz4&0' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'todo.apps.TodoConfig', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'todo_backend.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'todo_backend.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'UTC' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | 123 | APPEND_SLASH=False 124 | -------------------------------------------------------------------------------- /backend/todo_backend/urls.py: -------------------------------------------------------------------------------- 1 | """todo_backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/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 include, path 18 | 19 | urlpatterns = [ 20 | path('api/todo/', include('todo.urls')), 21 | path('admin/', admin.site.urls), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/todo_backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for todo_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.2/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', 'todo_backend.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djvonny/swpp-redux-tutorial/78dc19987b86ec514881febf0189d96d31ec76ce/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | 4 | import TodoList from './containers/TodoList/TodoList'; 5 | import TodoDetail from './components/TodoDetail/TodoDetail'; 6 | import NewTodo from './containers/TodoList/NewTodo/NewTodo'; 7 | 8 | import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom'; 9 | 10 | function App() { 11 | return ( 12 | 13 |
14 | 15 | } /> 16 | 17 | 18 | 19 |

Not Found

} /> 20 |
21 |
22 |
23 | ); 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/Todo/Todo.css: -------------------------------------------------------------------------------- 1 | .Todo { 2 | border-top: 1px solid #f1f3f5; 3 | padding: 1rem; 4 | display: flex; 5 | align-items: center; 6 | transition: all 0.15s; 7 | } 8 | 9 | .Todo .text { 10 | flex: 1; 11 | text-align: left; 12 | word-break: break-all; 13 | cursor: pointer; 14 | } 15 | 16 | .Todo .text:hover { 17 | color: orange; 18 | } 19 | 20 | .Todo .done { 21 | text-decoration: line-through; 22 | color: #adb5bd; 23 | } 24 | 25 | .Todo .done-mark { 26 | font-size: 1.5rem; 27 | line-height: 1rem; 28 | margin-left: 1rem; 29 | color: orange; 30 | font-weight: 800; 31 | } -------------------------------------------------------------------------------- /src/components/Todo/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './Todo.css'; 4 | 5 | const Todo = (props) => { 6 | return ( 7 |
8 |
11 | {props.title} 12 |
13 | {props.done &&
} 14 |
15 | ); 16 | }; 17 | 18 | export default Todo; -------------------------------------------------------------------------------- /src/components/TodoDetail/TodoDetail.css: -------------------------------------------------------------------------------- 1 | .TodoDetail .row { 2 | display: flex; 3 | padding: 20px; 4 | height: 30px; 5 | text-align: left; 6 | } 7 | 8 | .TodoDetail .left { 9 | font-weight: bold; 10 | flex: 25% 11 | } 12 | 13 | .TodoDetail .right { 14 | flex: 75%; 15 | } -------------------------------------------------------------------------------- /src/components/TodoDetail/TodoDetail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './TodoDetail.css'; 4 | 5 | class TodoDetail extends React.Component { 6 | render() { 7 | if (this.props.match) { 8 | console.log(this.props.match.params.id); 9 | } 10 | return ( 11 | < div className="TodoDetail" > 12 |
13 |
14 | Name: 15 |
16 |
17 | {this.props.title} 18 |
19 |
20 | 21 |
22 |
23 | Content: 24 |
25 |
26 | {this.props.content} 27 |
28 |
29 | 30 | ); 31 | } 32 | }; 33 | 34 | export default TodoDetail; -------------------------------------------------------------------------------- /src/containers/TodoList/NewTodo/NewTodo.css: -------------------------------------------------------------------------------- 1 | .NewTodo { 2 | width: 80%; 3 | margin: 20px auto; 4 | border: 1px solid #eee; 5 | box-shadow: 0 2px 3px #ccc; 6 | text-align: center; 7 | } 8 | 9 | .NewTodo label { 10 | display: block; 11 | margin: 10px auto; 12 | text-align: center; 13 | font-weight: bold; 14 | } 15 | 16 | .NewTodo input, 17 | .NewTodo textarea { 18 | display: block; 19 | width: 80%; 20 | box-sizing: border-box; 21 | border: 1px solid black; 22 | outline: none; 23 | font: inherit; 24 | margin: auto; 25 | } 26 | 27 | .NewTodo button { 28 | margin: 5px 0; 29 | padding: 10px; 30 | font: inherit; 31 | border: 1px solid #fa923f; 32 | background-color: transparent; 33 | color: #fa923f; 34 | cursor: pointer; 35 | } 36 | 37 | .NewTodo button:hover, 38 | .NewTodo button:active { 39 | color: white; 40 | background-color: #fa923f; 41 | } -------------------------------------------------------------------------------- /src/containers/TodoList/NewTodo/NewTodo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { Redirect } from 'react-router-dom'; 4 | 5 | import './NewTodo.css'; 6 | 7 | class NewTodo extends Component { 8 | state = { 9 | title: '', 10 | content: '', 11 | submitted: false, 12 | } 13 | 14 | postTodoHandler = () => { 15 | const data = { title: this.state.title, content: this.state.content }; 16 | alert('Submitted\n' + data.title + '\n' + data.content); 17 | this.setState({ submitted: true }); 18 | } 19 | 20 | render() { 21 | let redirect = null; 22 | if (this.state.submitted) { 23 | redirect = 24 | } 25 | return ( 26 |
27 | {redirect} 28 |

Add a Todo

29 | 30 | this.setState({ title: event.target.value })} /> 32 | 33 |