1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Python3 + Flask3 + SQLite3 Webアプリ開発メモ

Last updated at Posted at 2020-06-17

srappli-flask Webアプリ

 SRアプリという名のWebアプリをPython3 + Flask3 + SQLite3 で開発しています。これはその個人的な開発メモです。SRが何の略なのかは諸事情で明記できません(汗;)。

 これからしばらくの間ちょくちょく更新していくと思いますので、よろしくお願いします。

フォルダパス構成

スクリーンショット 2019-06-10 23.33.58.png

srappli-flask/instanceフォルダ

SQLiteデータベースのインスタンスを格納するフォルダです。

create_installdb.py
import sqlite3
DROP_INSTALLS = "DROP TABLE IF EXISTS installs"
CREATE_INSTALLS = '''
 CREATE TABLE installs
 (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 applidate DATE,
 applicantsid TEXT,
 applicantsdiv TEXT,
 applicantsname TEXT,
        ledgerno TEXT,
        hopedate DATE,
        enddate DATE,
        stragebin TEXT
    )
'''

conn = sqlite3.connect('installdb.sqlite3')
cur = conn.cursor()
cur.execute(DROP_INSTALLS)
cur.execute(CREATE_INSTALLS)
conn.commit()
conn.close
insert_installdb.py
# coding=utf-8
import sqlite3

INSERT_INSTALL = '''
    INSERT INTO installs (
            applidate,
            applicantsid,
            applicantsdiv,
            applicantsname,
            ledgerno,
            hopedate,
            enddate,
            stragebin
        )
        VALUES (
            date('2020-06-01'),
            'Z1234567',
            'ホゲホゲ開発部',
            '青木 孝',
            '678934',
            date('2020-07-01'),
            date('2020-09-30'),
            '1-2-3'
        )
'''
SELECT_INSTALL = "SELECT * FROM installs"

conn = sqlite3.connect('installdb.sqlite3')

cur = conn.cursor()
cur.execute(INSERT_INSTALL)
conn.commit()

cur.execute(SELECT_INSTALL)
result = cur.fetchall()
print(result)
conn.close()
insert_stragebintable.py
# coding=utf-8
import sqlite3

SELECT_INSTALL = "SELECT * FROM stragebins"

conn = sqlite3.connect('installdb.sqlite3')

cur = conn.cursor()

for i in range(1,5):
    for j in range(1,3):
        for k in range(1,5):
            insertbin = '''
                INSERT INTO stragebins
                (cabinetno,binno,seqno)
                VALUES (%d,%d,%d)
            ''' %(i,j,k)
            cur.execute(insertbin)

conn.commit()

cur.execute(SELECT_INSTALL)
result = cur.fetchall()
print(result)
conn.close()

srappli-flask/webフォルダ

srappli-flask/web/employees.py

employees.py
from flask import(
    Blueprint, render_template,
    request, redirect, url_for
)
from web.installdb import get_db
from web.files import save_csv, read_employees_csv

bp = Blueprint('employees', __name__)

@bp.route('/employees', methods = ['GET'])
def all():
    installdb = get_db()
    SELECT_EMPLOYEES = '''
        SELECT 
            employees.id, 
            employees.name, 
            employees.afficode, 
            departments.abbreviation
        FROM employees JOIN departments
            ON employees.afficode = departments.id
    '''
    alldata = installdb.execute(SELECT_EMPLOYEES).fetchall()
    return render_template('employees/all.html', employees = alldata)

@bp.route('/employees/upload', methods = ['GET', 'POST'])
def upload():
    if request.method == 'POST':
        if 'file' in request.files:
            file = request.files['file']
            save_csv(file)
            datadict = read_employees_csv(file.filename)
            installdb = get_db()
            installdb.execute('DELETE FROM employees')
            for data in datadict:
                installdb.execute(
                    "INSERT INTO employees (id, name, afficode) VALUES (?, ?, ?)",
                    ( data['id'], data['name'], data['afficode'] )
                )
            installdb.commit()
        return redirect(url_for('employees.all'))

    return render_template('employees/upload.html')

srappli-flask/web/files.py

files.py
from flask import current_app
import os
import csv

def save_img(file):
    file.save(os.path.join(current_app.config['UPLOAD_FOLDER'],'img',file.filename))

def save_csv(file):
    file.save(os.path.join(current_app.config['UPLOAD_FOLDER'],'csv',file.filename))

def read_departments_csv(filename):
    datalist=[]
    with open(os.path.join(current_app.config['UPLOAD_FOLDER'], 'csv',
        filename),encoding="utf-8") as datafile:
        datareader=csv.reader(datafile)
        for row in datareader:
                datadict={}
                datadict['id']=row[0]
                datadict['abbreviation']=row[1]
                datalist.append(datadict)

        return datalist

def read_employees_csv(filename):
    datalist=[]
    with open(os.path.join(current_app.config['UPLOAD_FOLDER'], 'csv',
        filename),encoding="utf-8") as datafile:
        datareader=csv.reader(datafile)
        for row in datareader:
            datadict={}
            datadict['name']=row[0]
            datadict['id']=row[1]
            datadict['afficode']=row[2]
            datalist.append(datadict)
        return datalist

def write_file(filename, str):
    with open(os.path.join(current_app.config['UPLOAD_FOLDER'], 'csv',
        filename), 'w') as datafile:
        datafile.write(str)

srappli-flask/web/installdb.py

installdb.py
import sqlite3
from flask import current_app, g

def get_db():
    if 'installdb' not in g:
        g.installdb = sqlite3.connect(
            current_app.config['INSTALLDB'],
            detect_types = sqlite3.PARSE_DECLTYPES
        )
        g.installdb.row_factory = sqlite3.Row
    return g.installdb

def close_db(e = None):
    installdb = g.pop('installdb', None)
    if installdb is not None:
        installdb.close()

def init_app(app):
    app.teardown_appcontext(close_db)

srappli/web/installs.py

installs.py
import sys
from tkinter import messagebox
from flask import(
    Blueprint, render_template,request, redirect, url_for
)
from web.installdb import get_db
from web.files import save_img, write_file
from web.utility import ValideteUtil #, EmailUtil


bp = Blueprint('installs', __name__)

@bp.route('/', methods = ['GET'])
def index():
    return render_template('index.html')

@bp.route('/installs', methods = ['GET'])
def all():
    installdb = get_db()
    alldata = installdb.execute('SELECT * FROM installs').fetchall()
    return render_template('installs/all.html', installs = alldata)

@bp.route('/installs/new', methods = ['GET', 'POST'])
def new():
    installdb = get_db()
    stragebins = installdb.execute('SELECT * FROM stragebins').fetchall()
    departments = installdb.execute('SELECT * FROM departments').fetchall()
    if request.method == 'POST':
        if ValideteUtil.isDecimal(request.form['applicantsid']) == False:
            applicantsid_message = '※社員ID:全て数値で入力してください',
            return render_template('installs/new.html',
                stragebins = stragebins, departments = departments,
                applicantsid_message = applicantsid_message
            )
        if ValideteUtil.isDecimal(request.form['ledgerno']) == False:
            ledgerno_message = '※台帳No.:全て数値で入力してください'
            return render_template('installs/new.html',
                stragebins = stragebins, departments = departments,
                ledgerno_message = ledgerno_message
            )
        install_data = (
            request.form['applidate'],
            request.form['applicantsid'],
            request.form['applicantsdiv'],
            request.form['applicantsname'],
            request.form['ledgerno'],
            request.form['hopedate'],
            request.form['enddate'],
            request.form['stragebin']
        )
        installdb.execute(
            "INSERT INTO installs (applidate, applicantsid, applicantsdiv, applicantsname, ledgerno, hopedate, enddate, stragebin) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            install_data
        )
        installdb.commit()
        #EmailUtil.sendEmail()
        return redirect(url_for('installs.all'))

    return render_template(
        'installs/new.html', 
        stragebins = stragebins, departments = departments
    )

@bp.route('/installs/show/<install_id>', methods = ['GET', 'POST'])
def show(install_id):
    if request.method == 'GET':
        installdb = get_db()
        install = installdb.execute('SELECT * FROM installs WHERE id = ?', install_id).fetchone()
        print(install)
        return render_template('installs/show.html', install = install)

@bp.route('/installs/edit/<install_id>', methods = ['GET', 'POST'])
def edit(install_id):
    installdb = get_db()
    if request.method == 'POST':
        install_data = (
            request.form['applidate'],
            request.form['applicantsid'],
            request.form['applicantsdiv'],
            request.form['applicantsname'],
            request.form['ledgerno'],
            request.form['hopedate'],
            request.form['enddate'],
            request.form['stragebin'],
            install_id
        )
        installdb.execute(
            "UPDATE installs SET applidate = ?, applicantsid = ?, applicantsdiv = ?, applicantsname = ?, ledgerno = ?, hopedate = ?, enddate = ?, stragebin = ? where id = ?",
            install_data
        )
        installdb.commit()
        return redirect(url_for('installs.all'))

    install = installdb.execute(
        "SELECT * FROM installs where id = ?", (install_id,)
    ).fetchone()
    stragebins = installdb.execute('SELECT * FROM stragebins').fetchall()
    departments = installdb.execute('SELECT * FROM departments').fetchall()
    return render_template(
        'installs/edit.html',
        install = install, stragebins = stragebins, departments = departments
    )

@bp.route('/installs/delete/<install_id>', methods = ['GET'])
def delete(install_id):
    installdb = get_db()
    installdb.execute(
        "DELETE from installs where id = ?", (install_id,)
    )
    installdb.commit()
    return redirect(url_for('installs.all'))

@bp.route('/installs/upload/<install_id>', methods = [ 'GET', 'POST' ])
def upload(install_id):
    db = get_db()
    if request.method == 'POST':
        if 'file' in request.files:
            file = request.files['file']
            save_img(file)
            db.execute(
                "UPDATE installs SET stragebin = ? WHERE id = ?",
                (file.filename, install_id)
            )
            db.commit()

        return redirect(url_for('installs.show', install_id = install_id))

    install = db.execute('SELECT * FROM installs WHERE id = ?', install_id).fetchone()
    return render_template('installs/upload.html', install = install)                                                                                           
@bp.route('/installs/write', methods = [ 'GET' ])
def write():
    csv_str = ""
    db = get_db()
    alldata = db.execute('SELECT * FROM installs').fetchall()
    for data in alldata:
        csv_str += ",".join( [ data['ledgerdno'], data['applicantsname'], data['hopedate'] ] )
        csv_str += "\n"
    write_file("installs.csv", csv_str)
    return render_template('installs/write.html', str = csv_str)

srappli/web/stragebins.py

stragebins.py
import sys
from tkinter import messagebox
from flask import(
    Blueprint, render_template,request, redirect, url_for
)
from web.installdb import get_db
from web.files import save_img, write_file
from web.utility import ValideteUtil #, EmailUtil


bp = Blueprint('installs', __name__)

@bp.route('/', methods = ['GET'])
def index():
    return render_template('index.html')

@bp.route('/installs', methods = ['GET'])
def all():
    installdb = get_db()
    alldata = installdb.execute('SELECT * FROM installs').fetchall()
    return render_template('installs/all.html', installs = alldata)

@bp.route('/installs/new', methods = ['GET', 'POST'])
def new():
    installdb = get_db()
    stragebins = installdb.execute('SELECT * FROM stragebins').fetchall()
    departments = installdb.execute('SELECT * FROM departments').fetchall()
    if request.method == 'POST':
        if ValideteUtil.isDecimal(request.form['applicantsid']) == False:
            applicantsid_message = '※社員ID:全て数値で入力してください',
            return render_template('installs/new.html',
                stragebins = stragebins, departments = departments,
                applicantsid_message = applicantsid_message
            )
        if ValideteUtil.isDecimal(request.form['ledgerno']) == False:
            ledgerno_message = '※台帳No.:全て数値で入力してください'
            return render_template('installs/new.html',
                stragebins = stragebins, departments = departments,
                ledgerno_message = ledgerno_message
            )
        install_data = (
            request.form['applidate'],
            request.form['applicantsid'],
            request.form['applicantsdiv'],
            request.form['applicantsname'],
            request.form['ledgerno'],
            request.form['hopedate'],
            request.form['enddate'],
            request.form['stragebin']
        )
        installdb.execute(
            "INSERT INTO installs (applidate, applicantsid, applicantsdiv, applicantsname, ledgerno, hopedate, enddate, stragebin) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            install_data
        )
        installdb.commit()
        #EmailUtil.sendEmail()
        return redirect(url_for('installs.all'))

    return render_template(
        'installs/new.html', 
        stragebins = stragebins, departments = departments
    )

@bp.route('/installs/show/<install_id>', methods = ['GET', 'POST'])
def show(install_id):
    if request.method == 'GET':
        installdb = get_db()
        install = installdb.execute('SELECT * FROM installs WHERE id = ?', install_id).fetchone()
        print(install)
        return render_template('installs/show.html', install = install)

@bp.route('/installs/edit/<install_id>', methods = ['GET', 'POST'])
def edit(install_id):
    installdb = get_db()
    if request.method == 'POST':
        install_data = (
            request.form['applidate'],
            request.form['applicantsid'],
            request.form['applicantsdiv'],
            request.form['applicantsname'],
            request.form['ledgerno'],
            request.form['hopedate'],
            request.form['enddate'],
            request.form['stragebin'],
            install_id
        )
        installdb.execute(
            "UPDATE installs SET applidate = ?, applicantsid = ?, applicantsdiv = ?, applicantsname = ?, ledgerno = ?, hopedate = ?, enddate = ?, stragebin = ? where id = ?",
            install_data
        )
        installdb.commit()
        return redirect(url_for('installs.all'))

    install = installdb.execute(
        "SELECT * FROM installs where id = ?", (install_id,)
    ).fetchone()
    stragebins = installdb.execute('SELECT * FROM stragebins').fetchall()
    departments = installdb.execute('SELECT * FROM departments').fetchall()
    return render_template(
        'installs/edit.html',
        install = install, stragebins = stragebins, departments = departments
    )

@bp.route('/installs/delete/<install_id>', methods = ['GET'])
def delete(install_id):
    installdb = get_db()
    installdb.execute(
        "DELETE from installs where id = ?", (install_id,)
    )
    installdb.commit()
    return redirect(url_for('installs.all'))

@bp.route('/installs/upload/<install_id>', methods = [ 'GET', 'POST' ])
def upload(install_id):
    db = get_db()
    if request.method == 'POST':
        if 'file' in request.files:
            file = request.files['file']
            save_img(file)
            db.execute(
                "UPDATE installs SET stragebin = ? WHERE id = ?",
                (file.filename, install_id)
            )
            db.commit()

        return redirect(url_for('installs.show', install_id = install_id))

    install = db.execute('SELECT * FROM installs WHERE id = ?', install_id).fetchone()
    return render_template('installs/upload.html', install = install)                                                                                           
@bp.route('/installs/write', methods = [ 'GET' ])
def write():
    csv_str = ""
    db = get_db()
    alldata = db.execute('SELECT * FROM installs').fetchall()
    for data in alldata:
        csv_str += ",".join( [ data['ledgerdno'], data['applicantsname'], data['hopedate'] ] )
        csv_str += "\n"
    write_file("installs.csv", csv_str)
    return render_template('installs/write.html', str = csv_str)

srappli/web/utility.py

utility.py
import re
import smtplib
# from flask_mail import Mail, Message

'''
class EmailUtil:
​
    @staticmethod
    def sendEmail():
        app.config.update(dict(
            MAIL_SERVER = 'smtp.ogis-ri.co.jp',
            MAIL_PORT = 465,
            MAIL_USE_TLS = False,
            MAIL_USE_SSL = True,
            MAIL_USERNAME = 'UserName',
            MAIL_PASSWORD = 'password'
        ))
​
        mail = Mail(app)
        msg = Message('Test', sender='Mail-Addrss@hogehoge.co.jp', recipients=['foo_bakako@hogehoge.co.jp'])
        msg.body = 'This is a test email'
        mail.send(msg)
​
        return 'done'
'''
class ValideteUtil: 

    @staticmethod
    def isInteger(value):
        """
        整数チェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、全て数値の場合 True
        """
        return re.match(r"^\d+$", value) is not None

    @staticmethod
    def isDecimal(value):
        '''
        小数チェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、整数または小数の場合 True True
        '''
        return re.match(r"^[+-]?[0-9]*[.]?[0-9]+$", value) is not None
        
    @staticmethod
    def isAlpha(value):
        """
        半角英字チェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、全て半角英字の場合 True
        """
        return re.match(r"^[a-z]+$", value) is not None

    @staticmethod
    def isAlphaNumeric(value):
        """
        半角英数字チェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、全て半角英数字の場合 True
        """
        return re.match(r"^\w+$", value) is not None

    @staticmethod
    def isHarf(value):
        """
        半角文字チェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、全て半角文字の場合 True (半角カナは含まない)
        """
        return re.match(r"^[\x20-\x7E]+$", value) is not None

    @staticmethod
    def isHarfKana(value):
        """
        半角カナチェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、全て半角カナの場合 True
        """
        return re.match(r"^[ヲ-゚]+$", value) is not None

    @staticmethod
    def isFull(value):
        """
        全角文字チェック
        :param value: チェック対象の文字列
        :rtype: チェック対象文字列が、全て全角文字の場合 True 
        """
        return re.match(r"^[^\x01-\x7E]+$", value) is not None

srappli/web/departments.py

departments.py
from flask import(
    Blueprint, render_template,
    request, redirect, url_for
)
from web.installdb import get_db
from web.files import save_csv, read_departments_csv

bp = Blueprint('departments', __name__)

@bp.route('/departments', methods = ['GET'])
def all():
    installdb = get_db()
    alldata = installdb.execute('SELECT * FROM departments').fetchall()
    return render_template('departments/all.html', departments = alldata)

@bp.route('/departments/upload', methods = ['GET', 'POST'])
def upload():
    if request.method == 'POST':
        if 'file' in request.files:
            file = request.files['file']
            save_csv(file)
            datadict = read_departments_csv(file.filename)
            installdb = get_db()
            installdb.execute('DELETE FROM departments')
            for data in datadict:
                installdb.execute(
                    "INSERT INTO departments (id, abbreviation) VALUES (?, ?)",
                    (data['id'], data['abbreviation'])
                )
            installdb.commit()
        return redirect(url_for('departments.all'))

    return render_template('departments/upload.html')

srappli/web/init.py

__init__.py
from flask import Flask
import os

def create_app():
    app = Flask(__name__)

    from . import installs
    app.register_blueprint(installs.bp)

    from . import stragebins
    app.register_blueprint(stragebins.bp)

    from . import departments
    app.register_blueprint(departments.bp)

    from . import employees
    app.register_blueprint(employees.bp)

    app.config.from_mapping(
        SELECT_KEY = 'temp',
        INSTALLDB = os.path.join(app.instance_path, 'installdb.sqlite3'),
    )

    from . import installdb
    installdb.init_app(app)

    UPLOAD_FOLDER = 'C:/Dev/flask/srappli-flask/web/static'
    app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

    return app

srappli-flask/web/staticフォルダ

calendar.js を利用させていただいたサイトさまはこちら

calendar.js
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  【 カレンダー選択入力 】  http://www.cman.jp/
//   商用,改変,再配布はすべて自由ですですが、動作保証はありません
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//    maintenance history
//    Ver   Date        contents
//    0.9   2016/6/20   New
//    0.91  2016/6/21   カレンダ表示位置修理
//    0.92  2017/4/28   ユーザ様ご指摘による不具合修正
//    0.93  2018/9/28   ユーザ様ご指摘による不具合修正                   
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//使用方法
// https://web-designer.cman.jp/javascript_ref/keybord/calendar/
//【注意】
//引数やユーザ設定内容についてはノーチェックです
//解析しやすいようにコメントを多く入れています。
//JavaScriptのファイルサイズを削減する場合は、コメントやスペースを消してください。
//再配布する場合は、当サイトのJavaScriptが元であることを記載ください。
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


var cmanCLD_VAR = {};

// ----- 初期イベントの登録 ------------------------------------------------
if ( window.addEventListener ){window.addEventListener("load", cmanCLD_JS_init, false);}
else if( window.attachEvent )     {window.attachEvent( 'onload', cmanCLD_JS_init );}


//======================================================================
//	初期処理(オンロード時)
//======================================================================
function cmanCLD_JS_init(){
	var wID		= '';
	var wObjAt;
	// ----- 対象タグの一覧を配列に格納 ----------------------------------
	var wTagList = document.getElementsByTagName('input');
	// ----- input type="date","text" を対象にイベント登録する -----------
	for(var i = 0; i < wTagList.length; i++){
		// ----- "date"と"text"だけを対象とする ---------------------
		if((wTagList[i].type.toLowerCase() != "date")&&(wTagList[i].type.toLowerCase() != "text")){
			continue;	// 次の要素へ
		}
		// ----- 属性"cmanCLDat"を取得 ---------------------------
		wObjAt = wTagList[i].getAttribute("cmanCLDat");
		if(wObjAt === null){
			wObjAt = '';
		}
		// ----- "text"の場合は属性"cmanCLDat"が実在しない場合は対象外
     if(wObjAt ==''){
			continue;	// 次の要素へ
		}
		// ----- 属性で「OFF」(未使用)が定義されている場合は対象外 --------
		wObjAt = wObjAt.replace(/\s+/g, "");
		if(wObjAt.match(/USE:OFF/i)){
			continue;	// 次の要素へ
		}
		// ----- "date"でブラウザが"date"に対応している場合は動作させない -
		//      (二重にパレットが出る)
		if(wTagList[i].type.toLowerCase() == "date"){

			// ブラウザがdateに対応しているかチェック
			var wObjTest = document.createElement("input");
			wObjTest.setAttribute("type", "date");

			if(wObjTest.type.toLowerCase() == "date"){
				continue;	// 次の要素へ
			}
		}
		// ----- 対象の要素にカラーパレットを開くイベント(KeyDown時)を登録 --
		wTagList[i].onkeydown	= cmanCLD_JS_open;
		wTagList[i].onclick	= cmanCLD_JS_open;
	}
	// ----- カレンダーを閉じるためのイベント登録 ----------------------------
  document.body.onclick	= cmanCLD_JS_bodyClick;
	window.onscroll		= cmanCLD_JS_cl;
}
//======================================================================
//	カレンダー以外がクリックされた場合は、カレンダを閉じる
//======================================================================
function cmanCLD_JS_bodyClick(e){

  var wObjClick = e.target;
 // ----- カレンダーが開いていない場合は対象外 --------------------------	 
 if(document.getElementById(cmanCLD_VAR["popId"])){
	}else{
		return;
	}
	// ----- クリックされた要素の親要素に「cmanCLD」が実在する場合は対象外 ------
	var wObjAt = wObjClick.getAttribute("cmanCLDat");
	if((wObjAt === null)||(wObjAt == '')){
	}else{
		return;
	}
	// ----- クリックされた要素の親要素に「cmanCLD」が実在する場合は対象外 -------
	if(wObjClick.id.match(/^cmanCLD/)){
		return;
	} 
	// ----- クリックされた要素の親要素に「cmanCLD」が実在する場合は対象外 -------
	var wOyaObj = wObjClick;
	for(var j = 0; j < 10; j++){
	  wOyaObj = wOyaObj.parentNode;
	  if((typeof wOyaObj === 'object')&&(wOyaObj.tagName.toLowerCase() != 'html')){
	    if(wOyaObj.id.match(/^cmanCLD/)){
	      return;
	    } 
	   }else{
	     break;
	   }
	}
	// ----- カレンダーが開いている場合は閉じる --------------------------
  cmanCLD_JS_cl();
}
//======================================================================
//	カレンダーを開く	
//======================================================================
function cmanCLD_JS_open(e){
	// ----- カレンダーが開いている場合は閉じる -----------------------------
  cmanCLD_JS_cl();
	// ----- 対象外キーは処理を抜ける -----------------------------------
  switch (e.keyCode){
	  case 9	:		// tab
	  case 16	:		// shift
		  return;
		  break;
	}
	// ----- 日付入力エリア --------------------------------------------
  	cmanCLD_VAR["objValue"] = e.target;
	// ----- 選択済み日付をハッシュ変数に保存 -----------------------------
  	cmanCLD_VAR["selectedDate"] = '';
	if(cmanCLD_JS_DateChk(cmanCLD_VAR["objValue"].value)){
		var wSplit = cmanCLD_VAR["objValue"].value.split("-");

		if(wSplit.length == 3){
			cmanCLD_VAR["selectedDate"] = cmanCLD_JS_dateEdit(wSplit[0], wSplit[1], wSplit[2]);
		}
	}
	// ----- カレンダ編集&表示 -----------------------------------------
  	if(cmanCLD_VAR["selectedDate"] == ''){
		cmanCLD_JS_create('','');
	}
	else{
		cmanCLD_JS_create( new Date(cmanCLD_VAR["selectedDate"]).getFullYear(),  new Date(cmanCLD_VAR["selectedDate"]).getMonth() + 1);
	}
	// ----- 押されたキーはクリアする -------------------------------------
  	e.keyCode = 0;
	return false;

}
//======================================================================
//	表示年月変更
//======================================================================
function cmanCLD_JS_res(argYM, argUD){
	var wNextOutYMD;
	wNextOutYMD = new Date( cmanCLD_VAR["outYYYY"], parseInt(cmanCLD_VAR["outMM"]) - 1, 1);
	// ----- 次に表示する年月を求める ------------------------------------
  	if(('outYYYY' in cmanCLD_VAR)&&('outMM' in cmanCLD_VAR)){
		if(argYM == 'y'){
			if(argUD == 'u'){				wNextOutYMD.setYear(wNextOutYMD.getFullYear() + 1);
			}else{				wNextOutYMD.setYear(wNextOutYMD.getFullYear() - 1);
			}
		}else{
			if(argUD == 'u'){				wNextOutYMD.setMonth(wNextOutYMD.getMonth() + 1);
			}else{
				wNextOutYMD.setMonth(wNextOutYMD.getMonth() - 1);
			}
		}
	}else{
		wNextOutYMD = new Date();
	}
	// ----- カレンダ編集&表示 -----------------------------------------
  	cmanCLD_JS_edit( wNextOutYMD.getFullYear(),  wNextOutYMD.getMonth() + 1);
}
//======================================================================
//	カレンダーの編集
//======================================================================
function cmanCLD_JS_edit(argYYYY, argMM){
	var wObjDate_FirstDay;         // 月初
	var wObjDate_LastDay;          // 月末
	var wStr_FirstDay_Week  = 0;   // 月初の曜日
	var wStr_LastDay_Week   = 0;   // 月末の曜日
	var wObjDate_LastMonth;        // 前月
	var wObjDate_NextMonth;        // 翌月
	var wObjDate_JpFirstDay;       // 元号判定用の元号別月初
	var wOutY_JpName        = '';  // 元号
	var wOutY_JpYY          = '';  // 和暦年
	var wDayList            = [];  // カレンダーに表示する日付
	var wDayListIdx         = 0;
	var wDayListTargetIdxF  = 0;
	var wDayListTargetIdxT  = 0;
	var wStrNowDate         = 0;   // 現在日付(表示のカレンダの月と違う場合は0)
	var wLang               = "";
	// ----- 曜日表示テーブル -----------------------------------------
  var wWeekJA	= [ '', '', '', '', '', '', '' ];
	var wWeekEN	= [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];

	// ----- 和暦の表示テーブル(元号変更時は追加が必要) --------------------
  var wG_YMS	= [ '1868-01-25', '1912-07-30', '1926-12-25', '1989-01-08' , '2019-05-01' ];
	var wG_STR	= [ '明治', '大正', '昭和', '平成', '令和' ];

	// ----- カレンダー枠のID設定 --------------------------------------
  cmanCLD_VAR["popId"]	= 'cmanCLD_POP'; // POP枠のID
	// ----- 属性"cmanCLDat"より言語を取得 -----------------------------
  var wObjAt = cmanCLD_VAR["objValue"].getAttribute("cmanCLDat").replace(/\s+/g, "");
	if(wObjAt.match(/LANG:EN/i))	{wLang = 'EN';}
	else							{wLang = 'JA';}
	// ----- 表示する日付を設定 ----------------------------------------
  if((argYYYY == '')||(argMM=='')){
		wObjDate_FirstDay = new Date(new Date().getFullYear(), new Date().getMonth() , 1);	// 当日の月初
	}else{
		wObjDate_FirstDay = new Date(argYYYY, argMM - 1, 1);
	}

	// 明治以前は表示しない
	if(wObjDate_FirstDay.getTime() < new Date(wG_YMS[0]).getTime()){
		wObjDate_FirstDay = new Date(wG_YMS[0]);
	}
	// ----- 月末を調べる ---------------------------------------------
  wObjDate_LastDay	= new Date(wObjDate_FirstDay.getFullYear(), wObjDate_FirstDay.getMonth() + 1, 0);
	// ----- 表示する和暦年・元号を設定 ----------------------------------
  for (var i = wG_YMS.length - 1; i >=0; i--){
		var wSplit = wG_YMS[i].split("-");
		wObjDate_JpFirstDay =  new Date(wSplit[0], wSplit[1] - 1, 1);			// Ver0.92 Insert
		if(wObjDate_FirstDay.getTime() >= wObjDate_JpFirstDay.getTime()){
			wOutY_JpName = wG_STR[i];
			wOutY_JpYY   = wObjDate_FirstDay.getFullYear() - wObjDate_JpFirstDay.getFullYear() + 1;
			if(wOutY_JpYY == 1){wOutY_JpYY = '';}
			break;
		}
	}
	// ----- 当日がカレンダに実在するか確認する ----------------------------
  wStrNowDate = cmanCLD_JS_dateEdit( new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate());
	// ----- 表示カレンダー設定(上部の年月) ------------------------------
  document.getElementById('cmanCLD_ID_outy').innerHTML	= wObjDate_FirstDay.getFullYear() + '';
	if(wLang == 'JA'){
		document.getElementById('cmanCLD_ID_outy').innerHTML	+= '<div style="font-size:80%;font-weight: normal;padding-top:1px;text-align: center;">' + wOutY_JpName + wOutY_JpYY + '年</div>';
	}
	document.getElementById('cmanCLD_ID_outm').innerText	= (wObjDate_FirstDay.getMonth() + 1) + '';
	// ----- 月初・月末の曜日を調べる ------------------------------------
  wStr_FirstDay_Week	= wObjDate_FirstDay.getDay();		// 0:日曜
	wStr_LastDay_Week	= wObjDate_LastDay.getDay();		// 0:日曜
	// ----- 前月末日を調べる ------------------------------------------
  wObjDate_LastMonth = new Date(wObjDate_FirstDay.getFullYear(), wObjDate_FirstDay.getMonth(), 0);
	wObjDate_NextMonth = new Date(wObjDate_FirstDay.getFullYear(), wObjDate_FirstDay.getMonth() + 1, 1);		// Ver0.93 Update
	// ----- 1行目の前月最終分を配列に格納 -------------------------------
  var wLastMonthDay = wObjDate_LastMonth.getDate() - wStr_FirstDay_Week + 1;
	if(wStr_FirstDay_Week != 0){
		for ( var j = 0; j < wStr_FirstDay_Week; j++){
			wDayList[wDayListIdx++] = cmanCLD_JS_dateEdit(wObjDate_LastMonth.getFullYear(), wObjDate_LastMonth.getMonth() + 1, wLastMonthDay++);
		}
	}
	// ----- 当月分を配列に格納 ----------------------------------------
  wDayListTargetIdxF = wDayListIdx;
	for ( var j = 1; j <= wObjDate_LastDay.getDate(); j++){
		wDayList[wDayListIdx++] = cmanCLD_JS_dateEdit(wObjDate_FirstDay.getFullYear(), wObjDate_FirstDay.getMonth() + 1, j);
	}
	wDayListTargetIdxT = wDayListIdx - 1;
	// ----- 最終行目の翌月最終分を配列に格納 ----------------------------
  var wNextMonthDay = 1;
	if(wStr_LastDay_Week != 6){
		for ( var j = wStr_LastDay_Week; j < 6; j++){
			wDayList[wDayListIdx++] = cmanCLD_JS_dateEdit(wObjDate_NextMonth.getFullYear(), wObjDate_NextMonth.getMonth() + 1, wNextMonthDay++);		// Ver0.93 Update
		}
	}
	// ----- カレンダー部分の編集 ---------------------------------------
  var wCldTbl  = '';
	wCldTbl		+=	'<table id="cmanCLD_ID_tbl1">';

	// ----- カレンダー部分(曜日)の編集 ----------------------------------
  var wWeekTbl;
	if(wLang == 'JA'){wWeekTbl = wWeekJA.concat();}
	else             {wWeekTbl = wWeekEN.concat();}
	wCldTbl		+=	'<tr><th style="color: #ff3333;">'+wWeekTbl[0]+'<  h><th>'+wWeekTbl[1]+'<  h><th>'+wWeekTbl[2]+'<  h><th>'+wWeekTbl[3]+'<  h><th>'+wWeekTbl[4]+'<  h><th>'+wWeekTbl[5]+'<  h><th style="color: #0033ff;">'+wWeekTbl[6]+'<  h><  r>';
	// ----- カレンダー部分(日付)の編集 ----------------------------------
  for (var i = 0; i < wDayList.length; i++){
		if((i % 7) == 0){
			wCldTbl		+=	'<tr>';
		}
		if	(i < wDayListTargetIdxF){
			wCldTbl		+=	'<td class="cmanCLD_CSS_StrOff" id="cmanCLD_ID_col' + i + '" onclick="cmanCLD_JS_sel(\'' + wDayList[i] + '\')">' + new Date(wDayList[i]).getDate() + '<  d>';
		}
		else if	(i > wDayListTargetIdxT){
			wCldTbl		+=	'<td class="cmanCLD_CSS_StrOff" id="cmanCLD_ID_col' + i + '" onclick="cmanCLD_JS_sel(\'' + wDayList[i] + '\')">' + new Date(wDayList[i]).getDate() + '<  d>';
		}
		else{
			wCldTbl		+=	'<td class="cmanCLD_CSS_StrOn" id="cmanCLD_ID_col' + i + '" onclick="cmanCLD_JS_sel(\'' + wDayList[i] + '\')"';
			var wStyle	= '';
			if(cmanCLD_VAR["selectedDate"] == wDayList[i]){
				wStyle	+=	'background-color:#ffcccc;';
			}
			if(wStrNowDate == wDayList[i]){
				wStyle	+=	'border:1px solid #0099ff;';
			}
			if(wStyle !=''){
				wCldTbl	+=	' style="'+wStyle+'"';
			}
			wCldTbl		+=	'>' + new Date(wDayList[i]).getDate() + '<  d>';
		}
		if((i % 7) == 6){
			wCldTbl		+=	'<  r>';
		}
	}
	wCldTbl		+=	'<  able>';
	document.getElementById('cmanCLD_ID_dateList').innerHTML = wCldTbl;
	// ----- カレンダの表示 --------------------------------------------
  cmanCLD_VAR["objPop"].style.display	= '';
	cmanCLD_VAR["outYYYY"]	= wObjDate_FirstDay.getFullYear();
	cmanCLD_VAR["outMM"]	= wObjDate_FirstDay.getMonth() + 1;
}
//======================================================================
//	カレンダーの雛形枠作成
//======================================================================
function cmanCLD_JS_create(argYYYY, argMM){
	cmanCLD_VAR["popId"]	= 'cmanCLD_POP';		// POP枠のID
	if(document.getElementById(cmanCLD_VAR["popId"])){return;}
	// ----- CSS定義 ------------------------------------------------
  var wCss =	'<style type="text/css">';
	wCss	+=	'#cmanCLD_ID_area {' +
				'width: 100%;' + 
				'max-width:260px;' + 
				'border: 1px solid #000;' + 
				'font-size: 9pt;' + 
				'background-color: #fff;' +
				'line-height: 1.1em;' +
				'letter-spacing: 1px;' +
			'}';
	wCss	+=	'#cmanCLD_ID_tbl1 {' + 
				'width: 100%;' + 
				'text-align: center;' + 
				'border-collapse:collapse;' + 
				'border: 1px solid #fff;' + 
				'font-size: 10pt;' + 
				'color: #333;' + 
			'}';
	wCss	+=	'#cmanCLD_ID_tbl1 th {' + 
				'background-color: #eee;' + 
				'padding: 3px;' + 
				'border-top: 1px solid #666;' + 
				'border-bottom: 1px solid #ccc;' + 
				'font-weight: normal;' + 
			'}';
	wCss	+=	'#cmanCLD_ID_tbl1 td {' + 
				'padding: 3px;' + 
				'cursor: pointer;' + 
				'font-weight: normal;' + 
			'}';
	wCss	+=	'#cmanCLD_ID_tbl1 td:hover {' + 
				'background-color: #ddeeff;' + 
			'}';
	wCss	+=	'#cmanCLD_ID_ym {' + 
				'width: 100%;' + 
				'text-align: center;' + 
				'border-collapse:collapse;' + 
				'border: 1px solid #fff;' + 
				'font-size: 10pt;' + 
				'color: #333;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_btm {' + 
				'margin: 5px;' + 
				'width: 22px;' + 
				'height: 22px;' + 
				'cursor: pointer;' + 
				'border: 1px solid #fff;' + 
				'border-radius: 15%;' + 
				'background-color: #fff;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_btm:hover {' + 
				'border: 1px solid #ccc;' + 
				'background-color: #eee;' + 
				'box-shadow: none;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_bty {' + 
				'margin: 2px auto 2px 0;' + 
				'width: 22px;' + 
				'height: 11px;' + 
				'cursor: pointer;' + 
				'border: 1px solid #fff;' + 
				'border-radius: 15%;' + 
				'background-color: #fff;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_bty:hover {' + 
				'border: 1px solid #ccc;' + 
				'background-color: #eee;' + 
				'box-shadow: none;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_mstr{' + 
				'font-size: 120%;' + 
				'font-weight :bold;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_StrOn{' + 
				'color: #333;' + 
			'}';
	wCss	+=	'.cmanCLD_CSS_StrOff{' + 
				'color: #999;' + 
			'}';
	wCss	+=	'</style>';
	// ----- HTML定義 -----------------------------------------------
  var wHtml =	'';
	// ----- カレンダー枠 ---------------------------------------------
  wHtml +='<div id="cmanCLD_ID_area">';
	// ----- 上部年月 ------------------------------------------------
  wHtml +=	'<div>' +
			'<table id="cmanCLD_ID_ym">' +
				'<colgroup span="1" style="width:10%">' +
				'<colgroup span="1" style="width:30%">' +
				'<colgroup span="2" style="width:10%">' +
				'<colgroup span="1" style="width:20%">' +
				'<colgroup span="2" style="width:10%">' +
				'<tr>' +
					'<td>' +
						'<div class="cmanCLD_CSS_btm" onclick="cmanCLD_JS_res(\'y\',\'d\')">' +
							'<svg width="20px" height="20px" viewBox="0 0 22 22">' +
								'<polygon points="14,4 14,18 7,11" style="fill:#999;stroke:none;stroke-width:1">' +
							'</svg>' +
						'</div>' +
					'<  d>' +
					'<td>' +
						'<div class="cmanCLD_CSS_mstr" style="text-align: center;" id="cmanCLD_ID_outy">YYYY年</div>' +
					'<  d>' +
					'<td>' +
						'<div class="cmanCLD_CSS_btm" onclick="cmanCLD_JS_res(\'y\',\'u\')">' +
							'<svg width="20px" height="20px" viewBox="0 0 22 22">' +
								'<polygon points="9,4 9,18 16,11" style="fill:#999;stroke:none;stroke-width:1">' +
							'</svg>' +
						'</div>' +
					'<  d>' +
					'<td>' +
						'<div class="cmanCLD_CSS_btm" style="margin-left:auto;margin-right:2px;" onclick="cmanCLD_JS_res(\'m\',\'d\')">' +
							'<svg width="20px" height="20px" viewBox="0 0 22 22">' +
								'<polygon points="14,4 14,18 7,11" style="fill:#999;stroke:none;stroke-width:1">' +
							'</svg>' +
						'</div>' +
					'<  d>' +
					'<td>' +
						'<div class="cmanCLD_CSS_mstr" style="text-align: center;" id="cmanCLD_ID_outm">MM月</div>' +
					'<  d>' +
					'<td>' +
						'<div class="cmanCLD_CSS_btm" style="margin-left:2px;margin-right:auto;" onclick="cmanCLD_JS_res(\'m\',\'u\')">' +
							'<svg width="20px" height="20px" viewBox="0 0 22 22">' +
								'<polygon points="9,4 9,18 16,11" style="fill:#999;stroke:none;stroke-width:1">' +
							'</svg>' +
						'</div>' +
					'<  d>' +
					'<td>' +
						'<div class="cmanCLD_CSS_btm" style="margin-left:auto;margin-right:2px;" onclick="cmanCLD_JS_cl()">' +
							'<svg width="20px" height="20px" viewBox="0 0 22 22">' +
								'<path stroke="#999" stroke-width="2" fill="none" d="M 7 7 L 16 16  M 7 16 L 16 7">' +
							'</svg>' +
						'</div>' +
					'<  d>' +
				'<  r>' +
			'<  able>' +
			'</div>';
	// ----- 日付 ---------------------------------------------------
	wHtml +=	'<div id="cmanCLD_ID_dateList">' +
			'</div>';
	wHtml +=	'<div style="margin:3px 0 0 auto;text-align: center;font-size: 8pt;padding: 2px 5px;color:#999;background-color:#f3f3f3">' +
				'calendar : ' +
				'<a href="http://web-designer.cman.jp/javascript_ref/" target="_blank" style="color:#999;font-weight:normal;">web-designer.cman.jp</a>' +
			'</div>';
	wHtml +='</div>';
	// ----- 表示枠の作成&割り当て(非表示で割り当て) -----------------------
	var wEle = document.createElement("div");   // 新規に要素(タグ)を生成
	wEle.id = cmanCLD_VAR["popId"];
	wEle.style.display = "none";
	wEle.style.position = 'fixed';
	document.body.appendChild(wEle);            // このページ (document.body) の最後に生成した要素を追加
	cmanCLD_VAR["objPop"] = wEle;
	cmanCLD_VAR["objPop"].innerHTML	= wCss + "\n" + wHtml;

	// ----- カレンダー内容を編集 ---------------------------------------
	cmanCLD_JS_edit(argYYYY, argMM);
	// ----- 入力要素の位置・サイズ取得 ----------------------------------
	var wObjValue = cmanCLD_VAR["objValue"].getBoundingClientRect();
	var wValueX = wObjValue.left;
	var wValueY = wObjValue.top;
	var wValueH = wObjValue.height;
	// ----- カレンダーの位置を一旦入力エリアの下に割り当て -------------------
	cmanCLD_VAR["objPop"].style.left	= wValueX + 'px';
	cmanCLD_VAR["objPop"].style.top		= ( wValueY + wValueH ) + 'px';
	// ----- カレンダーを100%透過(見えない状態で)非表示解除 -----------------
	cmanCLD_VAR["objPop"].style.opacity	= 0;
	cmanCLD_VAR["objPop"].style.display	= '';
	// ----- カレンダーの縦サイズを取得 ----------------------------------
	var wObjPop = cmanCLD_VAR["objPop"].getBoundingClientRect();
	var wPopH = wObjPop.height;
	// ----- カレンダーの表示位置設定 -----------------------------------
	// 表示可能域を比較し、優先順位:下>上で設定
	// 両方とも満たさない場合は下に表示
	var wH = document.documentElement.clientHeight - (wValueY + wValueH);
	if(wH > wPopH){
		cmanCLD_VAR["objPop"].style.top		= (wValueY + wValueH)+'px';
	}
	else if	(wValueY > wPopH){
		cmanCLD_VAR["objPop"].style.top		= (wValueY - wPopH )+'px';
	}
	else{
		cmanCLD_VAR["objPop"].style.top		= (wValueY + wValueH)+'px';
	}
	// ----- カレンダーの透過を解除して表示 -------------------------------
	cmanCLD_VAR["objPop"].style.opacity	= 1;
}
// =========================================================================================
//	日付が選択されたら
// =========================================================================================
function cmanCLD_JS_sel(argYMD){
	// ----- 選択した日付を設定する -------------------------------------
  cmanCLD_VAR["objValue"].value = argYMD;
	cmanCLD_VAR["selDate"]        = argYMD;
	// ----- カレンダーを閉じる -----------------------------------------
  cmanCLD_JS_cl();
}
//======================================================================
//	閉じるボタンが押されたら
//======================================================================
function cmanCLD_JS_cl(){
	// ----- カレンダーが開いていない場合はreturn -------------------------
  if(document.getElementById(cmanCLD_VAR["popId"])){
	}else{
		return;
	}
	// ----- 指定の関数を実行 -----------------------------------------
  var wObjAt = cmanCLD_VAR["objValue"].getAttribute("cmanCLDat").replace(/\s+/g, "").split(",");
	for( var i=0; i<wObjAt.length; i++){
		if(wObjAt[i].substr(0,5).toUpperCase() == "FUNC:"){
			if('selDate' in cmanCLD_VAR){
			}else{
				cmanCLD_VAR["selDate"] = '';
			}
			try{
				eval(wObjAt[i].substr(5)+'("'+cmanCLD_VAR["selDate"]+'")');
			}
			catch(e){
				alert('関数('+wObjAt[i].substr(5)+')を実行できません');
			}
		}
	}
	// ----- 登録したカレンダー要素を消す ---------------------------------
	var wDelElm = document.getElementById(cmanCLD_VAR["popId"]);
	document.body.removeChild(wDelElm);
	// ----- WKハッシュクリア -------------------------------------------
	for(var key in cmanCLD_VAR){
		delete cmanCLD_VAR[key];
	}
}
//======================================================================
//	日付チェック
//======================================================================
function cmanCLD_JS_DateChk(argDate) {
	var wY = 0;
	var wM = 0;
	var wD = 0;
	if(argDate==''){return true;}
	var wYMD = argDate.split("-");
	if(wYMD.length != 3){
		return false;
	}
	if(wYMD[0].toString().match(/^[0-9]+$/)){wY = parseInt(wYMD[0]);}
	if(wYMD[1].toString().match(/^[0-9]+$/)){wM = parseInt(wYMD[1]);}
	if(wYMD[2].toString().match(/^[0-9]+$/)){wD = parseInt(wYMD[2]);}
	if((wY < 1900)||(wM < 1)||(wM > 12)||(wD < 1)||(wD > 31)){
		return false;
	}
	var wDate = new Date(wY, wM - 1, wD, 0, 0, 0, 0);
	if((wDate.getFullYear() != wY)||(wDate.getMonth() != wM - 1)||(wDate.getDate() != wD)){
	        return false;
	}
	return true;
}
//======================================================================
//	日付編集
//======================================================================
function cmanCLD_JS_dateEdit(argYear, argMonth, argDate){
	if(argYear == ''){
		return '';
	}
	var wYYYY	= argYear;
	var wMM		= ("0" + argMonth).slice(-2);
	var wDD		= ("0" + argDate).slice(-2);
	var wObjAt = cmanCLD_VAR["objValue"].getAttribute("cmanCLDat").replace(/\s+/g, "");
	if      (wObjAt.match(/FORM:2/i)){return wYYYY + '' + wMM + '' + wDD;}
	else if (wObjAt.match(/FORM:3/i)){return wYYYY + '/' + wMM + '/' + wDD;}
	else                             {return wYYYY + '-' + wMM + '-' + wDD;}
}
utility.js
function confirm_delete_appli( id ){
  if( window.confirm("申請を削除しますがよろしいですか?")){
    location.href = "/delete/" + id;
  }
}

function getToday(){
  let today = new Date();
  return( 
    today.getFullYear() + '-' 
    + ('0' + (today.getMonth() + 1 ) ).slice( -2 ) + '-'
    + ('0' + today.getDate() ).slice( -2 )
  );
}

function numeric_check(itemobject){
  var flag = 0;
  if(itemobject.value.match(/[^0-9]+/)){
    flag = 1;
  }
  if(flag){
    window.alert('数字以外が入力されています');
    return false;
  }
  else{
    return true;
  }
}
style.css
body { margin: 0px; }
h1 { width:800px; background: slategray; color: snow; padding: 2px; }
h2 { margin-left:0px; padding-left:5px; width:780px; background: rgb(219, 219, 219); border-bottom: 1px solid dimgray; }
h3 { margin-left:20px; padding-left:5px; width:680px; background: rgb(239, 236, 239); border-top: 1px solid dimgray; }
a { color: #000; text-decoration: none; background: #ccc; }
th, td { border-collapse: collapse; border-bottom:1px solid dimgray; padding:2px; }
input.submit { width: 180px; font-weight: bold;}
button.menu { width: 250px; height:40px; background: seashell; font-size: 20px; }
.required { background: lemonchiffon; }
div.footer { width: 800px; margin-top: 50px; border-top: dimgray 1px solid; border-bottom: dimgray 1px solid;font-size: 12px; background: rgb(187, 186, 186); color: white; }
.caution { color: red;}
.required { background: lemonchiffon; color:black;}
.new_author { position: relative; left: 200px; }
.author,span { background:dimgray;color: white; }
.backgainsboro { background: gainsboro; }
.backsilver { background: silver; }
.backgray { background: gray; }
.rightalign { text-align: right; }
.centeralign { text-align: center; }
.leftalign { text-align: left; }
.bottomnone { border-bottom:none; }
.width200{ width: 200px;}.width300{width: 300px; }.width500{width: 500px; }
.padding5{padding: 5px;}
.marginleft200 { margin-left: 200px; }
# applicantsdiv { width: 300px; }

srappli-flask/web/templatesフォルダ

srappli-flask/web/templates/base.html

base.html
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta charset="utf-8" />
        <title>{% block title %}{% endblock %} - 設置申請<  itle>
        
        <link rel="stylesheet" href="{{ url_for('static', filename = 'style.css') }}" />
        <script src="{{ url_for('static', filename = 'calendar.js') }}" charset="utf-8"></script>
        <script src="{{ url_for('static', filename = 'utility.js') }}" charset="utf-8"></script>
        <script>
            window.onload = function() {
                if( document.getElementById('applidate') ){
                    document.getElementById('applidate').value = getToday();
                }
            }
        </script>
    </head>
    <body>
        {% block content %}{% endblock %}
        <div class="footer centeralign">ホゲホゲ本部ファーファー部</div>
    </body>
</html>

srappli-flask/web/templates/index.html

index.html
{% extends 'base.html' %}

{% block title %}サーバールーム設置申請システム{% endblock %}

{% block content %}
  <h1>サーバールーム設置申請システム</h1>

  <h2>申請メニュー</h2>
  <div>
    <h3>設置</h3>
    <a href="{{ url_for('installs.new') }}"><button class="menu">新規設置申請</button></a>
    <a href="{{ url_for('installs.all') }}"><button class="menu">設置申請一覧</button></a>
  </div>
  <h2>管理者メニュー</h2>
  <div>
    <h3></h3>
    <a href="{{ url_for('stragebins.all') }}"><button class="menu">棚番号一覧</button></a>  
    <a href="{{ url_for('stragebins.new') }}"><button class="menu">新規棚番号</button></a>
  </div>
  <div>
    <h3>所属部署</h3>
    <a href="{{ url_for('departments.all') }}"><button class="menu">所属部署一覧</button></a>  
    <a href="{{ url_for('departments.upload') }}"><button class="menu">所属部署アップロード</button></a>
  </div>
  <div>
    <h3>社員</h3>
    <a href="{{ url_for('employees.all') }}"><button class="menu">社員一覧</button></a>  
    <a href="{{ url_for('employees.upload') }}"><button class="menu">社員アップロード</button></a>
  </div>
{% endblock %}

web/templates/installs

web/templates/installs/all.html

all.html
{% extends 'base.html' %}

{% block title %}サーバー{% endblock %}

{% block content %}
    <h1>設置申請一覧</h1>
    <a href="/"><button>メニューに戻る</button></a>
    <a href="{{ url_for('installs.new') }}"><button>新規設置申請</button></a>
    <a href="{{ url_for('stragebins.all') }}"><button>棚番一覧</button></a>
    <a href="{{ url_for('installs.write') }}"><button>データの書き出し</button></a>
    <table>
        <thead class="backgainsboro">
            <th colspan="2">申請</th><th colspan="3">申請者</th><th colspan="3">コンピュータ</th><th></th>
        </thead>
        <thead>
            <th>ID</th><th>年月日</th><th>社員ID</th><th>所属</th><th>氏名</th><th>台帳No.</th><th>設置希望日</th><th>設置終了予定日</th><th>希望棚番</th>
        </thead>
        <tbody>
            {% for install in installs %}
                <tr>
                    <td>
                        <a href="{{ url_for( 'installs.show', install_id = install['id'] ) }}"><button>{{ install['id'] }}</button></a>
                    </td>
                    <td>{{ install['applidate'] }}</td>
                    <td>{{ install['applicantsid'] }}</td>
                    <td>{{ install['applicantsdiv'] }}</td>
                    <td>{{ install['applicantsname'] }}</td>
                    <td>{{ install['ledgerno'] }}</td>
                    <td>{{ install['hopedate'] }}</td>
                    <td>{{ install['enddate'] }}</td>
                    <td>{{ install['stragebin'] }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

web/templates/installs/edit.html

edit.html
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}申請Ωを編集{% endblock %}</h1>
{% endblock %}

{% block content %}
    <h2>申請を編集します</h2>

    <div>
        <a href="/"><button>メニューに戻る</button></a>
        <a href="{{ url_for( 'installs.show', install_id = install['id'] ) }}"><button>詳細に戻る</button></a>
        <a href="{{ url_for( 'installs.all' ) }}"><button>申請一覧に戻る</button></a>
    </div>
    <div class="caution">{{ applicantsid_message }}</div>
    <div class="caution">{{ ledgerno_message }}</div>
    <form method="POST">
        <table>
            <thead>
                <th class="rightalign">分類</th><th class="rightalign">申請項目</th><th class="width300 leftalign">申請入力</th>
            </thead>
            <tbody>
                <tr>
                    <td class="rightalign backgainsboro">申請</td>
                    <td class="rightalign">申請日</td>
                    <td><input name="applidate" id="applidate" size="10" class="backsilver" value="{{ install['applidate'] }}" /></td>
                </tr>
                <tr>
                    <td rowspan="3" class="rightalign backgainsboro">申請者</td>
                    <td class="rightalign bottomnone">社員ID(先頭P無し7桁)</td>
                    <td class="bottomnone"><input name="applicantsid" id="applicantsid" size="7" class="required" value="{{ install['applicantsid'] }}" /></td>
                </tr>
                <tr>
                    <td class="rightalign bottomnone">所属部署</td>
                    <td class="bottomnone">
                        <select name="applicantsdiv" id="applicantsdiv">
                            <option value=""></option>
                            {% for department in departments %}
                                {% if (department['id']+'-'+department['abbreviation']) == install['applicantsdiv'] %}
                                    <option value="{{ department['id'] }}-{{ department['abbreviation'] }}" selected>
                                            {{ department['id'] }}-{{ department['abbreviation'] }}
                                    </option>
                                {% else %}
                                    <option value="{{ department['id'] }}-{{ department['abbreviation'] }}">
                                    {{ department['id'] }}-{{ department['abbreviation'] }}
                                    </option>
                                {% endif %}
                            {% endfor %}
                        </select>
                    </td>
                </tr>
                <tr>
                    <td class="rightalign">氏名</td>
                    <td><input name="applicantsname" id="applicantsname" value="{{ install['applicantsname'] }}" /></td>
                </tr>
                <tr>
                    <td rowspan="3" class="rightalign backgainsboro">コンピューター</td>
                    <td class="rightalign bottomnone">台帳No.</td>
                    <td class="bottomnone"><input name="ledgerno" id="ledgerno" size="7" class="required" value="{{ install['ledgerno'] }}" /></td>
                </tr>
                <tr>
                    <td class="rightalign bottomnone">設置希望日</td>
                    <td class="bottomnone"><input name="hopedate" id="hopedate" type="date" cmanCLDat="USE:ON"  class="required" value="{{ install['hopedate'] }}" /></td>
                </tr>
                <tr>
                    <td class="rightalign">設置終了予定日</td>
                    <td><input name="enddate" id="enddate" type="date" cmanCLDat="USE:ON" value="{{ install['enddate'] }}" /></td>
                </tr>
                <tr>
                    <td class="rightalign backgainsboro"></td>
                    <td class="rightalign">希望棚番</td>
                    <td>
                        <select name="stragebin" id="stragebin">
                            <option value=""></option>
                            <option value="{{ install['stragebin'] }}" selected>{{ install['stragebin'] }}</option>
                            {% for stragebin in stragebins %}
                                <option value="{{ stragebin['cabinetno'] }}-{{ stragebin['columnno'] }}-{{ stragebin['stageno'] }}">
                                {{ stragebin['cabinetno'] }}-{{ stragebin['columnno'] }}-{{ stragebin['stageno'] }}
                                </option>
                            {% endfor %}
                        </select>
                        (キャビネットNO.-列No.-段No.)
                    </td>
                </tr>
            </tbody>
        </table>
        <p><input class="submit marginleft200" type="submit" value="編集確定"></p>
    </form>
    <h3>棚番マップ</h3>
    <img src="./../../static/img/staragebins_map.png" style="width:700px;" />
{% endblock %}

web/templates/installs/new.html

new.html
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}新規書籍{% endblock %}</h1>
{% endblock %}

{% block content %}
    <h2>サーバールームに新規サーバーの設置を申請します</h2>
    <div>
        <a href="/"><button>メニューに戻る</button></a>
        <a href="{{ url_for( 'installs.all' ) }}"><button>設置申請一覧に戻る</button></a>
    </div>
    <div class="caution">{{ applicantsid_message }}</div>
    <div class="caution">{{ ledgerno_message }}</div>
    <form method="POST">
        <table>
            <thead>
                <th class="rightalign">分類</th><th class="rightalign">申請項目</th><th class="width300 leftalign">申請入力</th>
            </thead>
            <tbody>
                <tr>
                    <td class="rightalign backgainsboro">申請</td>
                    <td class="rightalign">申請日</td>
                    <td><input name="applidate" id="applidate" size="10"  type="date" cmanCLDat="USE:ON"  class="backsilver" /></td>
                </tr>
                <tr>
                    <td rowspan="3" class="rightalign backgainsboro">申請者</td>
                    <td class="rightalign bottomnone">社員ID(先頭P無し7桁)</td>
                    <td class="bottomnone"><input name="applicantsid" id="applicantsid" size="7" class="required" /></td>
                </tr>
                <tr>
                    <td class="rightalign bottomnone">所属部署</td>
                    <td class="bottomnone">
                        <select name="applicantsdiv" id="applicantsdiv">
                            <option value=""></option>
                            {% for department in departments %}
                                <option value="{{ department['id'] }}-{{ department['abbreviation'] }}">
                                    {{ department['id'] }}-{{ department['abbreviation'] }}
                                </option>
                            {% endfor %}
                        </select>
                    </td>
                </tr>
                <tr>
                    <td class="rightalign">氏名</td>
                    <td><input name="applicantsname" id="applicantsname" /></td>
                </tr>
                <tr>
                    <td rowspan="3" class="rightalign backgainsboro">コンピューター</td>
                    <td class="rightalign bottomnone">台帳No.</td>
                    <td class="bottomnone"><input name="ledgerno" id="ledgerno" size="7" class="required" /></td>
                </tr>
                <tr>
                    <td class="rightalign bottomnone">設置希望日</td>
                    <td class="bottomnone"><input name="hopedate" id="hopedate" type="date" cmanCLDat="USE:ON" class="required" /></td>
                </tr>
                <tr>
                    <td class="rightalign">設置終了予定日</td>
                    <td><input name="enddate" id="enddate" type="date" cmanCLDat="USE:ON" /></td>
                </tr>
                <tr>
                    <td class="rightalign backgainsboro"></td>
                    <td class="rightalign">希望棚番</td>
                    <td>
                        <select name="stragebin" id="stragebin" class="required">
                            <option value=""></option>
                            {% for stragebin in stragebins %}
                                <option value="{{ stragebin['cabinetno'] }}-{{ stragebin['columnno'] }}-{{ stragebin['stageno'] }}">
                                    {{ stragebin['cabinetno'] }}-{{ stragebin['columnno'] }}-{{ stragebin['stageno'] }}
                                </option>
                            {% endfor %}
                        </select>
                        (キャビネットNO.-列No.-段No.)
                    </td>
                </tr>
            </tbody>
        </table><span class="required">黄色背景色の項目</span>は必須入力です
        <p><input class="submit marginleft200" type="submit" value="申請"></p>
    </form>
    <h3>棚番マップ</h3>
    <img src="../static/img/staragebins_map.png" style="width:700px;" />
{% endblock %}
show.html
{% extends 'base.html' %}

{% block title %}設置申請の詳細{% endblock %}

{% block content %}
    <h2>申請ID:{{ install['id'] }} 設置申請の詳細</h2>
    <div>
        <a href="/"><button>メニューに戻る</button></a>
        <a href="{{ url_for( 'installs.all' ) }}"><button>設置申請一覧に戻る</button></a>
        <a href="{{ url_for( 'installs.edit', install_id = install['id'] ) }}"><button>申請を編集</button></a>
        <a href="{{ url_for( 'installs.delete', install_id = install['id'] ) }}"><button>申請を削除</button></a>
    </div>
    <table>
        <thead>
            <th class="rightalign">分類<  h><th class="rightalign">申請項目<  h><th class="width300 leftalign">申請内容<  h>
        <  head>
        <tbody>
            <tr>
                <td class="rightalign backgainsboro">申請<  d>
                <td class="rightalign">申請日<  d>
                <td>{{ install['applidate'] }}<  d>
            <  r>
            <tr>
                <td rowspan="3" class="rightalign backgainsboro">申請者<  d>
                <td class="rightalign bottomnone">社員ID<  d>
                <td class="bottomnone">{{ install['applicantsid'] }}<  d>
            <  r>
            <tr>
                <td class="rightalign bottomnone">所属<  d>
                <td class="bottomnone">{{ install['applicantsdiv'] }}<  d>
            <  r>
            <tr>
                <td class="rightalign">氏名<  d>
                <td>{{ install['applicantsname'] }}<  d>
            <  r>
            <tr>
                <td rowspan="3" class="rightalign backgainsboro">コンピューター<  d>
                <td class="rightalign bottomnone">台帳No.<  d>
                <td class="bottomnone">{{ install['ledgerno'] }}<  d>
            <  r>
            <tr>
                <td class="rightalign bottomnone">設置希望日<  d>
                <td class="bottomnone">{{ install['hopedate'] }}<  d>
            <  r>
            <tr>
                <td class="rightalign">設置終了予定日<  d>
                <td>{{ install['enddate'] }}<  d>
            <  r>
            <tr>
                <td class="rightalign backgainsboro"><  d>
                <td class="rightalign">希望棚番<  d>
                <td>{{ install['stragebin'] }}<  d>
            <  r>
        <  body>
    <  able>
{% endblock %}
upload.html
{% extends 'base.html' %}

{% block title %}画像をアップロード{% endblock %}

{% block content %}
    <h2>  「{{ install['title'] }}」の画像を指定</h2>
    <form method="POST" enctype="multipart/form-data">
        <div>
            ファイルをアップロードしてください<br/>
            <input type="file" name="file" />
        </div>
        <input type="submit" value="アップロード" />
    </form>
    <div>
        <a href="{{ url_for('installs.show', install_id=install['id']) }}">
            アップロードをやめる
        </a>
    </div>
{% endblock %}
write.html
{% extends 'base.html' %}

{% block title %}データの書き出し{% endblock %}
{% block content %}
    <h1>一覧のCSV形式</h1>
    <div>
        <a href="{{ url_for( 'static' , filename='csv/installs.csv') }}" download="installs.csv">
            ダウンロード
        </a> | 
        <a href="{{ url_for( 'installs.all' ) }}">一覧に戻る</a>
    </div>
    <textarea rows="10" cols="50">{{ str }}<  extarea>
{% endblock %}
srappli-flask/web emplates/stragebinsフォルダ
all.html
{% extends 'base.html' %}

{% block title %}棚番号{% endblock %}

{% block content %}
    <h1>棚番一覧</h1>
    <a href="/"><button>メニューに戻る</button></a>
    <a href="{{ url_for('stragebins.new') }}"><button>新規棚番号</button></a>
    <a href="{{ url_for('installs.all') }}"><button>設置申請一覧</button></a>
    <a href="{{ url_for('stragebins.upload') }}"><button>CSVファイルから棚番号データを追加</button></a>
    <table>
        <thead>
            <th>ID<  h><th>棚番号<  h><th>キャビネットNo.<  h><th>棚段No.<  h><th>列No.<  h>
        <  head>
        <tbody>
            {% for stragebin in stragebins %}
                <tr>
                <td>{{ stragebin['id'] }}<td>
                    <a href="{{ url_for( 'stragebins.show', stragebin_id = stragebin['id'] ) }}">
                            <button>{{ stragebin['cabinetno'] }} - {{ stragebin['binno'] }} - {{ stragebin['seqno'] }}</button>
                    </a>
                <  d>
                <td class="centeralign">{{ stragebin['cabinetno'] }}<  d>
                <td class="centeralign">{{ stragebin['binno'] }}<  d>
                <td class="centeralign">{{ stragebin['seqno'] }}<  d>
            <  r>
        {% endfor %}
        <  body>
    <  able>
{% endblock %}
edit.html
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}棚番号を編集{% endblock %}</h1>
{% endblock %}

{% block content %}
    <h2>ID:{{ stragebin['id'] }} の棚番号を編集します</h2>
    <div>
        <a href="/"><button>メニューに戻る</button></a>
        <a href="{{ url_for( 'stragebins.show', stragebin_id = stragebin['id'] ) }}"><button>詳細に戻る</button></a>
        <a href="{{ url_for( 'stragebins.all' ) }}"><button>棚番一覧に戻る</button></a>
    </div>
    
    <form method="POST">
        <table>
            <thead>
                <th>項目<  h><th>番号<  h>
            <  head>
            <tbody>
                <tr>
                    <td class="backgainsboro">キャビネット番号<  d>
                    <td class="centeralign">
                        <select name="cabinetno" id="cabinetno">
                            {% for cabinetno in range(1,7) %}
                                {% if cabinetno == stragebin['cabinetno'] %}
                                    <option value="{{ cabinetno }}" selected>{{ cabinetno }}</option>
                                {% else %}
                                    <option value="{{ cabinetno }}">{{ cabinetno }}</option>
                                {% endif %}
                            {% endfor%}
                        </select>
                    <  d>
                <  r>
                <tr>
                    <td class="backgainsboro">段番号<  d>
                    <td class="centeralign">
                        <select name="binno" id="binno">
                            {% for binno in range(1,4) %}
                                {% if binno == stragebin['binno'] %}
                                    <option value="{{ binno }}" selected>{{ binno }}</option>
                                {% else %}
                                    <option value="{{ binno }}">{{ binno }}</option>
                                {% endif %}
                            {% endfor%}
                        </select>
                    <  d>
                <  r>
                <tr>
                    <td class="backgainsboro">位置番号<  d>
                    <td class="centeralign">
                        <select name="seqno" id="seqno">
                            {% for seqno in range(1,4) %}
                                {% if seqno == stragebin['seqno'] %}
                                    <option value="{{ seqno }}" selected>{{ seqno }}</option>
                                {% else %}
                                    <option value="{{ seqno }}">{{ seqno }}</option>
                                {% endif %}
                            {% endfor %}
                        </select>
                    <  d>
                <  r>
            <  body>
        <  able>
            
        <p><input type="submit" value="登録" class="submit"/></p>
    </form>
{% endblock %}
new.html
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}新規棚番号{% endblock %}</h1>
{% endblock %}

{% block content %}
    <h2>棚番号を追加します</h2>
    <div>
        <a href="/"><button>メニューに戻る</button></a>
        <a href="{{ url_for( 'stragebins.all' ) }}"><button>棚番号一覧に戻る</button></a>
    </div>
    <form method="POST">
        <table>
            <thead>
                <th>項目<  h><th>番号<  h>
            <  head>
            <tbody>
                <tr>
                    <td class="backgainsboro">キャビネット番号<  d>
                    <td class="centeralign">
                        <select name="cabinetno" id="cabinetno">
                            <option value=""></option>
                            {% for cabinetno in range(1,7) %}
                                <option value="{{ cabinetno }}">{{ cabinetno }}</option>
                            {% endfor%}
                        </select>
                    <  d>
                <  r>
                <tr>
                    <td class="backgainsboro">段番号<  d>
                    <td class="centeralign">
                        <select name="binno" id="binno">
                            <option value=""></option>
                            {% for binno in range(1,4) %}
                                <option value="{{ binno }}">{{ binno }}</option>
                            {% endfor%}
                        </select>
                    <  d>
                <  r>
                <tr>
                    <td class="backgainsboro">位置番号<  d>
                    <td class="centeralign">
                        <select name="seqno" id="seqno">
                            <option value=""></option>
                            {% for seqno in range(1,4) %}
                                <option value="{{ seqno }}">{{ seqno }}</option>
                            {% endfor %}
                        </select>
                    <  d>
                <  r>
            <  body>
        <  able>
        <p><input type="submit" value="登録" class="submit" /></p>
    </form>
{% endblock %}
show.html
{% extends 'base.html' %}

{% block title %}棚番号詳細{% endblock %}

{% block content %}
    <h2>
        棚ID:{{ stragebin['id'] }} 
        ( 棚番:
        {{ stragebin['cabinetno'] }} - {{ stragebin['binno'] }} - {{ stragebin['seqno'] }} 
        ) 詳細
    </h2>
    <div>
s        <a href="{{ url_for( 'stragebins.edit', stragebin_id = stragebin['id']) }}"><button>編集</button></a>
        <a href="{{ url_for( 'stragebins.delete', stragebin_id = stragebin['id'] ) }}"><button>データを削除</button></a>
    </div>
    <table>
        <thead>
            <th>項目<  h><th>番号<  h>
        <  head>
        <tbody>
            <tr>
                <td class="backgainsboro">ID番号<  d>
                <td class="centeralign">{{ stragebin['id'] }}<  d>
            <  r>
            <tr>
                <td class="backgainsboro">キャビネット番号<  d>
                <td class="centeralign">{{ stragebin['cabinetno'] }}<  d>
            <  r>
            <tr>
                <td class="backgainsboro">段番号<  d>
                <td class="centeralign">{{ stragebin['binno'] }}<  d>
            <  r>
            <tr>
                <td class="backgainsboro">位置番号<  d>
                <td class="centeralign">{{ stragebin['seqno'] }}<  d>
            <  r>
        <  body>
    <  able>
{% endblock %}
upload.html
{% extends 'base.html' %}

{% block title %}棚番号データをアップロード{% endblock %}

{% block content %}
    <h2>棚番号データをCSVで読みこませる</h2>
    <form method="POST", enctype="multipart/form-data">
        <div>ファイルをアップロードしてください</div>
        <input type="file" name="file" />
        <input type="submit" value="アップロード" />
    </form>
    <div>
        <a href="{{ url_for('stragebins.all') }}">アップロードをやめる</a>
    </div>
{% endblock %}

DB作成

bash(macOS,Linux)
$ cd instance
$ python create_installdb.py
$ python insert_installdb.py
$ insert_stragebintable.py
$ cd ..

Webアプリ起動

bash(macOS,Linux)
$ export FLASK_ENV="development"
$ export FLASK_APP="web"
$ flask run --host='0.0.0.0' --port=5000
powershell(WindowsServer2016)
> cd c:\Dev\srappli-flask\
> $env:FLASK_ENV="development"
> $env:FLASK_APP="web"
> flask run --host='0.0.0.0' --port=5000

Webアプリ実行

URL http://SaverName:5000/ にWebブラウザでアクセスする
image.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?