Stack: Django, Django REST framework (DRF), Vue.js 3, Axios, Vue Router
Junior cheat sheet for fullstack
pip3 install poetry --upgrade --user
pip3 install virtualenv --upgrade --user
npm install vue@next
npm install -g @vue/cli
vue upgrade --next
mkdir empty_project
cd empty_project
% touch README.md
% git init
-
/.gitignore
.idea .DS_Store
-
/Makefile
make install: cd backend ; \ make install ; \ mv .env_example .env ; \ make migrations ; \ make migrate ; \ make load-demo-data ; \ cd ../frontend ; \ make install make run: cd frontend ; \ make build ; \ cd ../backend ; \ make run
% mkdir backend
% cd backend
backend % touch .gitignore
-
backend/.gitignore
__pycache__ .venv .env dist snippets .vscode .idea .DS_Store .github/.DS_Store .coverage coverage.xml *.pyc db.sqlite3
backend % poetry init
backend % tree -aL 1
.
├── .gitignore
├── .venv
├── poetry.lock
└── pyproject.toml
if there is no
backend/.venv
:backend % virtualenv .venv backend % poetry env use .venv/bin/pythoncheck env setup
backend % poetry env list --full-path <..>/empty_project/backend/.venv (Activated)
backend % touch Makefile
-
backend/Makefile
install: poetry install run: poetry run python backend/manage.py runserver gunicorn-run: source .venv/bin/activate ; \ cd backend ; \ gunicorn backend.wsgi django-shell: cd backend ; \ poetry run python manage.py shell migrations: poetry run python backend/manage.py makemigrations migrate: poetry run python backend/manage.py migrate lint: poetry run flake8 backend load-demo-data: poetry run python backend/manage.py loaddata backend/memo_api/fixtures/01_sections.yaml ; \ poetry run python backend/manage.py loaddata backend/memo_api/fixtures/02_notes.yaml
backend % poetry add django@2.2.10
backend % poetry add djangorestframework
backend % poetry add django-cors-headers
backend % poetry add environs
backend % poetry add pyyaml
backend % poetry add flake8 --dev
backend % poetry add gunicorn --dev
backend % poetry show
django 2.2.10 A high-level Python Web framework that encourage...
django-cors-headers 3.7.0 django-cors-headers is a Django application for ...
djangorestframework 3.12.4 Web APIs for Django, made easy.
environs 9.3.2 simplified environment variable parsing
flake8 3.9.2 the modular source code checker: pep8 pyflakes a...
gunicorn 20.1.0 WSGI HTTP Server for UNIX
marshmallow 3.12.2 A lightweight library for converting complex dat...
mccabe 0.6.1 McCabe checker, plugin for flake8
pycodestyle 2.7.0 Python style guide checker
pyflakes 2.3.1 passive checker of Python programs
python-dotenv 0.18.0 Read key-value pairs from a .env file and set th...
pytz 2021.1 World timezone definitions, modern and historical
pyyaml 5.4.1 YAML parser and emitter for Python
sqlparse 0.4.1 A non-validating SQL parser.
backend % poetry run django-admin startproject backend
backend % tree -aL 2 -I .venv
.
├── .gitignore
├── Makefile
├── backend
│ ├── backend
│ └── manage.py
├── poetry.lock
└── pyproject.toml
backend % source .venv/bin/activate
backend % export DJANGO_SETTINGS_MODULE=backend.settings
backend % deactivate
Try to run using Django [?]
backend % make run
Try to run using Gunicorn
backend % make gunicorn-run
Create .env
backend % touch .env
-
backend/.env
SECRET_KEY='strong key'
Also I created file .env_example
. If you clone this repo - rename .env_example
to .env
. You don't need to create it.
Update key in settings
-
backend/backend/settings.py
<...> from environs import Env # Setup environment env = Env() env.read_env(override=True) <...> SECRET_KEY = env.str('SECRET_KEY') <...>
backend % make migrations
backend % make migrate
We don't need superuser here. You can skip this step.
backend % poetry run python backend/manage.py createsuperuser
This app will return Vue app.
backend % cd backend
backend/backend % poetry run django-admin startapp vue_app
backend/backend % touch vue_app/urls.py
backend/backend % mkdir vue_app/templates
backend/backend % mkdir vue_app/static
backend/backend % cd ..
This app will work as random API.
backend % cd backend
backend/backend % poetry run django-admin startapp random_api
backend/backend % touch random_api/urls.py
backend/backend % cd ..
backend % tree -aL 2 -I .venv
.
├── .env
├── .env_example
├── .gitignore
├── Makefile
├── backend
│ ├── backend
│ ├── random_api
│ ├── db.sqlite3
│ ├── manage.py
│ └── vue_app
├── poetry.lock
└── pyproject.toml
backend % tree backend
backend
├── backend
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── random_api
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── db.sqlite3
├── manage.py
└── vue_app
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── static
├── templates
├── tests.py
├── urls.py
└── views.py
-
backend/backend/settings.py
<...> # Application definition INSTALLED_APPS = [ <...>, 'corsheaders', 'rest_framework', 'random_api', 'vue_app', ] <...> MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', <...> ] <...> # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = '/static/' <...> # CORS setup # https://pypi.org/project/django-cors-headers/ CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = ( 'http://localhost:8080', )
backend % tree -aL 3
.
├── .env
├── .env_example
├── .gitignore
├── .venv
│ └── <..>
├── Makefile
├── backend
│ ├── backend
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── random_api
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── migrations
│ │ ├── models.py
│ │ ├── tests.py
│ │ ├── urls.py
│ │ └── views.py
│ ├── db.sqlite3
│ ├── manage.py
│ └── vue_app
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── static
│ ├── templates
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── poetry.lock
└── pyproject.toml
-
backend/backend/urls.py
<...> from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/random/', include('random_api.urls')), path('', include('vue_app.urls')), ]
-
backend/vue_app/urls.py
from django.urls import re_path from .views import MainView urlpatterns = [ # path("", MainView.as_view()), re_path(r'^.*$', MainView.as_view()), # TODO: using this url make mistakes ]
-
backend/vue_app/views.py
from django.shortcuts import render from django.views import View class MainView(View): def get(self, request, *args, **kwargs): return render(request, 'index.html')
-
backend/random_api/urls.py
from django.urls import path, re_path from .views import RandomSequenceView urlpatterns = [ path('sequence/', RandomSequenceView.as_view()), ]
-
backend/random_api/views.py
import random from rest_framework.views import APIView from rest_framework.response import Response class RandomSequenceView(APIView): """Random sequence View.""" def get(self, request, *args, **kwargs): """This is test function. Returns random sequence. Max length - 10.""" count = int(request.GET.get('count', 10)) count = 10 if count > 10 else count random_range = {i: random.randint(0, 100) for i in range(1, count + 1)} return Response(random_range)
After frontend is set up we will overwrite this page.
-
backend/vue_app/templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello, World!</title> </head> <body> Hello, World! </body> </html>
backend % make run
- check address http://127.0.0.1:8000. There you will see page with title and text
Hello, World!
- check address http://127.0.0.1:8000/api/random/sequence/?count=5. You will see the answer:
{
"1": 46,
"2": 23,
"3": 65,
"4": 22,
"5": 10
}
backend % cd ..
% vue create frontend
% cd frontend
frontend % rm README.md # I don't need it
frontend % touch Makefile
-
frontend/Makefile
install: npm install run: npm run serve build: rm -rf ../backend/backend/vue_app/static/* ; \ rm -rf ../backend/backend/vue_app/templates/* ; \ npm run build ; \ cp public/static/* ../backend/backend/vue_app/static lint: npm run lint
frontend % npm install axios --save
frontend % npm install vue-router@next --save
frontend % touch vue.config.js
Set up dest
directory
-
frontend/vue.config.js
module.exports = { outputDir : '../backend/backend/vue_app', assetsDir : 'static', indexPath : 'templates/index.html', }
We don't need to remove dest
directory, so we need to add tag --no-clean
to build
command
-
frontend/package.json
<...> "build": "vue-cli-service build --no-clean", <...>
Remove public/favicon.ico
frontend % rm public/favicon.ico
Make static
directory in frontend/public
.
Place image favicon.png
to frontend/public/static
.
-
frontend/public/static/favicon.png
-
Edit
src
infrontend/public/index.html
<...> <link rel="icon" type="image/png" href="/static/favicon.png"> <...>
frontend % tree -I node_modules
.
├── Makefile
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── index.html
│ └── static
│ └── favicon.png
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
└── vue.config.js
- Run dev server
frontend % make run
Check out url http://127.0.0.1:8080.
- Build Vue app for
production
and run it via Django server [?]
frontend % make build
frontend % cd ../backend
backend % make run
Check out url http://127.0.0.1:8000.
Go back to frontend.
backend % cd ../frontend
frontend %
If you run Vue app with make run
, your base url will be http://127.0.0.1:8000
. If you
build Vue app with make build
, your base url will be /
.
-
frontend/src/main.js
import { createApp } from 'vue' import App from './App.vue' import axios from "axios" // Setup development and production base urls for axios let dev_mode = process.env.NODE_ENV === 'development' axios.defaults.baseURL = dev_mode ? 'http://127.0.0.1:8000' : '/' createApp(App).mount('#app')
This app will have:
- One main app:
App.vue
- Three components:
NavigationComponent.vue
FooterComponent.vue
RandomSequenceComponent.vue
- Three views (for
vue-router
):HomeView.vue
RandomSequenceView.vue
PageNotFoundView.vue
App.vue
├─ NavigationComponent.vue
├─ router-view (by 'vue-router')
│ ├─ HomeView.vue (on path '/')
│ ├─ RandomSequenceView.vue (on path '/rasq/')
│ │ └─ RandomSequenceComponent.vue
│ └─ PageNotFoundView.vue (on any other path)
└─ FooterComponent.vue
Start with components
.
-
frontend/src/components/FooterComponent.vue
<template> <div id="footer-component"> <span>Created with</span> <img alt="Vue logo" src="../assets/logo.png" height="20"> </div> </template> <script> export default { name: "FooterComponent", } </script> <style scoped> #footer-component { position: absolute; bottom: 1rem; right: 1rem; font-size: small; } </style>
-
frontend/src/components/NavigationComponent.vue
<template> <div id="navigation-component"> <router-link to="/">home</router-link> <router-link to="/rasq/">rasq</router-link> <router-link to="/new/not/developed/feature">nefe</router-link> </div> </template> <script> export default { name: "NavigationComponent", } </script> <style scoped> #navigation-component { margin: 1rem 0; } a { padding: 0 1rem; } </style>
-
frontend/src/components/RandomSequenceComponent.vue
<template> <div id="random-sequence-component"> <div> {{ msg }} with <input v-on:input="edit_count_of_elements($event)" value="10" type="number"/> numbers </div> <li v-for="(value, key) in elements" v-bind:key="key"> {{ value }} </li> </div> </template> <script> import axios from 'axios' export default { name: 'RandomSequenceComponent', props: { msg: String }, data: () => ({ elements_count: 10, elements: [], }), mounted() { this.get_random_sequence() }, methods: { edit_count_of_elements: function (event) { let event_value = Number(event.target.value) if (event_value > 10) { event.target.value = 10 event_value = 10 } else if (event_value < 0) { event.target.value = 0 event_value = 0 } if (event_value !== this.elements_count) { this.elements_count = event_value this.get_random_sequence() } }, get_random_sequence: function () { axios({ method: 'get', url: "api/random/sequence/", params: { count: this.elements_count } }).then(response => { this.elements = response.data }) } } } </script> <style scoped> input { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; font-size: medium; border: none; width: 2em; } input:focus { outline: none; } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } li { display: inline-block; padding: 0.5em; margin: 10px 10px; } li:hover { background-color: #2c3e50; color: white; } </style>
Then views
.
-
frontend/src/views/HomeView.vue
<template> <div id="home-view"> <span>Home page</span> </div> </template> <script> export default { name: "HomeView.vue", } </script>
-
frontend/src/views/PageNotFoundView.vue
<template> <div id="not-found-view"> <span>404 | Page not found</span> </div> </template> <script> export default { name: "NotFoundView", } </script> <style scoped> span { font-size: x-large; } </style>
-
frontend/src/views/RandomSequenceView.vue
<template> <div id="random-sequence-view"> <RandomSequenceComponent msg="Generate random sequence"/> </div> </template> <script> import RandomSequenceComponent from '@/components/RandomSequenceComponent.vue' export default { name: 'RandomSequenceView', components: { RandomSequenceComponent, } } </script>
Now App.vue
.
-
frontend/src/App.vue
<template> <div id="home-app"> <NavigationComponent/> <router-view/> <FooterComponent/> </div> </template> <script> import NavigationComponent from "./components/NavigationComponent.vue" import FooterComponent from "./components/FooterComponent.vue" export default { name: "App.vue", components: { NavigationComponent, FooterComponent, } } </script> <style scoped> #home-app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } </style>
Don't try to run...
-
frontend/src/router/index.js
import { createWebHistory, createRouter } from 'vue-router' const routes = [ { path: '/', name: 'Home', component: () => import("@/views/HomeView.vue"), }, { path: '/rasq/', name: 'RandomSequenceGenerator', component: () => import("@/views/RandomSequenceView.vue"), }, { path: '/:catchAll(.*)', name: "Page not found", component: () => import("@/views/PageNotFoundView.vue"), }, ] const router = createRouter({ history: createWebHistory(), routes, }) export default router
-
frontend/src/main.js
import { createApp } from 'vue' import axios from "axios" import router from './router' import App from "./App.vue" // Setup dev and prod base urls for axios let dev_mode = process.env.NODE_ENV === 'development' axios.defaults.baseURL = dev_mode ? 'http://127.0.0.1:8000' : '/' // Setup url routing createApp(App).use(router).mount('#app')
frontend % tree -I node_modules
.
├── Makefile
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── index.html
│ └── static
│ └── favicon.png
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ ├── FooterComponent.vue
│ │ ├── NavigationComponent.vue
│ │ └── RandomSequenceComponent.vue
│ ├── main.js
│ ├── router
│ │ └── index.js
│ └── views
│ ├── HomeView.vue
│ ├── PageNotFoundView.vue
│ └── RandomSequenceView.vue
└── vue.config.js
Backend
- Django
- Django ORM @ describe
- Django REST Framework
- DRF Serializer @ describe
- Authentication *check source
- Tests
Frontend
- Vue.js 3
- axios
-
Setup axios for post requests (csrf-token)¯\_(ツ)_/¯ - Vue Router
- Vuex
- Tests
Dev part
- PostgreSQL
- CI/CD
- Docker
- nginx
- CI/CD/CD
Backend 2
- Django / Django ORM / DRF -> Fast API / SQLAlchemy / pydantic
- Poetry
- Django start guide
- Django REST Framework quickstart
- Django CORS
- Vue.js introduction
- About vue.config.js
Vue.js routingGithub example Vue.js routing- Vue Router
backend/backend % poetry run django-admin startapp memo_api
frontend % npm install js-cookie --save
- https://www.django-rest-framework.org/api-guide/authentication/
- https://www.django-rest-framework.org/api-guide/permissions/
- https://stackoverflow.com/questions/35970970/django-rest-framework-permission-classes-of-viewset-method
- https://pythonru.com/uroki/django-rest-api
- https://auth0.com/blog/building-modern-applications-with-django-and-vuejs/
- https://vue-loader-v14.vuejs.org/ru/configurations/pre-processors.html