├── backend ├── cars │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── urls.py │ ├── serializers.py │ ├── models.py │ └── views.py ├── README.md ├── authentication │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── views.py │ ├── models.py │ └── serializers.py ├── drf_jwt_backend │ ├── __init__.py │ ├── asgi.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── Pipfile ├── manage.py ├── .gitignore ├── DRF_JWT_Backend.postman_collection.json └── Pipfile.lock ├── frontend ├── src │ ├── pages │ │ ├── LoginPage │ │ │ ├── LoginPage.css │ │ │ └── LoginPage.js │ │ ├── HomePage │ │ │ └── HomePage.js │ │ └── RegisterPage │ │ │ └── RegisterPage.js │ ├── components │ │ ├── Footer │ │ │ ├── Footer.css │ │ │ └── Footer.jsx │ │ └── NavBar │ │ │ ├── NavBar.css │ │ │ └── NavBar.jsx │ ├── hooks │ │ ├── useAuth.js │ │ └── useCustomForm.js │ ├── utils │ │ └── PrivateRoute.js │ ├── index.js │ ├── App.js │ ├── App.css │ └── context │ │ └── AuthContext.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── package.json ├── .gitignore └── README.md ├── README.md └── .gitignore /backend/cars/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # drf_jwt_backend -------------------------------------------------------------------------------- /backend/authentication/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/cars/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/drf_jwt_backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/authentication/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/pages/LoginPage/LoginPage.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /backend/cars/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/authentication/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Topten1004/Django-React-Jwt/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Topten1004/Django-React-Jwt/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Topten1004/Django-React-Jwt/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /backend/cars/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Car 3 | 4 | # Register your models here. 5 | admin.site.register(Car) 6 | -------------------------------------------------------------------------------- /backend/cars/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CarsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'cars' 7 | -------------------------------------------------------------------------------- /backend/authentication/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AuthenticationConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'authentication' 7 | -------------------------------------------------------------------------------- /frontend/src/components/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | footer { 2 | position: fixed; 3 | bottom: 0; 4 | font-size: 1.2rem; 5 | left: 0; 6 | padding: 1rem; 7 | text-align: center; 8 | width: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import "./Footer.css"; 2 | 3 | const Footer = () => { 4 | return ( 5 | 8 | ); 9 | }; 10 | 11 | export default Footer; 12 | -------------------------------------------------------------------------------- /backend/cars/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from cars import views 3 | 4 | # <<<<<<<<<<<<<<<<< EXAMPLE FOR STARTER CODE USE <<<<<<<<<<<<<<<<< 5 | 6 | urlpatterns = [ 7 | path('', views.user_cars), 8 | path('all/', views.get_all_cars), 9 | ] 10 | -------------------------------------------------------------------------------- /frontend/src/hooks/useAuth.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import AuthContext from "../context/AuthContext"; 3 | 4 | const useAuth = () => { 5 | const { user, token } = useContext(AuthContext); 6 | return [user, token]; 7 | }; 8 | 9 | export default useAuth; 10 | -------------------------------------------------------------------------------- /backend/authentication/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from .models import User 4 | 5 | class CustomUserAdmin(UserAdmin): 6 | pass 7 | 8 | # Register your models here. 9 | admin.site.register(User, CustomUserAdmin) 10 | -------------------------------------------------------------------------------- /frontend/src/utils/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import { Navigate } from "react-router-dom"; 2 | import useAuth from "../hooks/useAuth"; 3 | 4 | const PrivateRoute = ({ children }) => { 5 | const [user] = useAuth(); 6 | return user ? children : ; 7 | }; 8 | 9 | export default PrivateRoute; 10 | -------------------------------------------------------------------------------- /backend/cars/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Car 3 | 4 | # <<<<<<<<<<<<<<<<< EXAMPLE FOR STARTER CODE USE <<<<<<<<<<<<<<<<< 5 | 6 | 7 | class CarSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = Car 10 | fields = ['id', 'make', 'model', 'year', 'user_id'] 11 | depth = 1 12 | -------------------------------------------------------------------------------- /backend/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = "*" 8 | djangorestframework = "*" 9 | djangorestframework-simplejwt = "*" 10 | mysql-connector-python = "==8.0.26" 11 | django-cors-headers = "*" 12 | tzdata = "*" 13 | 14 | [dev-packages] 15 | autopep8 = "*" 16 | 17 | [requires] 18 | python_version = "3.10" 19 | -------------------------------------------------------------------------------- /backend/authentication/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework_simplejwt.views import TokenRefreshView 3 | from .views import RegisterView, MyTokenObtainPairView 4 | 5 | urlpatterns = [ 6 | path('login/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), 7 | path('login/refresh/', TokenRefreshView.as_view(), name='token_refresh'), 8 | path('register/', RegisterView.as_view(), name='register'), 9 | ] 10 | -------------------------------------------------------------------------------- /backend/cars/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from authentication.models import User 3 | 4 | # Create your models here. 5 | 6 | # <<<<<<<<<<<<<<<<< EXAMPLE FOR STARTER CODE USE <<<<<<<<<<<<<<<<< 7 | 8 | 9 | class Car(models.Model): 10 | user = models.ForeignKey(User, on_delete=models.CASCADE) 11 | make = models.CharField(max_length=30) 12 | model = models.CharField(max_length=100) 13 | year = models.IntegerField() 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { AuthProvider } from "./context/AuthContext"; 5 | import { BrowserRouter as Router } from "react-router-dom"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById("root") 16 | ); 17 | -------------------------------------------------------------------------------- /backend/drf_jwt_backend/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for drf_jwt_backend project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 15 | 'drf_jwt_backend.settings') 16 | 17 | application = get_asgi_application() 18 | -------------------------------------------------------------------------------- /backend/drf_jwt_backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for drf_jwt_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/3.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', 15 | 'drf_jwt_backend.settings') 16 | 17 | application = get_wsgi_application() 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /backend/authentication/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from .serializers import MyTokenObtainPairSerializer, RegistrationSerializer 3 | from rest_framework import generics 4 | from rest_framework.permissions import AllowAny 5 | from rest_framework_simplejwt.views import TokenObtainPairView 6 | User = get_user_model() 7 | 8 | 9 | class MyTokenObtainPairView(TokenObtainPairView): 10 | serializer_class = MyTokenObtainPairSerializer 11 | 12 | 13 | class RegisterView(generics.CreateAPIView): 14 | queryset = User.objects.all() 15 | permission_classes = (AllowAny,) 16 | serializer_class = RegistrationSerializer 17 | -------------------------------------------------------------------------------- /frontend/src/components/NavBar/NavBar.css: -------------------------------------------------------------------------------- 1 | ul { 2 | display: flex; 3 | justify-content: space-between; 4 | list-style-type: none; 5 | } 6 | 7 | li { 8 | font-size: 1.25rem; 9 | } 10 | 11 | .navBar { 12 | background: steelblue; 13 | box-shadow: 4px 2px 5px gray; 14 | color: white; 15 | margin-bottom: 2rem; 16 | padding: 1.5rem 2rem; 17 | } 18 | 19 | .navBar button { 20 | background: transparent; 21 | border: 2px solid white; 22 | color: white; 23 | padding: 0.4rem 0.5rem; 24 | } 25 | 26 | .navBar button:hover { 27 | background: white; 28 | color: steelblue; 29 | cursor: pointer; 30 | } 31 | 32 | .brand { 33 | cursor: pointer; 34 | font-size: 2rem; 35 | } 36 | -------------------------------------------------------------------------------- /backend/authentication/models.py: -------------------------------------------------------------------------------- 1 | # from django.db import models 2 | from django.contrib.auth.models import AbstractUser 3 | 4 | 5 | class User(AbstractUser): 6 | pass 7 | ''' 8 | This is a custom version of the built in User class 9 | It contains all of the built in fields and functionality of the standard User 10 | You can add fields here for any additional properties you want a User to have 11 | This is useful for adding roles (Customer and Employee, for example) 12 | For just a few roles, adding boolean fields is advised 13 | ''' 14 | # Example (note import of models above that is commented out) 15 | # this will add a column to the user table 16 | # is_student = models.BooleanField('student status', default=False) 17 | -------------------------------------------------------------------------------- /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 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 10 | 'drf_jwt_backend.settings') 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /frontend/src/hooks/useCustomForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const useCustomForm = (initialValues = {}, onSubmit) => { 4 | const [formData, setFormValues] = useState(initialValues); 5 | 6 | const handleInputChange = (e) => { 7 | e.persist(); 8 | if (e.target.name === "isStudent") { 9 | setFormValues({ ...formData, [e.target.name]: e.target.checked }); 10 | } else { 11 | setFormValues({ ...formData, [e.target.name]: e.target.value }); 12 | } 13 | }; 14 | 15 | const handleSubmit = (e) => { 16 | e.preventDefault(); 17 | onSubmit(formData); 18 | }; 19 | 20 | const reset = () => { 21 | setFormValues(initialValues); 22 | }; 23 | 24 | return [formData, handleInputChange, handleSubmit, reset]; 25 | }; 26 | 27 | export default useCustomForm; 28 | -------------------------------------------------------------------------------- /frontend/src/components/NavBar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useContext } from "react"; 3 | import { useNavigate, Link } from "react-router-dom"; 4 | import AuthContext from "../../context/AuthContext"; 5 | import "./NavBar.css"; 6 | 7 | const Navbar = () => { 8 | const { logoutUser, user } = useContext(AuthContext); 9 | const navigate = useNavigate(); 10 | return ( 11 |
12 | 26 |
27 | ); 28 | }; 29 | 30 | export default Navbar; 31 | -------------------------------------------------------------------------------- /backend/drf_jwt_backend/urls.py: -------------------------------------------------------------------------------- 1 | """drf_jwt_backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.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 path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('api/auth/', include('authentication.urls')), 22 | path('api/cars/', include('cars.urls')), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/cars/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-20 14:58 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='Car', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('make', models.CharField(max_length=30)), 22 | ('model', models.CharField(max_length=100)), 23 | ('year', models.IntegerField()), 24 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | // General Imports 2 | import { Routes, Route } from "react-router-dom"; 3 | import "./App.css"; 4 | 5 | // Pages Imports 6 | import HomePage from "./pages/HomePage/HomePage"; 7 | import LoginPage from "./pages/LoginPage/LoginPage"; 8 | import RegisterPage from "./pages/RegisterPage/RegisterPage"; 9 | 10 | // Component Imports 11 | import Navbar from "./components/NavBar/NavBar"; 12 | import Footer from "./components/Footer/Footer"; 13 | 14 | // Util Imports 15 | import PrivateRoute from "./utils/PrivateRoute"; 16 | 17 | function App() { 18 | return ( 19 |
20 | 21 | 22 | 26 | 27 | 28 | } 29 | /> 30 | } /> 31 | } /> 32 | 33 |
34 |
35 | ); 36 | } 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^0.26.0", 10 | "jwt-decode": "^3.1.2", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-router-dom": "^6.2.1", 14 | "react-scripts": "5.0.0", 15 | "web-vitals": "^2.1.4" 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": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400&display=swap"); 2 | 3 | *, 4 | *::before, 5 | *::after { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: inherit; 9 | } 10 | 11 | html { 12 | box-sizing: border-box; 13 | font-family: "Poppins", sans-serif; 14 | } 15 | 16 | p { 17 | font-size: 20px; 18 | } 19 | 20 | button { 21 | background-color: transparent; 22 | border: 2px solid steelblue; 23 | color: steelblue; 24 | font-size: 1rem; 25 | font-family: inherit; 26 | max-width: 100px; 27 | padding: 0.5rem 0.5rem; 28 | } 29 | 30 | button:hover { 31 | cursor: pointer; 32 | color: white; 33 | background-color: steelblue; 34 | } 35 | 36 | .container { 37 | margin: 0 auto; 38 | max-width: 600px; 39 | overflow: auto; 40 | text-align: center; 41 | } 42 | 43 | .form { 44 | align-items: center; 45 | display: flex; 46 | flex-direction: column; 47 | justify-content: flex-end; 48 | gap: 1rem; 49 | } 50 | 51 | .form input { 52 | display: block; 53 | width: 100%; 54 | height: 25px; 55 | margin: 5px; 56 | padding: 3px 7px; 57 | font-size: 17px; 58 | } 59 | -------------------------------------------------------------------------------- /backend/cars/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.response import Response 3 | from rest_framework.permissions import IsAuthenticated, AllowAny 4 | from rest_framework.decorators import api_view, permission_classes 5 | from .models import Car 6 | from .serializers import CarSerializer 7 | 8 | # <<<<<<<<<<<<<<<<< EXAMPLE FOR STARTER CODE USE <<<<<<<<<<<<<<<<< 9 | 10 | 11 | @api_view(['GET']) 12 | @permission_classes([AllowAny]) 13 | def get_all_cars(request): 14 | cars = Car.objects.all() 15 | serializer = CarSerializer(cars, many=True) 16 | return Response(serializer.data) 17 | 18 | 19 | @api_view(['GET', 'POST']) 20 | @permission_classes([IsAuthenticated]) 21 | def user_cars(request): 22 | print( 23 | 'User ', f"{request.user.id} {request.user.email} {request.user.username}") 24 | if request.method == 'POST': 25 | serializer = CarSerializer(data=request.data) 26 | if serializer.is_valid(): 27 | serializer.save(user=request.user) 28 | return Response(serializer.data, status=status.HTTP_201_CREATED) 29 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 30 | elif request.method == 'GET': 31 | cars = Car.objects.filter(user_id=request.user.id) 32 | serializer = CarSerializer(cars, many=True) 33 | return Response(serializer.data) 34 | -------------------------------------------------------------------------------- /frontend/src/pages/HomePage/HomePage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useEffect, useState } from "react"; 3 | import useAuth from "../../hooks/useAuth"; 4 | 5 | import axios from "axios"; 6 | 7 | const HomePage = () => { 8 | // The "user" value from this Hook contains the decoded logged in user information (username, first name, id) 9 | // The "token" value is the JWT token that you will send in the header of any request requiring authentication 10 | //TODO: Add an AddCars Page to add a car for a logged in user's garage 11 | const [user, token] = useAuth(); 12 | const [cars, setCars] = useState([]); 13 | 14 | useEffect(() => { 15 | const fetchCars = async () => { 16 | try { 17 | let response = await axios.get("http://127.0.0.1:8000/api/cars/", { 18 | headers: { 19 | Authorization: "Bearer " + token, 20 | }, 21 | }); 22 | setCars(response.data); 23 | } catch (error) { 24 | console.log(error.response.data); 25 | } 26 | }; 27 | fetchCars(); 28 | }, [token]); 29 | return ( 30 |
31 |

Home Page for {user.username}!

32 | {cars && 33 | cars.map((car) => ( 34 |

35 | {car.year} {car.model} {car.make} 36 |

37 | ))} 38 |
39 | ); 40 | }; 41 | 42 | export default HomePage; 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactDjango_JWT_StarterCode 2 | 3 | Starter code for full stack React + Django applications using JWT for authentication/authorization and a fully working register/login system on the React side 4 | 5 | ## NOTE 6 | 7 | "cars" app in Django backend for example purposes only. Study it closely and review provided resources to understand how to properly create protected endpoints that require a JWT token for authorization. 8 | 9 | ## For implementing user roles 10 | 11 | - see comments in the following files in the order they are listed 12 | - backend/authentication/models.py 13 | - backend/authentication/serializers.py (note that there are several places needing modification in that file) 14 | - If modifying the User class in authentication/models.py, make sure to drop your existing database, 15 | create it, and run migrations from scratch 16 | - for a great reference, see the following article: https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html 17 | - note that this article is from 2018 and dealing with a full stack Django application scenario with HTML/CSS templates. The principles of setting up the backend portion for User roles is still valid! 18 | - once user roles are set up on your backend, you can now utilize them on the frontend. Recommend reviewing the React Router slideshow for ideas on how to use descendant routes and conditional rendering to control who can access what parts of your application based on a role! 19 | -------------------------------------------------------------------------------- /frontend/src/pages/LoginPage/LoginPage.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from "react"; 2 | import AuthContext from "../../context/AuthContext"; 3 | import useCustomForm from "../../hooks/useCustomForm"; 4 | import { Link } from "react-router-dom"; 5 | import "./LoginPage.css"; 6 | 7 | const LoginPage = () => { 8 | const { loginUser, isServerError } = useContext(AuthContext); 9 | const defaultValues = { username: "", password: "" }; 10 | const [formData, handleInputChange, handleSubmit, reset] = useCustomForm( 11 | defaultValues, 12 | loginUser 13 | ); 14 | 15 | useEffect(() => { 16 | if (isServerError) { 17 | reset(); 18 | } 19 | }, [isServerError]); 20 | 21 | return ( 22 |
23 |
24 | 33 | 42 | {isServerError ? ( 43 |

Login failed, incorrect credentials!

44 | ) : null} 45 | Click to register! 46 | 47 |
48 |
49 | ); 50 | }; 51 | 52 | export default LoginPage; 53 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /backend/authentication/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from rest_framework.validators import UniqueValidator 3 | from rest_framework_simplejwt.serializers import TokenObtainPairSerializer 4 | from django.contrib.auth.password_validation import validate_password 5 | from .models import User 6 | 7 | 8 | class MyTokenObtainPairSerializer(TokenObtainPairSerializer): 9 | @classmethod 10 | def get_token(cls, user): 11 | token = super().get_token(user) 12 | # for any additional fields you'd like to add to the JWT sent back in response 13 | # add below using the token["field name"] = user.name_of_property 14 | # token["is_student"] = user.is_student 15 | 16 | token["username"] = user.username 17 | token["first_name"] = user.first_name 18 | 19 | return token 20 | 21 | 22 | class RegistrationSerializer(serializers.ModelSerializer): 23 | email = serializers.EmailField(required=True, validators=[ 24 | UniqueValidator(queryset=User.objects.all())]) 25 | 26 | password = serializers.CharField( 27 | write_only=True, required=True, validators=[validate_password]) 28 | 29 | class Meta: 30 | model = User 31 | # If added new columns through the User model, add them in the fields 32 | # list as seen below 33 | fields = ('username', 'password', 'email', 34 | 'first_name', 'last_name',) 35 | 36 | def create(self, validated_data): 37 | 38 | user = User.objects.create( 39 | username=validated_data['username'], 40 | email=validated_data['email'], 41 | first_name=validated_data['first_name'], 42 | last_name=validated_data['last_name'], 43 | 44 | # If added new columns through the User model, add them in this 45 | # create method. Example below: 46 | 47 | # is_student=validated_data['is_student'] 48 | ) 49 | user.set_password(validated_data['password']) 50 | user.save() 51 | 52 | return user 53 | -------------------------------------------------------------------------------- /frontend/src/pages/RegisterPage/RegisterPage.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import AuthContext from "../../context/AuthContext"; 3 | import useCustomForm from "../../hooks/useCustomForm"; 4 | 5 | const RegisterPage = () => { 6 | const { registerUser } = useContext(AuthContext); 7 | const defaultValues = { 8 | username: "", 9 | email: "", 10 | password: "", 11 | firstName: "", 12 | lastName: "", 13 | }; 14 | const [formData, handleInputChange, handleSubmit] = useCustomForm( 15 | defaultValues, 16 | registerUser 17 | ); 18 | 19 | return ( 20 |
21 |
22 | 31 | 40 | 49 | 58 | 67 |

68 | NOTE: Make this an uncommon password with characters, numbers, and 69 | special characters! 70 |

71 | 72 |
73 |
74 | ); 75 | }; 76 | 77 | export default RegisterPage; 78 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 94 | __pypackages__/ 95 | 96 | # Celery stuff 97 | celerybeat-schedule 98 | celerybeat.pid 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # Local Settings 131 | drf_jwt_backend/local_settings.py 132 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* -------------------------------------------------------------------------------- /frontend/src/context/AuthContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import axios from "axios"; 4 | import jwtDecode from "jwt-decode"; 5 | 6 | const AuthContext = createContext(); 7 | 8 | export default AuthContext; 9 | 10 | function setUserObject(user) { 11 | if (!user) { 12 | return null; 13 | } 14 | return { 15 | username: user.username, 16 | id: user.user_id, 17 | first_name: user.first_name, 18 | }; 19 | } 20 | 21 | export const AuthProvider = ({ children }) => { 22 | const BASE_URL = "http://127.0.0.1:8000/api/auth"; 23 | const userToken = JSON.parse(localStorage.getItem("token")); 24 | const decodedUser = userToken ? jwtDecode(userToken) : null; 25 | const [token, setToken] = useState(userToken); 26 | const [user, setUser] = useState(setUserObject(decodedUser)); 27 | const [isServerError, setIsServerError] = useState(false); 28 | const navigate = useNavigate(); 29 | 30 | const registerUser = async (registerData) => { 31 | try { 32 | let finalData = { 33 | username: registerData.username, 34 | password: registerData.password, 35 | email: registerData.email, 36 | first_name: registerData.firstName, 37 | last_name: registerData.lastName, 38 | }; 39 | let response = await axios.post(`${BASE_URL}/register/`, finalData); 40 | if (response.status === 201) { 41 | console.log("Successful registration! Log in to access token"); 42 | setIsServerError(false); 43 | navigate("/login"); 44 | } else { 45 | navigate("/register"); 46 | } 47 | } catch (error) { 48 | console.log(error.response.data); 49 | } 50 | }; 51 | 52 | const loginUser = async (loginData) => { 53 | try { 54 | let response = await axios.post(`${BASE_URL}/login/`, loginData); 55 | if (response.status === 200) { 56 | localStorage.setItem("token", JSON.stringify(response.data.access)); 57 | setToken(JSON.parse(localStorage.getItem("token"))); 58 | let loggedInUser = jwtDecode(response.data.access); 59 | setUser(setUserObject(loggedInUser)); 60 | setIsServerError(false); 61 | navigate("/"); 62 | } else { 63 | navigate("/register"); 64 | } 65 | } catch (error) { 66 | console.log(error.response.data); 67 | setIsServerError(true); 68 | navigate("/register"); 69 | } 70 | }; 71 | 72 | const logoutUser = () => { 73 | if (user) { 74 | localStorage.removeItem("token"); 75 | setUser(null); 76 | setToken(null); 77 | navigate("/"); 78 | } 79 | }; 80 | 81 | const contextData = { 82 | user, 83 | token, 84 | loginUser, 85 | logoutUser, 86 | registerUser, 87 | isServerError, 88 | }; 89 | 90 | return ( 91 | {children} 92 | ); 93 | }; 94 | -------------------------------------------------------------------------------- /backend/authentication/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-20 14:58 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0012_alter_user_first_name_max_length'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='User', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 25 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 26 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 32 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), 33 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), 34 | ], 35 | options={ 36 | 'verbose_name': 'user', 37 | 'verbose_name_plural': 'users', 38 | 'abstract': False, 39 | }, 40 | managers=[ 41 | ('objects', django.contrib.auth.models.UserManager()), 42 | ], 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /backend/drf_jwt_backend/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for drf_jwt_backend project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from datetime import timedelta 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | 23 | # SECURITY WARNING: don't run with debug turned on in production! 24 | DEBUG = True 25 | 26 | ALLOWED_HOSTS = [] 27 | 28 | AUTH_USER_MODEL = 'authentication.User' 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'authentication.apps.AuthenticationConfig', 42 | 'corsheaders', 43 | 'cars.apps.CarsConfig' # This app is for example use only 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | 'corsheaders.middleware.CorsMiddleware', 55 | ] 56 | 57 | CORS_ORIGIN_ALLOW_ALL = True 58 | 59 | ROOT_URLCONF = 'drf_jwt_backend.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'drf_jwt_backend.wsgi.application' 78 | 79 | 80 | # Password validation 81 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 82 | 83 | AUTH_PASSWORD_VALIDATORS = [ 84 | { 85 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 86 | }, 87 | { 88 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 89 | }, 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 95 | }, 96 | ] 97 | 98 | 99 | # Internationalization 100 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 101 | 102 | LANGUAGE_CODE = 'en-us' 103 | 104 | TIME_ZONE = 'UTC' 105 | 106 | USE_I18N = True 107 | 108 | USE_L10N = True 109 | 110 | USE_TZ = True 111 | 112 | 113 | # Static files (CSS, JavaScript, Images) 114 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 115 | 116 | STATIC_URL = '/static/' 117 | 118 | # Default primary key field type 119 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 120 | 121 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 122 | 123 | REST_FRAMEWORK = { 124 | 'DEFAULT_AUTHENTICATION_CLASSES': ('rest_framework_simplejwt.authentication.JWTAuthentication',) 125 | } 126 | 127 | SIMPLE_JWT = { 128 | 'ACCESS_TOKEN_LIFETIME': timedelta(days=10), 129 | 'REFRESH_TOKEN_LIFETIME': timedelta(days=20), 130 | 'ROTATE_REFRESH_TOKENS': False, 131 | 'BLACKLIST_AFTER_ROTATION': True, 132 | 133 | 'ALGORITHM': 'HS256', 134 | 'SIGNING_KEY': 'devCodeCamp', 135 | 'VERIFYING_KEY': None, 136 | 'AUDIENCE': None, 137 | 'ISSUER': None, 138 | 139 | 'AUTH_HEADER_TYPES': ('Bearer',), 140 | 'USER_ID_FIELD': 'id', 141 | 'USER_ID_CLAIM': 'user_id', 142 | 143 | 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), 144 | 'TOKEN_TYPE_CLAIM': 'token_type', 145 | 146 | 'JTI_CLAIM': 'jti', 147 | 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', 148 | 149 | 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', 150 | 'SLIDING_TOKEN_LIFETIME': timedelta(days=10), 151 | 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=20), 152 | } 153 | 154 | try: 155 | from drf_jwt_backend.local_settings import * 156 | except ImportError: 157 | pass 158 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 94 | __pypackages__/ 95 | 96 | # Celery stuff 97 | celerybeat-schedule 98 | celerybeat.pid 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | 130 | # Local Settings 131 | drf_jwt_backend/local_settings.py 132 | 133 | # Logs 134 | logs 135 | *.log 136 | npm-debug.log* 137 | yarn-debug.log* 138 | yarn-error.log* 139 | lerna-debug.log* 140 | .pnpm-debug.log* 141 | 142 | # Diagnostic reports (https://nodejs.org/api/report.html) 143 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 144 | 145 | # Runtime data 146 | pids 147 | *.pid 148 | *.seed 149 | *.pid.lock 150 | 151 | # Directory for instrumented libs generated by jscoverage/JSCover 152 | lib-cov 153 | 154 | # Coverage directory used by tools like istanbul 155 | coverage 156 | *.lcov 157 | 158 | # nyc test coverage 159 | .nyc_output 160 | 161 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 162 | .grunt 163 | 164 | # Bower dependency directory (https://bower.io/) 165 | bower_components 166 | 167 | # node-waf configuration 168 | .lock-wscript 169 | 170 | # Compiled binary addons (https://nodejs.org/api/addons.html) 171 | build/Release 172 | 173 | # Dependency directories 174 | node_modules/ 175 | jspm_packages/ 176 | 177 | # Snowpack dependency directory (https://snowpack.dev/) 178 | web_modules/ 179 | 180 | # TypeScript cache 181 | *.tsbuildinfo 182 | 183 | # Optional npm cache directory 184 | .npm 185 | 186 | # Optional eslint cache 187 | .eslintcache 188 | 189 | # Optional stylelint cache 190 | .stylelintcache 191 | 192 | # Microbundle cache 193 | .rpt2_cache/ 194 | .rts2_cache_cjs/ 195 | .rts2_cache_es/ 196 | .rts2_cache_umd/ 197 | 198 | # Optional REPL history 199 | .node_repl_history 200 | 201 | # Output of 'npm pack' 202 | *.tgz 203 | 204 | # Yarn Integrity file 205 | .yarn-integrity 206 | 207 | # dotenv environment variable files 208 | .env 209 | .env.development.local 210 | .env.test.local 211 | .env.production.local 212 | .env.local 213 | 214 | # parcel-bundler cache (https://parceljs.org/) 215 | .cache 216 | .parcel-cache 217 | 218 | # Next.js build output 219 | .next 220 | out 221 | 222 | # Nuxt.js build / generate output 223 | .nuxt 224 | dist 225 | 226 | # Gatsby files 227 | .cache/ 228 | # Comment in the public line in if your project uses Gatsby and not Next.js 229 | # https://nextjs.org/blog/next-9-1#public-directory-support 230 | # public 231 | 232 | # vuepress build output 233 | .vuepress/dist 234 | 235 | # vuepress v2.x temp and cache directory 236 | .temp 237 | .cache 238 | 239 | # Docusaurus cache and generated files 240 | .docusaurus 241 | 242 | # Serverless directories 243 | .serverless/ 244 | 245 | # FuseBox cache 246 | .fusebox/ 247 | 248 | # DynamoDB Local files 249 | .dynamodb/ 250 | 251 | # TernJS port file 252 | .tern-port 253 | 254 | # Stores VSCode versions used for testing VSCode extensions 255 | .vscode-test 256 | 257 | # yarn v2 258 | .yarn/cache 259 | .yarn/unplugged 260 | .yarn/build-state.yml 261 | .yarn/install-state.gz 262 | .pnp.* 263 | -------------------------------------------------------------------------------- /backend/DRF_JWT_Backend.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "d4409d57-7119-4339-bfae-1f3a18bc6341", 4 | "name": "DRF_JWT_Backend", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Authentication", 10 | "item": [ 11 | { 12 | "name": "Register_User", 13 | "request": { 14 | "method": "POST", 15 | "header": [], 16 | "body": { 17 | "mode": "raw", 18 | "raw": "{\r\n \"username\": \"dantheman\",\r\n \"password\": \"password1@\",\r\n \"email\": \"dan@devcodecamp.com\",\r\n \"first_name\": \"Daniel\",\r\n \"last_name\": \"Tulpa\"\r\n}", 19 | "options": { 20 | "raw": { 21 | "language": "json" 22 | } 23 | } 24 | }, 25 | "url": { 26 | "raw": "http://127.0.0.1:8000/api/auth/register/", 27 | "protocol": "http", 28 | "host": [ 29 | "127", 30 | "0", 31 | "0", 32 | "1" 33 | ], 34 | "port": "8000", 35 | "path": [ 36 | "api", 37 | "auth", 38 | "register", 39 | "" 40 | ] 41 | } 42 | }, 43 | "response": [] 44 | }, 45 | { 46 | "name": "Login_User", 47 | "request": { 48 | "method": "POST", 49 | "header": [], 50 | "body": { 51 | "mode": "raw", 52 | "raw": "{\r\n \"username\": \"dantheman\",\r\n \"password\": \"password1@\"\r\n}", 53 | "options": { 54 | "raw": { 55 | "language": "json" 56 | } 57 | } 58 | }, 59 | "url": { 60 | "raw": "http://127.0.0.1:8000/api/auth/login/", 61 | "protocol": "http", 62 | "host": [ 63 | "127", 64 | "0", 65 | "0", 66 | "1" 67 | ], 68 | "port": "8000", 69 | "path": [ 70 | "api", 71 | "auth", 72 | "login", 73 | "" 74 | ] 75 | } 76 | }, 77 | "response": [] 78 | }, 79 | { 80 | "name": "Refresh_Access", 81 | "request": { 82 | "method": "POST", 83 | "header": [], 84 | "body": { 85 | "mode": "raw", 86 | "raw": "{\r\n \"refresh\": \"PASTE REFRESH TOKEN FROM LOGIN HERE\"\r\n}", 87 | "options": { 88 | "raw": { 89 | "language": "json" 90 | } 91 | } 92 | }, 93 | "url": { 94 | "raw": "http://127.0.0.1:8000/api/auth/login/refresh/", 95 | "protocol": "http", 96 | "host": [ 97 | "127", 98 | "0", 99 | "0", 100 | "1" 101 | ], 102 | "port": "8000", 103 | "path": [ 104 | "api", 105 | "auth", 106 | "login", 107 | "refresh", 108 | "" 109 | ] 110 | } 111 | }, 112 | "response": [] 113 | } 114 | ] 115 | }, 116 | { 117 | "name": "Cars", 118 | "item": [ 119 | { 120 | "name": "GET all cars (unprotected)", 121 | "request": { 122 | "method": "GET", 123 | "header": [], 124 | "url": { 125 | "raw": "http://127.0.0.1:8000/api/cars/all/", 126 | "protocol": "http", 127 | "host": [ 128 | "127", 129 | "0", 130 | "0", 131 | "1" 132 | ], 133 | "port": "8000", 134 | "path": [ 135 | "api", 136 | "cars", 137 | "all", 138 | "" 139 | ] 140 | } 141 | }, 142 | "response": [] 143 | }, 144 | { 145 | "name": "GET cars for user (protected)", 146 | "protocolProfileBehavior": { 147 | "disableBodyPruning": true 148 | }, 149 | "request": { 150 | "method": "GET", 151 | "header": [ 152 | { 153 | "key": "Authorization", 154 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjQ4MTU2ODM5LCJpYXQiOjE2NDcyOTI4MzksImp0aSI6ImJjYzJhZTUzOTRlYzQ3OThiNjI0YTcwYTYzYjcxOThmIiwidXNlcl9pZCI6MSwidXNlcm5hbWUiOiJqanZlZ2EiLCJmaXJzdF9uYW1lIjoiIn0.l9ATW5CpWF2TGniqVgU1I-a8LkuqgLQ4ok3tJh6asAA", 155 | "type": "text" 156 | } 157 | ], 158 | "body": { 159 | "mode": "raw", 160 | "raw": "\n", 161 | "options": { 162 | "raw": { 163 | "language": "json" 164 | } 165 | } 166 | }, 167 | "url": { 168 | "raw": "http://127.0.0.1:8000/api/cars/", 169 | "protocol": "http", 170 | "host": [ 171 | "127", 172 | "0", 173 | "0", 174 | "1" 175 | ], 176 | "port": "8000", 177 | "path": [ 178 | "api", 179 | "cars", 180 | "" 181 | ] 182 | } 183 | }, 184 | "response": [] 185 | }, 186 | { 187 | "name": "POST new car for user (protected)", 188 | "request": { 189 | "method": "POST", 190 | "header": [ 191 | { 192 | "key": "Authorization", 193 | "value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjQ4MTU3MjczLCJpYXQiOjE2NDcyOTMyNzMsImp0aSI6IjIyMDQ4YTg2NzEzMTQyMmZhZjJmNDNmYmI2MTExZDg2IiwidXNlcl9pZCI6MiwidXNlcm5hbWUiOiJkYW50aGVtYW4iLCJmaXJzdF9uYW1lIjoiRGFuaWVsIn0.DeTe47E7-9eCr7xKE22m3CvFMDj0Sxye5A6ndwJ024M", 194 | "type": "text" 195 | } 196 | ], 197 | "body": { 198 | "mode": "raw", 199 | "raw": "{\n \"make\": \"Ford\",\n \"model\": \"F150\",\n \"year\": 1999\n}", 200 | "options": { 201 | "raw": { 202 | "language": "json" 203 | } 204 | } 205 | }, 206 | "url": { 207 | "raw": "http://127.0.0.1:8000/api/cars/", 208 | "protocol": "http", 209 | "host": [ 210 | "127", 211 | "0", 212 | "0", 213 | "1" 214 | ], 215 | "port": "8000", 216 | "path": [ 217 | "api", 218 | "cars", 219 | "" 220 | ] 221 | } 222 | }, 223 | "response": [] 224 | } 225 | ] 226 | } 227 | ] 228 | } -------------------------------------------------------------------------------- /backend/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "ba3683a69b92797881258adfdbb476b136ae4d1a7a5c09aa0f67ca4398c3f52b" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "asgiref": { 20 | "hashes": [ 21 | "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4", 22 | "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==3.5.2" 26 | }, 27 | "django": { 28 | "hashes": [ 29 | "sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687", 30 | "sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5" 31 | ], 32 | "index": "pypi", 33 | "version": "==4.0.4" 34 | }, 35 | "django-cors-headers": { 36 | "hashes": [ 37 | "sha256:39d1d5acb872c1860ecfd88b8572bfbb3a1f201b5685ede951d71fc57c7dfae5", 38 | "sha256:5f07e2ff8a95c887698e748588a4a0b2ad0ad1b5a292e2d33132f1253e2a97cb" 39 | ], 40 | "index": "pypi", 41 | "version": "==3.12.0" 42 | }, 43 | "djangorestframework": { 44 | "hashes": [ 45 | "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", 46 | "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" 47 | ], 48 | "index": "pypi", 49 | "version": "==3.13.1" 50 | }, 51 | "djangorestframework-simplejwt": { 52 | "hashes": [ 53 | "sha256:a60b09afb27d91ad1d7ac904cc632bd52cecead8f389f0fa1532ceb0fb757a74", 54 | "sha256:bcc4cb74dcb637ca1e17eed35276bd618ab19491f8c53e65dee6271177c355e8" 55 | ], 56 | "index": "pypi", 57 | "version": "==5.2.0" 58 | }, 59 | "mysql-connector-python": { 60 | "hashes": [ 61 | "sha256:136630a0a7c52e84851fb8f622dee03b1b7e797b7f5639f3f4965825cdcd6567", 62 | "sha256:1d2be3287689aff582ec58e4b4f053a2fd097a73f3330138330408595824e5e1", 63 | "sha256:3da7a33da3ee28f0175dcac8084900e0e97ab28e2711129711d651acd8eb4241", 64 | "sha256:3f85fa406b74517daad49c584f068478941963d5cccc4a7a140d577b17f393fb", 65 | "sha256:4de9aab6fddc67ac105e576bf0b5a507795a13ce3bba6959f5c401885119c080", 66 | "sha256:547b16542fa7eae657c48bae0b126a5757cda5d1e8356139f4514f1cc6efbde3", 67 | "sha256:62e939c88a1be022b25023e5a4ba21472bfe563d2c3487c3286605d6ecc45214", 68 | "sha256:69c4071f3b397e862b7f4c8d7ad0c88417687fbaf308167f9f3e63bcf0f24f25", 69 | "sha256:7e053c818dff6dc42c1f40861918a377b2b60aa3987fed7e40587d2467881844", 70 | "sha256:84008628cfe6aca25eb2d7cef1c255b017dc32ad8b4a78e29089c0f2cd627820", 71 | "sha256:a139ecea8fa49df0aa9df71d4bf9c30ae250034b8f1580c7f9ba96d531920a1f", 72 | "sha256:b8659919b4d6973764de7bdcb8d2d9563fb5d7d5ed6386bc6fce494421455145", 73 | "sha256:c10e4f47f1ab04a17f6ab50e39dbf99c81c6213b89533b901ccca57c3e78ade6", 74 | "sha256:d822d0bfe171a7d4aec735f37e8a10dec13338f93ec3f879c90ef4c687c7c699", 75 | "sha256:eaaa4a41e8508bc0eee5c39f5bb4454cc6d091a332535e988d09746ab4428f3e", 76 | "sha256:f5fde784da884fe4f6f4734c0c356b6bf8f1a55fe770bc3ee0ded3d111f6123a", 77 | "sha256:fdbb4d0ecd326952eff557d6e597a1350a4f40c206f669dd2f8f487518ef7d0f" 78 | ], 79 | "index": "pypi", 80 | "version": "==8.0.26" 81 | }, 82 | "protobuf": { 83 | "hashes": [ 84 | "sha256:0d4719e724472e296062ba8e82a36d64693fcfdb550d9dff98af70ca79eafe3d", 85 | "sha256:2b35602cb65d53c168c104469e714bf68670335044c38eee3c899d6a8af03ffc", 86 | "sha256:32fff501b6df3050936d1839b80ea5899bf34db24792d223d7640611f67de15a", 87 | "sha256:34400fd76f85bdae9a2e9c1444ea4699c0280962423eff4418765deceebd81b5", 88 | "sha256:3767c64593a49c7ac0accd08ed39ce42744405f0989d468f0097a17496fdbe84", 89 | "sha256:3f2ed842e8ca43b790cb4a101bcf577226e0ded98a6a6ba2d5e12095a08dc4da", 90 | "sha256:52c1e44e25f2949be7ffa7c66acbfea940b0945dd416920231f7cb30ea5ac6db", 91 | "sha256:5d9b5c8270461706973c3871c6fbdd236b51dfe9dab652f1fb6a36aa88287e47", 92 | "sha256:72d357cc4d834cc85bd957e8b8e1f4b64c2eac9ca1a942efeb8eb2e723fca852", 93 | "sha256:79cd8d0a269b714f6b32641f86928c718e8d234466919b3f552bfb069dbb159b", 94 | "sha256:a4c0c6f2f95a559e59a0258d3e4b186f340cbdc5adec5ce1bc06d01972527c88", 95 | "sha256:b309fda192850ac4184ca1777aab9655564bc8d10a9cc98f10e1c8bf11295c22", 96 | "sha256:b3d7d4b4945fe3c001403b6c24442901a5e58c0a3059290f5a63523ed4435f82", 97 | "sha256:c8829092c5aeb61619161269b2f8a2e36fd7cb26abbd9282d3bc453f02769146" 98 | ], 99 | "markers": "python_version >= '3.7'", 100 | "version": "==4.21.1" 101 | }, 102 | "pyjwt": { 103 | "hashes": [ 104 | "sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf", 105 | "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba" 106 | ], 107 | "markers": "python_version >= '3.6'", 108 | "version": "==2.4.0" 109 | }, 110 | "pytz": { 111 | "hashes": [ 112 | "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", 113 | "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" 114 | ], 115 | "version": "==2022.1" 116 | }, 117 | "sqlparse": { 118 | "hashes": [ 119 | "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", 120 | "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" 121 | ], 122 | "markers": "python_version >= '3.5'", 123 | "version": "==0.4.2" 124 | }, 125 | "tzdata": { 126 | "hashes": [ 127 | "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", 128 | "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" 129 | ], 130 | "index": "pypi", 131 | "version": "==2022.1" 132 | } 133 | }, 134 | "develop": { 135 | "autopep8": { 136 | "hashes": [ 137 | "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979", 138 | "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f" 139 | ], 140 | "index": "pypi", 141 | "version": "==1.6.0" 142 | }, 143 | "pycodestyle": { 144 | "hashes": [ 145 | "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", 146 | "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" 147 | ], 148 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 149 | "version": "==2.8.0" 150 | }, 151 | "toml": { 152 | "hashes": [ 153 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 154 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 155 | ], 156 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 157 | "version": "==0.10.2" 158 | } 159 | } 160 | } 161 | --------------------------------------------------------------------------------