Skip to content

mnogom/_empty

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Empty fullstack project

Stack: Django, Django REST framework (DRF), Vue.js 3, Axios, Vue Router

Junior cheat sheet for fullstack


Look around

  1. Prepare tools
  2. Create a project
  3. Backend
  4. Roadmap
  5. Resources

Prepare tools

pip3 install poetry --upgrade --user
pip3 install virtualenv --upgrade --user

npm install vue@next
npm install -g @vue/cli
vue upgrade --next

Create a project

1. Create project directory and README.md

mkdir empty_project
cd empty_project
% touch README.md

2. Create git

% git init
  • /.gitignore

    .idea
    .DS_Store

3. Create master Makefile

  • /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

Backend

1. Create dir for backend

% mkdir backend
% cd backend

2. Make .gitignore

backend % touch .gitignore
  • backend/.gitignore

    __pycache__
    .venv
    .env
    dist
    snippets
    .vscode
    .idea
    .DS_Store
    .github/.DS_Store
    .coverage
    coverage.xml
    *.pyc
    db.sqlite3
    
    

3. Init virtual environment

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/python

check env setup

backend % poetry env list --full-path

<..>/empty_project/backend/.venv (Activated)

4. Create Makefile

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

5. Add packages

backend % poetry add [email protected]
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.

6. Start Django project

backend % poetry run django-admin startproject backend
backend % tree -aL 2 -I .venv

.
├── .gitignore
├── Makefile
├── backend
│   ├── backend
│   └── manage.py
├── poetry.lock
└── pyproject.toml

7. Setup WSGI

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

8. Setup secret keys

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')
    <...>

9. Setup database sqlite

backend % make migrations
backend % make migrate

10. Create superuser

We don't need superuser here. You can skip this step.

backend % poetry run python backend/manage.py createsuperuser

11. Add apps to project

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 ..

12. Check structure

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

13. Add apps CORS, Django REST, api and vue_app to settings

  • 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',
    )

14. Total structure

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

15. Add some features to backend

  • 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)

18. Add demo page

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>

19. Check out

backend % make run
Hello, World!
{
    "1": 46,
    "2": 23,
    "3": 65,
    "4": 22,
    "5": 10
}

20. Return to main project directory

backend % cd ..

Frontend

1. Start Vue app

% vue create frontend
% cd frontend

2. Setup Makefile

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

3. Add axios and vue-router

frontend % npm install axios --save
frontend % npm install vue-router@next --save

4. Set up build settings

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",
    <...>

5. Favicon as static file [?]

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 in frontend/public/index.html

    <...>
        <link rel="icon" type="image/png" href="/static/favicon.png">
    <...>

6. Check structure

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

7. Try to run and build

  1. Run dev server
frontend % make run

Check out url http://127.0.0.1:8080.

  1. 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 %

8. Setup axios for development and production api urls

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')

9. Add functionality

This app will have:

  • One main app: App.vue
  • Three components:
    1. NavigationComponent.vue
    2. FooterComponent.vue
    3. RandomSequenceComponent.vue
  • Three views (for vue-router):
    1. HomeView.vue
    2. RandomSequenceView.vue
    3. 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...

9. Setup router

  • 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')

10. Check structure

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

Roadmap

Backend

Frontend

Dev part

Backend 2


Resources

  1. Poetry
  2. Django start guide
  3. Django REST Framework quickstart
  4. Django CORS
  5. Vue.js introduction
  6. About vue.config.js
  7. Vue.js routing
  8. Github example Vue.js routing
  9. Vue Router

Unsorted

New app with models

backend/backend % poetry run django-admin startapp memo_api

Cookie setup for frontend

frontend % npm install js-cookie --save
  1. https://www.django-rest-framework.org/api-guide/authentication/
  2. https://www.django-rest-framework.org/api-guide/permissions/
  3. https://stackoverflow.com/questions/35970970/django-rest-framework-permission-classes-of-viewset-method
  4. https://pythonru.com/uroki/django-rest-api
  5. https://auth0.com/blog/building-modern-applications-with-django-and-vuejs/
  6. https://vue-loader-v14.vuejs.org/ru/configurations/pre-processors.html