From dae67ce6535f636a2b7174440e9b1e2298b381ab Mon Sep 17 00:00:00 2001 From: Jimmy Vargo Date: Tue, 7 May 2024 18:59:57 +0900 Subject: [PATCH] Update Events (#10) This update restructures the events page code and provides additional functionality. - Overwrite old entries on re-submission of previously used emails - Build data and stats caches on launch for use in submission checks and stats regeneration - Update to form content - Move stats function to separate utils file Reviewed-on: https://git.ayo.tokyo/ayo/website/pulls/10 Co-authored-by: Jimmy Vargo Co-committed-by: Jimmy Vargo --- .gitignore | 1 + events/app.py | 81 ++++++++++++++++--------------------- events/form_content.py | 15 ++++++- events/pyproject.toml | 8 ++++ events/static/app.js | 16 +++++++- events/templates/form.html | 15 +++++++ events/templates/stats.html | 17 ++++++-- events/utils.py | 28 +++++++++++++ 8 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 events/pyproject.toml create mode 100644 events/utils.py diff --git a/.gitignore b/.gitignore index 78dd9bd..a8cec41 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules public package-lock.json events/.venv +data.json *.log *.lock diff --git a/events/app.py b/events/app.py index f2180af..8831471 100644 --- a/events/app.py +++ b/events/app.py @@ -1,75 +1,62 @@ -from flask import Flask, request, render_template +from datetime import datetime import json from pathlib import Path +from flask import Flask, request, render_template + from form_content import FormContent +from utils import get_data_stats app = Flask(__name__) -save_path = Path('data.txt') -all_keys = set(FormContent.times | FormContent.topics | FormContent.languages) | { 'name', 'email', 'topic-other' } +save_path = Path('data.json') +data_cache = { entry['email']: entry for entry in json.loads(save_path.read_text()) }\ + if save_path.exists() else {} +stats_cache = get_data_stats(data_cache) -def get_data_stats() -> dict: - data = {} - summed = { - key: { - 'sum': 0, - 'total': 0, - 'percent': 0 - } for key in all_keys - } - summed['topic-other'] = set() +MAX_FILE_SIZE = 1_000_000 +MAX_REQUEST_LENGTH = 1_000 - if save_path.exists(): - for raw_line in save_path.read_text().split('\n'): - if raw_line: - line = json.loads(raw_line) - data[line['email']] = line - for line in data.values(): - for key, value in dict.items(line): - if key == 'topic-other' and value is not None: - summed['topic-other'] = summed['topic-other'] | { value } - if type(value) is bool: - sum = summed[key]['sum'] if key in summed else 0 - sum = sum + 1 if value is True else sum - total = summed[key]['total'] + 1 if key in summed else 1 - summed[key] = { - 'sum': sum, - 'total': total, - 'percent': f'{((sum / total) * 100):.1f}' - } - summed['topic-other'] = list(summed['topic-other']) - return summed - -stats_cache = (0, get_data_stats()) @app.route('/') def form(): - return render_template('form.html', times=FormContent.times, topics=FormContent.topics, languages=FormContent.languages) + return render_template('form.html', + times=FormContent.times, + topics=FormContent.topics, + background=FormContent.background, + languages=FormContent.languages) + @app.route('/submit', methods=['POST']) def submit(): - if save_path.exists() and save_path.stat().st_size > 1_000_000: + if save_path.exists() and save_path.stat().st_size > MAX_FILE_SIZE: return { 'error': 'Database full.' }, 500 - if len(request.data) > 1000: + if len(request.data) > MAX_REQUEST_LENGTH: return { 'error': 'Request too long.' }, 400 data = request.get_json() - if set(data) != all_keys: + if set(data) != FormContent.all_keys: return { 'error': 'Invalid request format.' }, 400 - with save_path.open(mode='a') as file: - file.write(f'{json.dumps(data)}\n') + data['date_submitted'] = datetime.strftime(datetime.now(), '%Y/%m/%d, %H:%M:%S') + + data_cache[data['email']] = data + save_path.write_text(json.dumps(list(data_cache.values()), indent='\t')) + + global stats_cache + stats_cache = get_data_stats(data_cache) return {}, 200 + @app.route('/stats') def stats(): - global stats_cache - if save_path.exists() and save_path.stat().st_mtime > stats_cache[0]: - stats_cache = (save_path.stat().st_mtime, get_data_stats()) - return render_template('stats.html', times=FormContent.times, topics=FormContent.topics, - languages=FormContent.languages, data=stats_cache[1]) + return render_template('stats.html', + times=FormContent.times, + topics=FormContent.topics, + background=FormContent.background, + languages=FormContent.languages, + data=stats_cache) if __name__ == '__main__': - app.run(host='0.0.0.0', port=9700, debug=False) \ No newline at end of file + app.run(host='0.0.0.0', port=9700, debug=False) diff --git a/events/form_content.py b/events/form_content.py index 92d4741..f68d7c1 100644 --- a/events/form_content.py +++ b/events/form_content.py @@ -15,7 +15,20 @@ class FormContent: 'topic-ai': 'AI / Deep Learning libraries ディープラーニングのライブラリー (Pytorch, Tensorflow, etc.) ', 'topic-web-dev': 'Web Development ウェブ開発' } + background = { + 'background-business': 'Business / Sales 営業', + 'background-manager': 'Manager マネージャー', + 'background-engineer': 'Engineer エンジニア', + 'background-other': 'Other その他' + } languages = { 'language-english': 'English 英語', - 'language-japanese': 'Japanese 日本語', + 'language-japanese': 'Japanese 日本語' + } + + all_keys = set(times | topics | background | languages) | { + 'name', + 'email', + 'topic-other', + 'presentation-interest' } diff --git a/events/pyproject.toml b/events/pyproject.toml new file mode 100644 index 0000000..9d27ec0 --- /dev/null +++ b/events/pyproject.toml @@ -0,0 +1,8 @@ +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +preview = true +select = ["A", "ARG", "B", "C", "E", "F", "FURB", "G", "I","ICN", "ISC", "PERF", "PIE", "PL", "PLE", "PTH", + "Q", "RET", "RSE", "RUF", "SLF", "SIM", "T20", "TCH", "UP", "W"] +ignore = ["E201", "E202", "FURB140", "I001", "PERF203", "PLW0603", "Q000", "RET502", "RET503", "RUF012"] \ No newline at end of file diff --git a/events/static/app.js b/events/static/app.js index e7164e2..d122445 100644 --- a/events/static/app.js +++ b/events/static/app.js @@ -35,7 +35,7 @@ const send = () => { const times = [...document.querySelectorAll('[id^=time-]')]?.reduce((entries, checkbox) => { return { ...entries, [checkbox.id]: checkbox.checked } }, {}) - if (Object.values(times).every(time => time == false)) { + if (Object.values(times).every(item => item == false)) { error.style.display = 'block' error.innerHTML = 'Please select at least one convenient time. 都合の時間を一つ以上を選択してください。' return @@ -44,12 +44,21 @@ const send = () => { const topics = [...document.querySelectorAll('[id^=topic-]')]?.reduce((entries, checkbox) => { return { ...entries, [checkbox.id]: checkbox.checked } }, {}) - if (Object.values(topics).every(time => time == false)) { + if (Object.values(topics).every(item => item == false)) { error.style.display = 'block' error.innerHTML = 'Please select at least one topic of interest. 興味あるテーマを一つ以上を選択してください。' return } + const background = [...document.querySelectorAll('[id^=background-]')]?.reduce((entries, checkbox) => { + return { ...entries, [checkbox.id]: checkbox.checked } + }, {}) + if (Object.values(background).every(item => item == false)) { + error.style.display = 'block' + error.innerHTML = 'Please select at least one background. 履歴・業種を一つ以上を選択してください。' + return + } + const otherText = document.getElementById('other-text') if (topics['topic-other'] && !otherText.value.trim()) { error.style.display = 'block' @@ -58,12 +67,15 @@ const send = () => { } const language = document.getElementById('language') + const presentationInterest = document.getElementById('presentation-interest') const data = { name: name.value, email: email.value, ...times, ...topics, + ...background, 'topic-other': topics['topic-other'] ? otherText.value : null, + 'presentation-interest': presentationInterest.value, 'language-english': language.value == 'english' || language.value == 'either', 'language-japanese': language.value == 'japanese' || language.value == 'either', } diff --git a/events/templates/form.html b/events/templates/form.html index e307b16..ae6bfb7 100644 --- a/events/templates/form.html +++ b/events/templates/form.html @@ -57,6 +57,21 @@ +
+ + +
+ +
+

What is your professional background? 履歴・業種を教えてください。

+ {% for id, name in background.items() %} +
+ + +
+ {% endfor %} +
+

What language would you prefer? どちらの言語がご希望ですか?