📜 ⬆️ ⬇️

How to quickly write a website or web application and not get bogged down in collectors

This small tutorial describes how to create a reactive web application using server-side rendering (Server-Side Rendering, SSR). The client part is a full-fledged Vue application, in my case using the MVVM template. The server application runs on the Flask microfree framework, which can provide endpoints (endpoint) and render the finished HTML page. HTML pages (located in the myapp / templates subdirectory) are rendered with the Jinja template engine (set as a Flask dependency).

Warning: quickly does not mean that the article is intended for beginners.

Used technologies and frameworks:


For the API, use the JSON-RPC protocol www.jsonrpc.org/specification . The protocol is simple, readable, and without extra crutches, it works both on the server and on the client side.

Training


Installing the necessary packages

pip install flask flask-jsonrpc 

Create a project directory and prepare the structure inside. The recommended structure of the application can be found here https://habr.com/ru/en/post/421887/

 mkdir -p myapp/{myapp/{static/{js,css},ns_api,templates},config,data} cd myapp 

Download the necessary files JS and CSS frameworks

 wget -O myapp/static/js/jquery-3.3.1.slim.min.js https://code.jquery.com/jquery-3.3.1.slim.min.js wget -O myapp/static/js/popper.min.js https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js wget -O myapp/static/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js wget -O myapp/static/css/bootstrap.min.css https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css wget -O myapp/static/js/vue.min.js https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js wget -O myapp/static/js/axios.min.js https://unpkg.com/axios/dist/axios.min.js 

There is a jquery dependency here, but only for Bootstrap

Minimum Flask application


Run.py file for manual start and testing

 #!/usr/bin/env python3 from myapp import app as application application.run(host='0.0.0.0', port=8000) 

File config / default.py to configure the application

 import os import sys # Конфигурация DEBUG = True SQLDEBUG = False SESSION_COOKIE_NAME = 'myapp' SESSION_TYPE = 'filesystem' TITLE = 'Проект' DIR_BASE = '/'.join(os.path.dirname(os.path.abspath(__file__)).split('/')[:-1]) DIR_DATA = DIR_BASE + '/data' # Генерировать можно утилитой pwgen # Пример: # pwgen -sy 64 SECRET_KEY = '''0123456789''' # Логирование LOG_FILE = DIR_DATA + '/myapp.log' LONG_LOG_FORMAT = '%(asctime)s - [%(name)s.%(levelname)s] [%(threadName)s, %(module)s.%(funcName)s@%(lineno)d] %(message)s' LOG_FILE_SIZE = 128 # Размер файла лога в МБ Файл config/__init__.py CONFIG = 'config.default' 

Myapp / __ init__.py file

 import config import logging from flask import Flask from logging.handlers import RotatingFileHandler app = Flask(__name__) app.config.from_object(config.CONFIG) app.config.from_envvar('FLASKR_SETTINGS', silent=True) # Логирование handler = RotatingFileHandler(app.config['LOG_FILE'], maxBytes=app.config['LOG_FILE_SIZE']*1024*1024, backupCount=1) handler.setLevel(logging.INFO) formatter = logging.Formatter(app.config['LONG_LOG_FORMAT']) handler.setFormatter(formatter) app.logger.addHandler(handler) # API from . import ns_api from . import views 

File myapp / ns_api / __ init__.py

 from flask_jsonrpc import JSONRPC from .. import app jsonrpc = JSONRPC(app, '/api') from . import logic 

Myapp / views.py file

 from myapp import app from flask import render_template from . import forms, models @app.route('/') def index(): pagedata = {} pagedata['title'] = app.config['TITLE'] pagedata['data'] = { "A": True, "B": False, "result": False } body = render_template('index.html', pagedata=pagedata) return body 

File myapp / ns_api / logic.py

 import operator from . import jsonrpc @jsonrpc.method('logic.and(A=bool, B=bool)') def logic_and(A, B): """ Логическое И """ return operator.and_(A, B) @jsonrpc.method('logic.not(A=bool)') def logic_not(A): """ Логическое НЕ """ return operator.not_(A) @jsonrpc.method('logic.or(A=bool, B=bool)') def logic_or(A, B): """ Логическое ИЛИ """ return operator.or_(A, B) @jsonrpc.method('logic.xor(A=bool, B=bool)') def logic_xor(A, B): """ Логическое ИСКЛЮЧАЮЩЕЕ ИЛИ """ return operator.xor(A, B) 

Set launch rights

 chmod +x run.py 

Client side of the user interface (front-end, front-end)


Myapp / templates / header.html file

 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css" /> <script src="/static/js/jquery-3.3.1.slim.min.js"></script> <script src="/static/js/popper.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/vue.min.js"></script> <script src="/static/js/axios.min.js"></script> <title>{{ pagedata['title'] }}</title> </head> 

File myapp / templates / skeleton.html

 <!DOCTYPE html> <html lang="ru"> {% include 'header.html' %} <body> <section id="app"> <div class="container-fluid"> {% block content %} {% endblock %} </div> </section> {% block script %} <script type="text/javascript"> var app = new Vue({ el: '#app', data: { }, methods: { } }) </script> {% endblock %} </body> </html> 

File myapp / templates / index.html

 {% extends "skeleton.html" %} {% block content %} <h1>Микросервисная архитектура</h1> <a href="http://127.0.0.1:8000/api/browse">http://127.0.0.1:8000/api/browse</a> <h2>API</h2> <pre>curl -i -X POST \ -H "Content-Type: application/json; indent=4" \ -d '{ "jsonrpc": "2.0", "method": "logic.and", "params": { "A": true, "B": true }, "id": "1" }' http://127.0.0.1:8000/api </pre> <h3>Логические</h3> <ul> <li>logic.and(A, B)</li> <li>logic.not(A)</li> <li>logic.or(A, B)</li> <li>logic.xor(A, B)</li> </ul> <h3>API</h3> <div class="btn-group"> <div class="btn btn-outline-success" v-if="A" v-on:click="changeA">Истина</div> <div class="btn btn-outline-danger" v-else v-on:click="changeA">Ложь</div> <div class="btn btn-outline-secondary disabled">И</div> <div class="btn btn-outline-success" v-if="B" v-on:click="changeB">Истина</div> <div class="btn btn-outline-danger" v-else v-on:click="changeB">Ложь</div> <div class="btn btn-outline-secondary disabled">=</div> <div class="btn btn-success disabled" v-if="result">Истина</div> <div class="btn btn-danger disabled" v-else>Ложь</div> </div> {% endblock %} {% block script %} <script type="text/javascript"> var app = new Vue({ el: '#app', data: {{ pagedata['data']|tojson|safe }}, methods: { changeA: function() { var vm = this; vm.A = !vm.A; vm.update(); }, changeB: function() { var vm = this; vm.B = !vm.B; vm.update(); }, update: function() { var vm = this; axios.post( '/api', { "jsonrpc": "2.0", "method": 'logic.and', "params": { "A": vm.A, "B": vm.B }, "id": 1 } ).then( function(response) { if ('result' in response.data) { vm.result = response.data['result']; } } ); } } }) </script> {% endblock %} 

Source: https://habr.com/ru/post/440944/