Created webapp for reviewing WaniKani burned items.
This commit is contained in:
commit
c6b94ee31c
110
.gitignore
vendored
Normal file
110
.gitignore
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
## Directory-based project format:
|
||||||
|
.idea/
|
||||||
|
# if you remove the above rule, at least ignore the following:
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
# .idea/workspace.xml
|
||||||
|
# .idea/tasks.xml
|
||||||
|
# .idea/dictionaries
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
# .idea/dataSources.ids
|
||||||
|
# .idea/dataSources.xml
|
||||||
|
# .idea/sqlDataSources.xml
|
||||||
|
# .idea/dynamic.xml
|
||||||
|
# .idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
# .idea/gradle.xml
|
||||||
|
# .idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
# .idea/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
### Python template
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
wanikani.db
|
||||||
|
config.py
|
BIN
static/burned.png
Normal file
BIN
static/burned.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
39
static/style.css
Normal file
39
static/style.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.centered {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left:50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.padded {
|
||||||
|
padding: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.correct{
|
||||||
|
background-color: greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrong{
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-size{
|
||||||
|
font-size: 1000%;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-size{
|
||||||
|
font-size: 200%;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-size{
|
||||||
|
font-size: 200%;
|
||||||
|
color: white;
|
||||||
|
}
|
1
static/wanakana.min.js
vendored
Normal file
1
static/wanakana.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
127
templates/quiz.html
Normal file
127
templates/quiz.html
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
<!-- Latest compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<!-- Optional theme -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
||||||
|
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||||
|
<!-- Latest compiled and minified JavaScript -->
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||||
|
<script src="{{ url_for('static', filename='wanakana.min.js') }}"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-default">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand">
|
||||||
|
<img alt="Brand" height="24" width="24" src="{{ url_for('static', filename='burned.png')}}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li>
|
||||||
|
<button id="api-refresh" type="submit" class="btn btn-default navbar-btn" aria-label="Refresh API">
|
||||||
|
<span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="navbar-text" data-toggle="tooltip" data-placement="bottom" title="Last API Refresh: {{ current_user.last_updated }}"><span><img alt="avatar" src="http://www.gravatar.com/avatar/{{ current_user.gravatar }}?s=24"></span> {{ current_user.username }}</li>
|
||||||
|
<li><a href="/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 text-center"><p class="question-size" id="question-area"></p><img id="question-image" src=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" autocomplete=off id="kana" class="form-control answer-size">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 text-center">
|
||||||
|
<button class="btn btn-default btn-lg" id="submit-answer">Check</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div id="help-area" class="col-md-12 text-center help-size">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 text-center">
|
||||||
|
<button class="btn btn-default btn-lg" id="get-help">I Don't Know</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
var input = document.getElementById('kana');
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
refreshQuestion()
|
||||||
|
}).keypress(function(e){
|
||||||
|
if(e.which == 13){
|
||||||
|
$("#submit-answer").click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
function refreshQuestion(){
|
||||||
|
$.getJSON("/quiz_item", function(data){
|
||||||
|
$("#help-area").text('');
|
||||||
|
$("#kana").removeClass("correct").val('');
|
||||||
|
sessionStorage.setItem("question", data['question']);
|
||||||
|
sessionStorage.setItem("answer", data['answer']);
|
||||||
|
sessionStorage.setItem("item_type", data['item_type']);
|
||||||
|
$("#question-area").text('');
|
||||||
|
$("#question-image").attr("src", '');
|
||||||
|
if(sessionStorage.getItem('item_type') == 'radical'){
|
||||||
|
$("#kana").attr("placeholder", "Meaning")
|
||||||
|
document.body.style.backgroundColor = "deepskyblue";
|
||||||
|
wanakana.unbind(input);
|
||||||
|
if(sessionStorage.getItem('question').indexOf('http') >= 0){
|
||||||
|
$("#question-image").attr("src", sessionStorage.getItem('question'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#question-area").text(sessionStorage.getItem('question'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("#kana").attr("placeholder", "かな")
|
||||||
|
if(sessionStorage.getItem('item_type') == 'kanji'){
|
||||||
|
document.body.style.backgroundColor = "deeppink";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
document.body.style.backgroundColor = "darkviolet";
|
||||||
|
}
|
||||||
|
wanakana.bind(input);
|
||||||
|
$("#question-area").text(sessionStorage.getItem('question'));
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
$("#submit-answer").click(function (e){
|
||||||
|
e.preventDefault();
|
||||||
|
var input_answer = document.getElementById('kana').value;
|
||||||
|
if($("#kana").hasClass("correct")){
|
||||||
|
refreshQuestion();
|
||||||
|
}
|
||||||
|
else if(sessionStorage.getItem('answer').replace(/\s/g, '').split(/[,\.]/g).indexOf(input_answer) !== -1){
|
||||||
|
$("#kana").addClass("correct").removeClass("wrong");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("#kana").addClass("wrong").removeClass("correct").val('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#get-help").click(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
$("#help-area").text(sessionStorage.getItem('answer'));
|
||||||
|
});
|
||||||
|
$("#api-refresh").click(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
$.post("{{ url_for('refresh_api') }}")
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
52
templates/welcome.html
Normal file
52
templates/welcome.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
<!-- Latest compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<!-- Optional theme -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||||
|
<!-- Latest compiled and minified JavaScript -->
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container centered">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<img class="padded" src="{{ url_for('static', filename='burned.png')}}">
|
||||||
|
</div>
|
||||||
|
<form id="submissionform" role="form" class="col-md-8 form-group" method="POST" action="">
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
{{ form.api_key(**{'class':'form-control','placeholder':'Insert API Key'}) }}
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button id="submitbutton" class="btn btn-default" type="submit">Go!</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="col-md-8">
|
||||||
|
{% for error in form.api_key.errors %}
|
||||||
|
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<strong>{{ category }}</strong>{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
195
wanikaniburned.py
Normal file
195
wanikaniburned.py
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import json
|
||||||
|
import random
|
||||||
|
from flask import Flask, render_template, redirect, url_for, jsonify, flash
|
||||||
|
from flask.ext.login import LoginManager, login_user, login_required, current_user, logout_user, login_fresh
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
from flask.ext.wtf import Form
|
||||||
|
import requests
|
||||||
|
from wtforms import StringField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_pyfile('config.py')
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(Form):
|
||||||
|
api_key = StringField('api_key', validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
__tablename__ = 'users'
|
||||||
|
api_key = db.Column(db.String, unique=True, primary_key=True)
|
||||||
|
radicals = db.Column(db.String)
|
||||||
|
kanji = db.Column(db.String)
|
||||||
|
vocabulary = db.Column(db.String)
|
||||||
|
username = db.Column(db.String)
|
||||||
|
gravatar = db.Column(db.String)
|
||||||
|
last_updated = db.Column(db.DateTime)
|
||||||
|
|
||||||
|
def __init__(self, api_key):
|
||||||
|
self.api_key = api_key
|
||||||
|
self.last_updated = datetime.utcnow()
|
||||||
|
self.parse_radicals_and_userdata()
|
||||||
|
self.parse_kanji()
|
||||||
|
self.parse_vocabulary()
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def is_authenticated(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_anonymous(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return str(self.api_key)
|
||||||
|
|
||||||
|
def parse_radicals_and_userdata(self):
|
||||||
|
response = requests.get("https://www.wanikani.com/api/user/" + self.api_key + "/radicals/")
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get('error'):
|
||||||
|
raise ValueError(data['error']['message'])
|
||||||
|
items = []
|
||||||
|
self.username = data['user_information']['username']
|
||||||
|
self.gravatar = data['user_information']['gravatar']
|
||||||
|
if data.get('requested_information'):
|
||||||
|
for item in data['requested_information']:
|
||||||
|
if item.get('user_specific') and item['user_specific']['burned']:
|
||||||
|
items.append({'character': item['character'], 'meaning': item['meaning'], 'image': item['image']})
|
||||||
|
self.radicals = json.dumps(items)
|
||||||
|
|
||||||
|
def parse_kanji(self):
|
||||||
|
response = requests.get("https://www.wanikani.com/api/user/" + self.api_key + "/kanji/")
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get('error'):
|
||||||
|
raise ValueError(data['error']['message'])
|
||||||
|
items = []
|
||||||
|
if data.get('requested_information'):
|
||||||
|
for item in data['requested_information']:
|
||||||
|
if item.get('user_specific') and item['user_specific']['burned']:
|
||||||
|
items.append({'character': item['character'], 'meaning': item['meaning'], 'onyomi': item['onyomi'],
|
||||||
|
'kunyomi': item['kunyomi']})
|
||||||
|
self.kanji = json.dumps(items)
|
||||||
|
|
||||||
|
def parse_vocabulary(self):
|
||||||
|
response = requests.get("https://www.wanikani.com/api/user/" + self.api_key + "/vocabulary/")
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get('error'):
|
||||||
|
raise ValueError(data['error']['message'])
|
||||||
|
items = []
|
||||||
|
if data.get('requested_information'):
|
||||||
|
for item in data['requested_information']['general']:
|
||||||
|
if item.get('user_specific') and item['user_specific']['burned']:
|
||||||
|
items.append({'character': item['character'], 'meaning': item['meaning'], 'kana': item['kana']})
|
||||||
|
self.vocabulary = json.dumps(items)
|
||||||
|
|
||||||
|
def update_all(self):
|
||||||
|
self.parse_radicals_and_userdata()
|
||||||
|
self.parse_kanji()
|
||||||
|
self.parse_vocabulary()
|
||||||
|
self.last_updated = datetime.utcnow()
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(api_key):
|
||||||
|
return User.query.get(str(api_key))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/', methods=('GET', 'POST'))
|
||||||
|
def show_home():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('show_quiz'))
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.get(str(form.api_key.data))
|
||||||
|
if user:
|
||||||
|
login_user(user)
|
||||||
|
return redirect(url_for('show_quiz'))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
new_user = User(str(form.api_key.data))
|
||||||
|
login_user(new_user)
|
||||||
|
return redirect(url_for('show_quiz'))
|
||||||
|
except ValueError as err:
|
||||||
|
flash(err)
|
||||||
|
return render_template("welcome.html", form=form)
|
||||||
|
return render_template("welcome.html", form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@app.route('/quiz')
|
||||||
|
def show_quiz():
|
||||||
|
if not login_fresh():
|
||||||
|
if User.query.get(current_user.api_key):
|
||||||
|
return render_template("quiz.html")
|
||||||
|
else:
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('show_home'))
|
||||||
|
return render_template("quiz.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/quiz_item')
|
||||||
|
@login_required
|
||||||
|
def get_quiz():
|
||||||
|
choices = []
|
||||||
|
if current_user.radicals:
|
||||||
|
choices.append('radical')
|
||||||
|
if current_user.kanji:
|
||||||
|
choices.append('kanji')
|
||||||
|
if current_user.vocabulary:
|
||||||
|
choices.append('vocabulary')
|
||||||
|
selected_type = random.choice(choices)
|
||||||
|
if selected_type is 'radical':
|
||||||
|
selected_item = random.choice(json.loads(current_user.radicals))
|
||||||
|
if selected_item['image']:
|
||||||
|
return jsonify(item_type='radical', question=selected_item['image'], answer=selected_item['meaning'])
|
||||||
|
else:
|
||||||
|
return jsonify(item_type='radical', question=selected_item['character'], answer=selected_item['meaning'])
|
||||||
|
elif selected_type is 'kanji':
|
||||||
|
selected_item = random.choice(json.loads(current_user.kanji))
|
||||||
|
made_answer = ""
|
||||||
|
if selected_item['onyomi'] and selected_item['kunyomi']:
|
||||||
|
made_answer = selected_item['onyomi'] + ',' + selected_item['kunyomi'].replace('.*', '')
|
||||||
|
elif selected_item['onyomi']:
|
||||||
|
made_answer = selected_item['onyomi']
|
||||||
|
elif selected_item['kunyomi']:
|
||||||
|
made_answer = selected_item['kunyomi'].replace('.*', '')
|
||||||
|
return jsonify(item_type='kanji', question=selected_item['character'],
|
||||||
|
answer=made_answer)
|
||||||
|
else:
|
||||||
|
selected_item = random.choice(json.loads(current_user.vocabulary))
|
||||||
|
return jsonify(item_type='vocabulary', question=selected_item['character'], answer=selected_item['kana'])
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/refresh', methods=['POST'])
|
||||||
|
def refresh_api():
|
||||||
|
current_user.update_all()
|
||||||
|
return "202"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('show_home'))
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.unauthorized_handler
|
||||||
|
def unauthorized():
|
||||||
|
return redirect(url_for('show_home'))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
db.create_all()
|
||||||
|
app.debug = True
|
||||||
|
app.run()
|
Loading…
Reference in New Issue
Block a user