├── 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 |
6 | Copyright © 2022
{" "}
7 |
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 |
13 |
14 |
15 | React/Django JWT
16 |
17 |
18 |
19 | {user ? (
20 | Logout
21 | ) : (
22 | navigate("/login")}>Login
23 | )}
24 |
25 |
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 |
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 | You need to enable JavaScript to run this app.
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 |
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 |
--------------------------------------------------------------------------------