forked from mrlan/EnglishPal
Compare commits
73 Commits
master
...
bug359-zha
Author | SHA1 | Date |
---|---|---|
YAKUBU ABDULAI | 8127ddcea5 | |
YAKUBU ABDULAI | f6c7928fc9 | |
YAKUBU ABDULAI | 0ff73b2213 | |
YAKUBU ABDULAI | c3e01b20ce | |
YAKUBU ABDULAI | 8a900404e7 | |
YAKUBU ABDULAI | 90d64ff447 | |
YAKUBU ABDULAI | 6b64eca9e3 | |
YAKUBU ABDULAI | 303a792c76 | |
YAKUBU ABDULAI | b15df83df4 | |
YAKUBU ABDULAI | 4fc557aff3 | |
Benedict Gbalawoe | cfa0329701 | |
Benedict Gbalawoe | 36c100b1a8 | |
Lan Hui | f3d609c92b | |
王梓铭 | 15bb925024 | |
Lan Hui | 688ed72473 | |
吴宇涵 | 1f150fc847 | |
王梓铭 | 7107f634c2 | |
王梓铭 | 6f1dd13419 | |
Hui Lan | 4417cf7017 | |
王梓铭 | 0c16a4dc6f | |
王梓铭 | 5b2f5199a8 | |
Lan Hui | 0ce1c6eb6e | |
Lan Hui | d4ac709385 | |
Lan Hui | 9eb5210d3f | |
Lan Hui | 0e25737381 | |
Lan Hui | b3b154a24f | |
Lan Hui | 4d99405bfa | |
Lan Hui | 8d8b9197b6 | |
Lan Hui | 3bc61a602f | |
Hui Lan | fb6d0b23ce | |
Hui Lan | c6010ccbbd | |
吴宇涵 | f17995a35c | |
吴宇涵 | ce28a5bf65 | |
吴宇涵 | 99aa4e0990 | |
Hui Lan | a220450b03 | |
Hui Lan | 7eb276937a | |
Hui Lan | b97210a9e0 | |
Hui Lan | e27985127a | |
吴宇涵 | 7941e5d1eb | |
吴宇涵 | 2cf65123e9 | |
吴宇涵 | 3e35679a91 | |
吴宇涵 | 82896de336 | |
吴宇涵 | 13ccbaf25c | |
吴宇涵 | ec6a2249ae | |
吴宇涵 | bdda754af6 | |
吴宇涵 | 52025d55bc | |
吴宇涵 | 5cffa1fada | |
王梓铭 | c9bfa08658 | |
王梓铭 | 6df25c58b4 | |
Hui Lan | 2909b4d973 | |
Hui Lan | 9075fe9eea | |
Hui Lan | 691c5b0d43 | |
吴宇涵 | 44db2218c1 | |
吴宇涵 | 48de496caa | |
吴宇涵 | 1015704e23 | |
吴宇涵 | cabf6702a7 | |
Lan Hui | b34f260d98 | |
Lan Hui | 70d44fcf5c | |
吴宇涵 | b80fbc936c | |
吴宇涵 | ade10e5843 | |
吴宇涵 | 13d8977636 | |
吴宇涵 | e2b165ada8 | |
吴宇涵 | df82748518 | |
吴宇涵 | 376ef9bcbc | |
吴宇涵 | 90db6534ab | |
王梓铭 | 944c931c9b | |
王梓铭 | cb0132fd31 | |
王梓铭 | 93390374ad | |
yugaoxiang | 43bd0bd09d | |
yugaoxiang | 0209548896 | |
徐嘉辉 | 74eccfbebd | |
徐嘉辉 | c76d4f21ec | |
keli_Chuang | 40c86b2050 |
|
@ -10,3 +10,4 @@ app/static/frequency/frequency.p
|
||||||
app/static/wordfreqapp.db
|
app/static/wordfreqapp.db
|
||||||
app/static/donate-the-author.jpg
|
app/static/donate-the-author.jpg
|
||||||
app/static/donate-the-author-hidden.jpg
|
app/static/donate-the-author-hidden.jpg
|
||||||
|
app/model/__pycache__/
|
|
@ -182,6 +182,7 @@ Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=215
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 丁锐
|
### 丁锐
|
||||||
|
|
||||||
修复了以下漏洞
|
修复了以下漏洞
|
||||||
|
@ -192,3 +193,4 @@ Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=489
|
||||||
|
|
||||||
|
|
||||||
*Last modified on 2023-01-30*
|
*Last modified on 2023-01-30*
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
from WordFreq import WordFreq
|
from WordFreq import WordFreq
|
||||||
from wordfreqCMD import youdao_link, sort_in_descending_order
|
from wordfreqCMD import youdao_link, sort_in_descending_order
|
||||||
from UseSqlite import InsertQuery, RecordQuery
|
from UseSqlite import InsertQuery, RecordQuery
|
||||||
|
@ -32,12 +33,17 @@ def get_article_body(s):
|
||||||
return '\n'.join(lst)
|
return '\n'.join(lst)
|
||||||
|
|
||||||
|
|
||||||
def get_today_article(user_word_list, articleID):
|
def get_today_article(user_word_list, existing_articles):
|
||||||
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
if articleID == None:
|
if existing_articles is None:
|
||||||
|
existing_articles = {
|
||||||
|
"index" : 0, # 为 article_ids 的索引
|
||||||
|
"article_ids": [] # 之前显示文章的id列表,越后越新
|
||||||
|
}
|
||||||
|
if existing_articles["index"] > len(existing_articles["article_ids"])-1:
|
||||||
rq.instructions("SELECT * FROM article")
|
rq.instructions("SELECT * FROM article")
|
||||||
else:
|
else:
|
||||||
rq.instructions('SELECT * FROM article WHERE article_id=%d' % (articleID))
|
rq.instructions('SELECT * FROM article WHERE article_id=%d' % (existing_articles["article_ids"][existing_articles["index"]]))
|
||||||
rq.do()
|
rq.do()
|
||||||
result = rq.get_results()
|
result = rq.get_results()
|
||||||
random.shuffle(result)
|
random.shuffle(result)
|
||||||
|
@ -47,36 +53,41 @@ def get_today_article(user_word_list, articleID):
|
||||||
d2 = load_freq_history(path_prefix + 'static/words_and_tests.p')
|
d2 = load_freq_history(path_prefix + 'static/words_and_tests.p')
|
||||||
d3 = get_difficulty_level(d1, d2)
|
d3 = get_difficulty_level(d1, d2)
|
||||||
|
|
||||||
d = {}
|
d = None
|
||||||
d_user = load_freq_history(user_word_list)
|
d_user = load_freq_history(user_word_list)
|
||||||
user_level = user_difficulty_level(d_user, d3) # more consideration as user's behaviour is dynamic. Time factor should be considered.
|
user_level = user_difficulty_level(d_user, d3) # more consideration as user's behaviour is dynamic. Time factor should be considered.
|
||||||
random.shuffle(result) # shuffle list
|
text_level = 0
|
||||||
d = random.choice(result)
|
if existing_articles["index"] > len(existing_articles["article_ids"])-1: # 下一篇
|
||||||
text_level = text_difficulty_level(d['text'], d3)
|
flag_get_article = False
|
||||||
if articleID == None:
|
|
||||||
for reading in result:
|
for reading in result:
|
||||||
text_level = text_difficulty_level(reading['text'], d3)
|
text_level = text_difficulty_level(reading['text'], d3)
|
||||||
factor = random.gauss(0.8,
|
factor = random.gauss(0.8,
|
||||||
0.1) # a number drawn from Gaussian distribution with a mean of 0.8 and a stand deviation of 1
|
0.1) # a number drawn from Gaussian distribution with a mean of 0.8 and a stand deviation of 1
|
||||||
if within_range(text_level, user_level, (8.0 - user_level) * factor):
|
if reading['article_id'] not in existing_articles["article_ids"] and within_range(text_level, user_level, (8.0 - user_level) * factor): # 新的文章之前没有出现过且符合一定范围的水平
|
||||||
d = reading
|
d = reading
|
||||||
|
existing_articles["article_ids"].append(d['article_id']) # 列表添加新的文章id;下面进行
|
||||||
|
flag_get_article = True
|
||||||
break
|
break
|
||||||
|
if not flag_get_article:
|
||||||
|
existing_articles["index"] -= 1
|
||||||
|
else: # 上一篇
|
||||||
|
d = random.choice(result)
|
||||||
|
text_level = text_difficulty_level(d['text'], d3)
|
||||||
|
|
||||||
s = '<div class="alert alert-success" role="alert">According to your word list, your level is <span class="badge bg-success">%4.2f</span> and we have chosen an article with a difficulty level of <span class="badge bg-success">%4.2f</span> for you.</div>' % (
|
today_article = None
|
||||||
user_level, text_level)
|
if d:
|
||||||
s += '<p class="text-muted">Article added on: %s</p>' % (d['date'])
|
today_article = {
|
||||||
s += '<div class="p-3 mb-2 bg-light text-dark">'
|
"user_level": '%4.2f' % user_level,
|
||||||
article_title = get_article_title(d['text'])
|
"text_level": '%4.2f' % text_level,
|
||||||
article_body = get_article_body(d['text'])
|
"date": d['date'],
|
||||||
s += '<p class="display-5">%s</p>' % (article_title)
|
"article_title": get_article_title(d['text']),
|
||||||
s += '<p class="lead"><font id="article" size=2>%s</font></p>' % (article_body)
|
"article_body": get_article_body(d['text']),
|
||||||
s += '<p><small class="text-muted">%s</small></p>' % (d['source'])
|
"source": d["source"],
|
||||||
s += '<p><b>%s</b></p>' % (get_question_part(d['question']))
|
"question": get_question_part(d['question']),
|
||||||
s = s.replace('\n', '<br/>')
|
"answer": get_answer_part(d['question'])
|
||||||
s += '%s' % (get_answer_part(d['question']))
|
}
|
||||||
s += '</div>'
|
|
||||||
session['articleID'] = d['article_id']
|
return existing_articles, today_article
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def load_freq_history(path):
|
def load_freq_history(path):
|
||||||
|
@ -116,21 +127,4 @@ def get_answer_part(s):
|
||||||
flag = 1
|
flag = 1
|
||||||
elif flag == 1:
|
elif flag == 1:
|
||||||
result.append(line)
|
result.append(line)
|
||||||
# https://css-tricks.com/snippets/javascript/showhide-element/
|
return '\n'.join(result)
|
||||||
js = '''
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
function toggle_visibility(id) {
|
|
||||||
var e = document.getElementById(id);
|
|
||||||
if(e.style.display == 'block')
|
|
||||||
e.style.display = 'none';
|
|
||||||
else
|
|
||||||
e.style.display = 'block';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
'''
|
|
||||||
html_code = js
|
|
||||||
html_code += '\n'
|
|
||||||
html_code += '<button onclick="toggle_visibility(\'answer\');">ANSWER</button>\n'
|
|
||||||
html_code += '<div id="answer" style="display:none;">%s</div>\n' % ('\n'.join(result))
|
|
||||||
return html_code
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
import hashlib
|
import hashlib
|
||||||
import string
|
import string
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
@ -96,9 +97,9 @@ class UserName:
|
||||||
if ' ' in self.username: # a user name must not include a whitespace
|
if ' ' in self.username: # a user name must not include a whitespace
|
||||||
return 'Whitespace is not allowed in the user name.'
|
return 'Whitespace is not allowed in the user name.'
|
||||||
for c in self.username: # a user name must not include special characters, except non-leading periods or underscores
|
for c in self.username: # a user name must not include special characters, except non-leading periods or underscores
|
||||||
if c in string.punctuation and c is not '.' and c is not '_':
|
if c in string.punctuation and c != '.' and c != '_':
|
||||||
return f'{c} is not allowed in the user name.'
|
return f'{c} is not allowed in the user name.'
|
||||||
if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del']:
|
if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del', 'admin']:
|
||||||
return 'You used a restricted word as your user name. Please come up with a better one.'
|
return 'You used a restricted word as your user name. Please come up with a better one.'
|
||||||
|
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
'''
|
'''
|
||||||
Yaml.py
|
Yaml.py
|
||||||
配置文件包括:
|
配置文件包括:
|
||||||
|
|
|
@ -19,21 +19,15 @@ def signup():
|
||||||
# POST方法需判断是否注册成功,再根据结果返回不同的内容
|
# POST方法需判断是否注册成功,再根据结果返回不同的内容
|
||||||
username = escape(request.form['username'])
|
username = escape(request.form['username'])
|
||||||
password = escape(request.form['password'])
|
password = escape(request.form['password'])
|
||||||
password2 = escape(request.form['password2'])
|
|
||||||
|
|
||||||
#! 添加如下代码为了过滤注册时的非法字符
|
#! 添加如下代码为了过滤注册时的非法字符
|
||||||
warn = WarningMessage(username)
|
warn = WarningMessage(username)
|
||||||
if str(warn) != 'OK':
|
if str(warn) != 'OK':
|
||||||
return str(warn)
|
return jsonify({'status': '3', 'warn': str(warn)})
|
||||||
|
|
||||||
available = check_username_availability(username)
|
available = check_username_availability(username)
|
||||||
if not available: # 用户名不可用
|
if not available: # 用户名不可用
|
||||||
flash('用户名 %s 已经被注册。' % (username))
|
return jsonify({'status': '0'})
|
||||||
return render_template('signup.html')
|
|
||||||
elif len(password.strip()) < 4: # 密码过短
|
|
||||||
return '密码过于简单。'
|
|
||||||
elif password != password2:
|
|
||||||
return '确认密码与输入密码不一致!'
|
|
||||||
else: # 添加账户信息
|
else: # 添加账户信息
|
||||||
add_user(username, password)
|
add_user(username, password)
|
||||||
verified = verify_user(username, password)
|
verified = verify_user(username, password)
|
||||||
|
@ -43,11 +37,10 @@ def signup():
|
||||||
session[username] = username
|
session[username] = username
|
||||||
session['username'] = username
|
session['username'] = username
|
||||||
session['expiry_date'] = get_expiry_date(username)
|
session['expiry_date'] = get_expiry_date(username)
|
||||||
session['articleID'] = None
|
session['existing_articles'] = None
|
||||||
return '<p>恭喜,你已成功注册, 你的用户名是 <a href="%s">%s</a>。</p>\
|
return jsonify({'status': '2'})
|
||||||
<p><a href="/%s">开始使用</a> <a href="/">返回首页</a><p/>' % (username, username, username)
|
|
||||||
else:
|
else:
|
||||||
return '用户名密码验证失败。'
|
return jsonify({'status': '1'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,13 +52,7 @@ def login():
|
||||||
'''
|
'''
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
# GET请求
|
# GET请求
|
||||||
if not session.get('logged_in'):
|
|
||||||
# 未登录,返回登录页面
|
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
else:
|
|
||||||
# 已登录,提示信息并显示登出按钮
|
|
||||||
return '你已登录 <a href="/%s">%s</a>。 登出点击<a href="/logout">这里</a>。' % (
|
|
||||||
session['username'], session['username'])
|
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
# POST方法用于判断登录是否成功
|
# POST方法用于判断登录是否成功
|
||||||
# check database and verify user
|
# check database and verify user
|
||||||
|
@ -79,10 +66,10 @@ def login():
|
||||||
session['username'] = username
|
session['username'] = username
|
||||||
user_expiry_date = get_expiry_date(username)
|
user_expiry_date = get_expiry_date(username)
|
||||||
session['expiry_date'] = user_expiry_date
|
session['expiry_date'] = user_expiry_date
|
||||||
session['articleID'] = None
|
session['existing_articles'] = None
|
||||||
return redirect(url_for('user_bp.userpage', username=username))
|
return jsonify({'status': '1'})
|
||||||
else:
|
else:
|
||||||
return '无法通过验证。'
|
return jsonify({'status': '0'})
|
||||||
|
|
||||||
|
|
||||||
@accountService.route("/logout", methods=['GET', 'POST'])
|
@accountService.route("/logout", methods=['GET', 'POST'])
|
||||||
|
@ -115,31 +102,9 @@ def reset():
|
||||||
# POST请求用于提交修改后信息
|
# POST请求用于提交修改后信息
|
||||||
old_password = escape(request.form['old-password'])
|
old_password = escape(request.form['old-password'])
|
||||||
new_password = escape(request.form['new-password'])
|
new_password = escape(request.form['new-password'])
|
||||||
|
|
||||||
re_new_password = escape(request.form['re-new-password']) # 确认新密码
|
|
||||||
if re_new_password != new_password: #验证新密码两次输入是否相同
|
|
||||||
return '新密码不匹配,请重新输入'
|
|
||||||
if len(new_password) < 4: #验证新密码长度,原则参照注册模块
|
|
||||||
return '密码过于简单。(密码长度至少4位)'
|
|
||||||
|
|
||||||
flag = change_password(username, old_password, new_password) # flag表示是否修改成功
|
flag = change_password(username, old_password, new_password) # flag表示是否修改成功
|
||||||
if flag:
|
if flag:
|
||||||
session['logged_in'] = False
|
session['logged_in'] = False
|
||||||
return \
|
return jsonify({'status':'1'}) # 修改成功
|
||||||
'''
|
|
||||||
<script>
|
|
||||||
alert('密码修改成功,请重新登录。');
|
|
||||||
window.location.href="/login";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return \
|
return jsonify({'status':'2'}) # 修改失败
|
||||||
'''
|
|
||||||
<script>
|
|
||||||
alert('密码修改失败');
|
|
||||||
window.location.href="/reset";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
# System Library
|
||||||
|
from flask import *
|
||||||
|
|
||||||
|
# Personal library
|
||||||
|
from Yaml import yml
|
||||||
|
from model.user import *
|
||||||
|
from model.article import *
|
||||||
|
|
||||||
|
ADMIN_NAME = "lanhui" # unique admin name
|
||||||
|
_cur_page = 1 # current article page
|
||||||
|
_page_size = 5 # article sizes per page
|
||||||
|
adminService = Blueprint("admin_service", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def check_is_admin():
|
||||||
|
# 未登录,跳转到未登录界面
|
||||||
|
if not session.get("logged_in"):
|
||||||
|
return render_template("not_login.html")
|
||||||
|
|
||||||
|
# 用户名不是admin_name
|
||||||
|
if session.get("username") != ADMIN_NAME:
|
||||||
|
return "You are not admin!"
|
||||||
|
|
||||||
|
return "pass"
|
||||||
|
|
||||||
|
|
||||||
|
@adminService.route("/admin", methods=["GET"])
|
||||||
|
def admin():
|
||||||
|
is_admin = check_is_admin()
|
||||||
|
if is_admin != "pass":
|
||||||
|
return is_admin
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"admin_index.html", yml=yml, username=session.get("username")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@adminService.route("/admin/article", methods=["GET", "POST"])
|
||||||
|
def article():
|
||||||
|
global _cur_page, _page_size
|
||||||
|
|
||||||
|
is_admin = check_is_admin()
|
||||||
|
if is_admin != "pass":
|
||||||
|
return is_admin
|
||||||
|
|
||||||
|
_article_number = get_number_of_articles()
|
||||||
|
try:
|
||||||
|
_page_size = min(
|
||||||
|
max(1, int(request.args.get("size", 5))), _article_number
|
||||||
|
) # 最小的size是1
|
||||||
|
_cur_page = min(
|
||||||
|
max(1, int(request.args.get("page", 1))), _article_number // _page_size + (_article_number % _page_size > 0)
|
||||||
|
) # 最小的page是1
|
||||||
|
except ValueError:
|
||||||
|
return "page parmas must be int!"
|
||||||
|
|
||||||
|
_articles = get_page_articles(_cur_page, _page_size)
|
||||||
|
for article in _articles: # 获取每篇文章的title
|
||||||
|
article.title = article.text.split("\n")[0]
|
||||||
|
article.content = '<br/>'.join(article.text.split("\n")[1:])
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"article_number": _article_number,
|
||||||
|
"text_list": _articles,
|
||||||
|
"page_size": _page_size,
|
||||||
|
"cur_page": _cur_page,
|
||||||
|
"username": session.get("username"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _update_context():
|
||||||
|
article_len = get_number_of_articles()
|
||||||
|
context["article_number"] = article_len
|
||||||
|
context["text_list"] = get_page_articles(_cur_page, _page_size)
|
||||||
|
_articles = get_page_articles(_cur_page, _page_size)
|
||||||
|
for article in _articles: # 获取每篇文章的title
|
||||||
|
article.title = article.text.split("\n")[0]
|
||||||
|
context["text_list"] = _articles
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
try:
|
||||||
|
delete_id = int(request.args.get("delete_id", 0))
|
||||||
|
except:
|
||||||
|
return "Delete article ID must be int!"
|
||||||
|
if delete_id: # delete article
|
||||||
|
delete_article_by_id(delete_id)
|
||||||
|
_update_context()
|
||||||
|
elif request.method == "POST":
|
||||||
|
data = request.form
|
||||||
|
content = data.get("content", "")
|
||||||
|
source = data.get("source", "")
|
||||||
|
question = data.get("question", "")
|
||||||
|
level = data.get("level", "4")
|
||||||
|
if content:
|
||||||
|
try: # check level
|
||||||
|
if level not in ['1', '2', '3', '4']:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
return "Level must be between 1 and 4."
|
||||||
|
add_article(content, source, level, question)
|
||||||
|
_update_context()
|
||||||
|
title = content.split('\n')[0]
|
||||||
|
flash(f'Article added. Title: {title}')
|
||||||
|
return render_template("admin_manage_article.html", **context)
|
||||||
|
|
||||||
|
|
||||||
|
@adminService.route("/admin/user", methods=["GET", "POST"])
|
||||||
|
def user():
|
||||||
|
is_admin = check_is_admin()
|
||||||
|
if is_admin != "pass":
|
||||||
|
return is_admin
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"user_list": get_users(),
|
||||||
|
"username": session.get("username"),
|
||||||
|
}
|
||||||
|
if request.method == "POST":
|
||||||
|
data = request.form
|
||||||
|
username = data.get("username","")
|
||||||
|
new_password = data.get("new_password", "")
|
||||||
|
expiry_time = data.get("expiry_time", "")
|
||||||
|
if username:
|
||||||
|
if new_password:
|
||||||
|
update_password_by_username(username, new_password)
|
||||||
|
flash(f'Password updated to {new_password}')
|
||||||
|
if expiry_time:
|
||||||
|
update_expiry_time_by_username(username, "".join(expiry_time.split("-")))
|
||||||
|
flash(f'Expiry date updated to {expiry_time}.')
|
||||||
|
return render_template("admin_manage_user.html", **context)
|
||||||
|
|
||||||
|
|
||||||
|
@adminService.route("/admin/expiry", methods=["GET"])
|
||||||
|
def user_expiry_time():
|
||||||
|
is_admin = check_is_admin()
|
||||||
|
if is_admin != "pass":
|
||||||
|
return is_admin
|
||||||
|
|
||||||
|
username = request.args.get("username", "")
|
||||||
|
if not username:
|
||||||
|
return "Username can't be empty."
|
||||||
|
|
||||||
|
user = get_user_by_username(username)
|
||||||
|
if not user:
|
||||||
|
return "User does not exist."
|
||||||
|
|
||||||
|
return user.expiry_date
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
###########################################################################
|
###########################################################################
|
||||||
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
# Written permission must be obtained from the author for commercial uses.
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
|
17
app/main.py
17
app/main.py
|
@ -5,24 +5,24 @@
|
||||||
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
# Written permission must be obtained from the author for commercial uses.
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
from flask import escape
|
from flask import escape
|
||||||
from Login import *
|
from Login import *
|
||||||
from Article import *
|
from Article import *
|
||||||
import Yaml
|
import Yaml
|
||||||
from user_service import userService
|
from user_service import userService
|
||||||
from account_service import accountService
|
from account_service import accountService
|
||||||
|
from admin_service import adminService, ADMIN_NAME
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = 'lunch.time!'
|
app.secret_key = 'lunch.time!'
|
||||||
|
|
||||||
# 将蓝图注册到Lab app
|
# 将蓝图注册到Lab app
|
||||||
app.register_blueprint(userService)
|
app.register_blueprint(userService)
|
||||||
app.register_blueprint(accountService)
|
app.register_blueprint(accountService)
|
||||||
|
app.register_blueprint(adminService)
|
||||||
|
|
||||||
path_prefix = '/var/www/wordfreq/wordfreq/'
|
path_prefix = '/var/www/wordfreq/wordfreq/'
|
||||||
path_prefix = './' # comment this line in deployment
|
path_prefix = './' # comment this line in deployment
|
||||||
|
|
||||||
|
|
||||||
def get_random_image(path):
|
def get_random_image(path):
|
||||||
'''
|
'''
|
||||||
返回随机图
|
返回随机图
|
||||||
|
@ -39,8 +39,7 @@ def get_random_ads():
|
||||||
返回随机广告
|
返回随机广告
|
||||||
:return: 一个广告(包含HTML标签)
|
:return: 一个广告(包含HTML标签)
|
||||||
'''
|
'''
|
||||||
ads = random.choice(['个性化分析精准提升', '你的专有单词本', '智能捕捉阅读弱点,针对性提高你的阅读水平'])
|
return random.choice(['个性化分析精准提升', '你的专有单词本', '智能捕捉阅读弱点,针对性提高你的阅读水平'])
|
||||||
return ads + '。 <a href="/signup">试试</a>吧!'
|
|
||||||
|
|
||||||
|
|
||||||
def appears_in_test(word, d):
|
def appears_in_test(word, d):
|
||||||
|
@ -98,9 +97,13 @@ def mainpage():
|
||||||
d = load_freq_history(path_prefix + 'static/frequency/frequency.p')
|
d = load_freq_history(path_prefix + 'static/frequency/frequency.p')
|
||||||
d_len = len(d)
|
d_len = len(d)
|
||||||
lst = sort_in_descending_order(pickle_idea.dict2lst(d))
|
lst = sort_in_descending_order(pickle_idea.dict2lst(d))
|
||||||
return render_template('mainpage_get.html', random_ads=random_ads, number_of_essays=number_of_essays,
|
return render_template('mainpage_get.html',
|
||||||
d_len=d_len, lst=lst, yml=Yaml.yml)
|
admin_name=ADMIN_NAME,
|
||||||
|
random_ads=random_ads,
|
||||||
|
d_len=d_len,
|
||||||
|
lst=lst,
|
||||||
|
yml=Yaml.yml,
|
||||||
|
number_of_essays=number_of_essays)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
from pony.orm import *
|
||||||
|
|
||||||
|
db = Database()
|
||||||
|
db.bind("sqlite", "../static/wordfreqapp.db", create_db=True) # bind sqlite file
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Entity):
|
||||||
|
_table_ = "user" # table name
|
||||||
|
name = PrimaryKey(str)
|
||||||
|
password = Optional(str)
|
||||||
|
start_date = Optional(str)
|
||||||
|
expiry_date = Optional(str)
|
||||||
|
|
||||||
|
|
||||||
|
class Article(db.Entity):
|
||||||
|
_table_ = "article" # table name
|
||||||
|
article_id = PrimaryKey(int, auto=True)
|
||||||
|
text = Optional(str)
|
||||||
|
source = Optional(str)
|
||||||
|
date = Optional(str)
|
||||||
|
level = Optional(str)
|
||||||
|
question = Optional(str)
|
||||||
|
|
||||||
|
|
||||||
|
db.generate_mapping(create_tables=True) # must mapping after class declaration
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
with db_session:
|
||||||
|
print(Article[2].text) # test get article which id=2 text content
|
|
@ -0,0 +1,34 @@
|
||||||
|
from model import *
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def add_article(content, source="manual_input", level="5", question="No question"):
|
||||||
|
with db_session:
|
||||||
|
# add one article to sqlite
|
||||||
|
Article(
|
||||||
|
text=content,
|
||||||
|
source=source,
|
||||||
|
date=datetime.now().strftime("%-d %b %Y"), # format style of `5 Oct 2022`
|
||||||
|
level=level,
|
||||||
|
question=question,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_article_by_id(article_id):
|
||||||
|
article_id &= 0xFFFFFFFF # max 32 bits
|
||||||
|
with db_session:
|
||||||
|
article = Article.select(article_id=article_id)
|
||||||
|
if article:
|
||||||
|
article.first().delete()
|
||||||
|
|
||||||
|
|
||||||
|
def get_number_of_articles():
|
||||||
|
with db_session:
|
||||||
|
return len(Article.select()[:])
|
||||||
|
|
||||||
|
|
||||||
|
def get_page_articles(num, size):
|
||||||
|
with db_session:
|
||||||
|
return [
|
||||||
|
x
|
||||||
|
for x in Article.select().order_by(desc(Article.article_id)).page(num, size)
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
from model import *
|
||||||
|
from Login import md5
|
||||||
|
|
||||||
|
def get_users():
|
||||||
|
with db_session:
|
||||||
|
return User.select().order_by(User.name)[:]
|
||||||
|
|
||||||
|
def get_user_by_username(username):
|
||||||
|
with db_session:
|
||||||
|
user = User.select(name=username)
|
||||||
|
if user:
|
||||||
|
return user.first()
|
||||||
|
|
||||||
|
def update_password_by_username(username, password="123456"):
|
||||||
|
with db_session:
|
||||||
|
user = User.select(name=username)
|
||||||
|
if user:
|
||||||
|
user.first().password = md5(username + password)
|
||||||
|
|
||||||
|
def update_expiry_time_by_username(username, expiry_time="20230323"):
|
||||||
|
with db_session:
|
||||||
|
user = User.select(name=username)
|
||||||
|
if user:
|
||||||
|
user.first().expiry_date = expiry_time
|
|
@ -68,7 +68,7 @@ def save_frequency_to_pickle(d, pickle_fname):
|
||||||
d2 = {}
|
d2 = {}
|
||||||
for k in d:
|
for k in d:
|
||||||
if not k in exclusion_lst and not k.isnumeric() and not len(k) < 2:
|
if not k in exclusion_lst and not k.isnumeric() and not len(k) < 2:
|
||||||
d2[k] = list(sorted(set(d[k])))
|
d2[k] = list(sorted(d[k])) # This code sorts the values of the dictionary 'd' associated with the key 'k' and assigns the resulting sorted list to the key 'k' in the dictionary 'd2'. However, it does not remove duplicates from the original list, which means that it keeps track of all counts even when the same value is clicked multiple times in a row.
|
||||||
pickle.dump(d2, f)
|
pickle.dump(d2, f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
{{ yml['header'] | safe }}
|
||||||
|
{% if yml['css']['item'] %}
|
||||||
|
{% for css in yml['css']['item'] %}
|
||||||
|
<link href="{{ css }}" rel="stylesheet">
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if yml['js']['head'] %}
|
||||||
|
{% for js in yml['js']['head'] %}
|
||||||
|
<script src="{{ js }}"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="container" style="width: 800px; margin: auto; margin-top:24px;">
|
||||||
|
<nav class="navbar navbar-expand-lg bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/{{ username }}">返回 {{ username }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:24px;">
|
||||||
|
<div class="card-header">
|
||||||
|
请选择您需要的操作
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/admin/article" class="btn btn-outline-primary" type="button">管理文章</a>
|
||||||
|
<a href="/admin/user" class="btn btn-outline-primary" type="button">管理用户</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,103 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
<link href="../static/css/bootstrap.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="container" style="width: 800px; margin: auto; margin-top:24px;">
|
||||||
|
<nav class="navbar navbar-expand-lg bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/admin">前一页</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:24px;">
|
||||||
|
{% if tips %}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
{{ tips }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-content">
|
||||||
|
<h5 style="margin-top: 10px;padding-left: 10px;">录入文章</h5>
|
||||||
|
<form action="" method="post" class="container mb-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">文章内容</label>
|
||||||
|
<textarea id="content" name="content" class="form-control" rows="8" placeholder="首行是标题,后面是正文。"></textarea>
|
||||||
|
<label class="form-label">文章来源</label>
|
||||||
|
<textarea id="source" name="source" class="form-control" placeholder="推荐格式:Source: HTTP 链接。"></textarea>
|
||||||
|
<label class="form-label">文章等级</label>
|
||||||
|
<select id="level" class="form-select" name="level">
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option selected value="4">4</option>
|
||||||
|
</select>
|
||||||
|
<label class="form-label">文章问题</label>
|
||||||
|
<textarea id="question" name="question" class="form-control" rows="6" placeholder="格式:
 QUESTION
 What?

 ANSWER
 Apple. "></textarea>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="保存" class="btn btn-outline-primary">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:24px;">
|
||||||
|
<h5 style="margin-top: 10px;padding-left: 10px;">文章列表</h5>
|
||||||
|
<div class="list-group">
|
||||||
|
{% for text in text_list %}
|
||||||
|
<div class="list-group-item list-group-item-action" aria-current="true">
|
||||||
|
<div>
|
||||||
|
<a type="button" href="/admin/article?delete_id={{text.article_id}}" class="btn btn-outline-danger btn-sm">删除</a>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">{{ text.title }}</h5>
|
||||||
|
</div>
|
||||||
|
<div><small>{{ text.source }}</small></div>
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<small>Level: {{text.level }}</small>
|
||||||
|
<small>Date: {{ text.date }}</small>
|
||||||
|
</div>
|
||||||
|
{{ text.content | safe }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin:20px 0;">
|
||||||
|
<ul class="pagination pagination-sm justify-content-center">
|
||||||
|
<li class="page-item"><a class="page-link" href="/admin/article?page={{ cur_page - 1 }}&size={{ page_size }}">Previous</a>
|
||||||
|
</li>
|
||||||
|
{% for i in range(1, article_number // page_size + (article_number % page_size > 0) + 1) %}
|
||||||
|
{% if cur_page == i %}
|
||||||
|
<li class="page-item active"><a class="page-link" href="/admin/article?page={{ i }}&size={{ page_size }}">{{ i }}</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item"><a class="page-link" href="/admin/article?page={{ i }}&size={{ page_size }}">{{ i }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<li class="page-item"><a class="page-link" href="/admin/article?page={{ cur_page + 1 }}&size={{ page_size }}">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,99 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
<link href="../static/css/bootstrap.css" rel="stylesheet">
|
||||||
|
<script src="../static/js/jquery.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="container" style="width: 800px; margin: auto; margin-top:24px;">
|
||||||
|
<nav class="navbar navbar-expand-lg bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/admin">前一页</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:24px;">
|
||||||
|
<h5 style="margin-top: 10px;padding-left: 10px;">重置选中用户的信息</h5>
|
||||||
|
<form id="user_form" action="" method="post" class="container mb-3">
|
||||||
|
<div>
|
||||||
|
<label class="form-label" style="padding-top: 10px;">用户</label>
|
||||||
|
<select onchange="loadUserExpiryDate()" id="username" name="username" class="form-select" aria-label="Default select example">
|
||||||
|
<option selected>选择用户</option>
|
||||||
|
{% for user in user_list %}
|
||||||
|
<option value="{{ user.name }}">{{ user.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label class="form-label" style="padding-top: 10px;">修改密码</label>
|
||||||
|
<div>
|
||||||
|
<button type="button" id="reset_pwd_btn" class="btn btn-outline-success">获取12位随机密码</button>
|
||||||
|
<input style="margin-left: 20px;border: 0; font-size: 20px;" name="new_password"
|
||||||
|
id="new_password"></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="form-label" style="padding-top: 10px;">过期时间</label>
|
||||||
|
<div>
|
||||||
|
<input type="date" id="expiry_date" name="expiry_time" placeholder="YYYY-MM-DD" pattern="yyyyMMdd">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button style="margin-top: 50px;" type="submit" class="btn btn-primary">更新用户信息</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 密码生成器
|
||||||
|
function generatePassword(length) {
|
||||||
|
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+~`|}{[]\:;?><,./-=";
|
||||||
|
var password = "";
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
password += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||||
|
}
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
document.getElementById("reset_pwd_btn").addEventListener("click", () => {
|
||||||
|
// 生成12位随机密码
|
||||||
|
let pwd = generatePassword(12)
|
||||||
|
document.getElementById("new_password").value = pwd
|
||||||
|
})
|
||||||
|
// 选择用户后更新其过期时间
|
||||||
|
function loadUserExpiryDate() {
|
||||||
|
const cur_user = $('#username').val();
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: `/admin/expiry?username=${cur_user}`,
|
||||||
|
success: function(resp) {
|
||||||
|
const year = resp.substr(0,4);
|
||||||
|
const month = resp.substr(4,2);
|
||||||
|
const day = resp.substr(6,2);
|
||||||
|
document.getElementById("expiry_date").value = year + '-' + month + '-' + day
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
|
@ -1,28 +1,47 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% if session['logged_in'] %}
|
{% if session['logged_in'] %}
|
||||||
|
|
||||||
You're logged in already!
|
你已登录 <a href="/{{ session['username'] }}">{{ session['username'] }}</a>。 登出点击<a href="/logout">这里</a>。
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
||||||
<link rel="stylesheet" href="static/css/login_service.css">
|
<link rel="stylesheet" href="static/css/login_service.css">
|
||||||
|
<script src="static/js/jquery.js"></script>
|
||||||
|
<script>
|
||||||
|
function login(){
|
||||||
|
let username = $("#username").val();
|
||||||
|
let password = $("#password").val();
|
||||||
|
if (username === "" || password === ""){
|
||||||
|
alert('输入不能为空!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$.post(
|
||||||
|
"/login", {'username': username, 'password': password},
|
||||||
|
function (response) {
|
||||||
|
if (response.status === '0') {
|
||||||
|
alert('无法通过验证。');
|
||||||
|
window.location.href = "/login";
|
||||||
|
} else if (response.status === '1') {
|
||||||
|
window.location.href = "/"+username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<section class="signin-heading">
|
<section class="signin-heading">
|
||||||
<h1>Sign In</h1>
|
<h1>Sign In</h1>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<form action="/login" method="POST">
|
<input type="text" placeholder="用户名" class="username" id="username">
|
||||||
<input type="text" placeholder="用户名" class="username" name="username" required>
|
<input type="password" placeholder="密码" class="password" id="password">
|
||||||
<input type="password" placeholder="密码" class="password" name="password" required>
|
<button type="button" class="btn" onclick="login()">登录</button>
|
||||||
<button type="submit" class="btn">登录</button>
|
<a class="signup" href="/signup">注册</a>
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="/signup" class="signup">注册</a>
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,13 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<p><b><font size="+3" color="red">English Pal - Learn English smartly!</font></b></p>
|
<p><b><font size="+3" color="red">English Pal - Learn English smartly!</font></b></p>
|
||||||
{% if session['logged_in'] %}
|
{% if session['logged_in'] %}
|
||||||
<a href="/{{session['username']}}">{{session['username']}}</a></p>
|
<a href="/{{ session['username'] }}">{{ session['username'] }}</a>
|
||||||
|
{% if session['username'] == admin_name %}
|
||||||
|
<a href="/admin">管理</a></p>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p><a href="/login">登录</a> <a href="/signup">注册</a> <a href="/static/usr/instructions.html">使用说明</a></p >
|
<p><a href="/login">登录</a> <a href="/signup">注册</a> <a href="/static/usr/instructions.html">使用说明</a></p >
|
||||||
<p><b>{{random_ads|safe}}</b></p>
|
<p><b> {{ random_ads }}。 <a href="/signup">试试</a>吧!</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span> 篇</div>
|
<div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span> 篇</div>
|
||||||
<p>粘贴1篇文章 (English only)</p>
|
<p>粘贴1篇文章 (English only)</p>
|
||||||
|
|
|
@ -2,6 +2,38 @@
|
||||||
<meta charset="utf-8" name="viewport"
|
<meta charset="utf-8" name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes"/>
|
content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes"/>
|
||||||
<link rel="stylesheet" href="static/css/login_service.css">
|
<link rel="stylesheet" href="static/css/login_service.css">
|
||||||
|
<script src="static/js/jquery.js"></script>
|
||||||
|
<script>
|
||||||
|
function reset() {
|
||||||
|
let old_password = $("#old-password").val();
|
||||||
|
let new_password = $("#new-password").val();
|
||||||
|
let re_new_password = $("#re-new-password").val();
|
||||||
|
if (old_password === "" || new_password === "" || re_new_password === ""){
|
||||||
|
alert('输入不能为空!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (new_password !== re_new_password) {
|
||||||
|
alert('新密码不匹配,请重新输入');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (new_password.length < 4) {
|
||||||
|
alert('密码过于简单。(密码长度至少4位)');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$.post("/reset", {'old-password': old_password, 'new-password': new_password},
|
||||||
|
function (response) {
|
||||||
|
if (response.status === '1') {
|
||||||
|
alert('密码修改成功,请重新登录。');
|
||||||
|
window.location.href = "/login";
|
||||||
|
} else if (response.status === '2') {
|
||||||
|
alert('密码修改失败');
|
||||||
|
window.location.href = "/reset";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
|
@ -9,14 +41,11 @@
|
||||||
<h1>Reset Password</h1>
|
<h1>Reset Password</h1>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<form action="/reset" method="POST">
|
<input type="password" placeholder="原密码" class="old-password" name="old-password" id="old-password"/>
|
||||||
<input type="password" placeholder="原密码" class="old-password" name="old-password" required>
|
<input type="password" placeholder="新密码" class="new-password" name="new-password" id="new-password"/>
|
||||||
<input type="password" placeholder="新密码" class="new-password" name="new-password" required>
|
<input type="password" placeholder="确认新密码" class="re-new-password" name="re-new-password" id="re-new-password"/>
|
||||||
<input type="password" placeholder="确认新密码" class="re-new-password" name="re-new-password" required>
|
<button id="submit" class="btn" onclick="reset()">提交</button>
|
||||||
<input type="submit" name="submit" class="btn" value="提交"/>
|
<button class="btn" onclick="window.location.href='/{{ username }}'">放弃修改</button>
|
||||||
<input type="button" name="submit" class="btn" value="放弃修改"
|
|
||||||
onclick="window.location.href='/{{ username }}'"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -6,6 +6,47 @@ You're logged in already! <a href="/logout">Logout</a>.
|
||||||
{% else %}
|
{% else %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
||||||
<link rel="stylesheet" href="static/css/login_service.css">
|
<link rel="stylesheet" href="static/css/login_service.css">
|
||||||
|
<script src="static/js/jquery.js"></script>
|
||||||
|
<script>
|
||||||
|
function signup() {
|
||||||
|
let username = $("#username").val();
|
||||||
|
let password = $("#password").val();
|
||||||
|
let password2 = $("#password2").val();
|
||||||
|
if (username === "" || password === "" || password2 === ""){
|
||||||
|
alert('输入不能为空!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (password !== password2) {
|
||||||
|
alert('确认密码与输入密码不一致!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (password.length < 4) {
|
||||||
|
alert('密码过于简单。(密码长度至少4位)');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$.post("/signup", {'username': username, 'password': password},
|
||||||
|
function (response) {
|
||||||
|
if (response.status === '0') {
|
||||||
|
alert('用户名'+username+'已经被注册。');
|
||||||
|
window.location.href = "/signup";
|
||||||
|
} else if (response.status === '1') {
|
||||||
|
alert('用户名密码验证失败。');
|
||||||
|
window.location.href = "/signup";
|
||||||
|
} else if (response.status === '2') {
|
||||||
|
let f = confirm("恭喜,你已成功注册,你的用户名是"+username+'.\n点击“确认”开始使用,或点击“取消”返回首页');
|
||||||
|
if (f) {
|
||||||
|
window.location.href = '/'+username;
|
||||||
|
} else {
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
} else if (response.status === '3') {
|
||||||
|
alert(response.warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<p>{{ get_flashed_messages()[0] | safe }}</p>
|
<p>{{ get_flashed_messages()[0] | safe }}</p>
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,12 +56,10 @@ You're logged in already! <a href="/logout">Logout</a>.
|
||||||
<h1>Sign Up</h1>
|
<h1>Sign Up</h1>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<form action="/signup" method="POST">
|
<p><input type="username" id="username" placeholder="输入用户名" class="username"></p>
|
||||||
<p><input type="username" name="username" placeholder="输入用户名" required="required" class="username"></p>
|
<p><input type="password" id="password" placeholder="输入密码" class="password"></p>
|
||||||
<p><input type="password" name="password" placeholder="输入密码" required="required" class="password"></p>
|
<p><input type="password" id="password2" placeholder="确认密码" class="password" ></p>
|
||||||
<p><input type="password" name="password2" placeholder="确认密码" required="required" class="password" ></p>
|
<button type="button" class="btn" onclick="signup()">注册</button>
|
||||||
<button type="submit" class="btn">注册</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -37,20 +37,49 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<p><b>English Pal for <font id="username" color="red">{{ username }}</font></b>
|
<p><b>English Pal for <font id="username" color="red">{{ username }}</font></b>
|
||||||
<a class="btn btn-secondary" href="/logout" role="button">退出</a>
|
{% if username == admin_name %}
|
||||||
<a class="btn btn-secondary" href="/reset" role="button">重设密码</a>
|
<a class="btn btn-secondary" href="/admin" role="button" onclick="stopRead()">管理</a>
|
||||||
|
{% endif %}
|
||||||
|
<a class="btn btn-secondary" href="/logout" role="button" onclick="stopRead()">退出</a>
|
||||||
|
<a class="btn btn-secondary" href="/reset" role="button" onclick="stopRead()">重设密码</a>
|
||||||
</p>
|
</p>
|
||||||
{{ flashed_messages|safe }}
|
{# {% for message in flashed_messages %}#} {# 根据user_service.userpage,取消了参数flashed_messages,因此注释了这段代码 #}
|
||||||
|
{# <div class="alert alert-warning" role="alert">Congratulations! {{ message }}</div>#}
|
||||||
|
{# {% endfor %}#}
|
||||||
|
|
||||||
<a class="btn btn-success" href="/{{ username }}/reset" role="button"> 下一篇 Next Article </a>
|
<a class="btn btn-success" href="/{{ username }}/reset" role="button"> 下一篇 Next Article </a>
|
||||||
{% if session.get('articleID') != session.get('old_articleID') %}
|
{% if session.get('existing_articles') != None and session.get('existing_articles')["index"] !=0 %}
|
||||||
{% if session.get('old_articleID') != None %}
|
|
||||||
<a class="btn btn-success" href="/{{ username }}/back" role="button"> 上一篇 Previous Article </a>
|
<a class="btn btn-success" href="/{{ username }}/back" role="button"> 上一篇 Previous Article </a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p><b>阅读文章并回答问题</b></p>
|
<p><b>阅读文章并回答问题</b></p>
|
||||||
<div id="text-content">{{ today_article|safe }}</div>
|
<div id="text-content">
|
||||||
|
{% if today_article %}
|
||||||
|
<div class="alert alert-success" role="alert">According to your word list, your level is <span class="badge bg-success">{{ today_article["user_level"] }}</span> and we have chosen an article with a difficulty level of <span class="badge bg-success">{{ today_article["text_level"] }}</span> for you.</div>
|
||||||
|
<p class="text-muted">Article added on: {{ today_article["date"] }}</p><br/>
|
||||||
|
<div class="p-3 mb-2 bg-light text-dark"><br/>
|
||||||
|
<p class="display-5">{{ today_article["article_title"] }}</p><br/>
|
||||||
|
<p class="lead"><font id="article" size=2>{{ today_article["article_body"] }}</font></p><br/>
|
||||||
|
<p><small class="text-muted">{{ today_article['source'] }}</small></p><br/>
|
||||||
|
<p><b>{{ today_article['question'] }}</b></p><br/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function toggle_visibility(id) { {# https://css-tricks.com/snippets/javascript/showhide-element/#}
|
||||||
|
const e = document.getElementById(id);
|
||||||
|
if(e.style.display === 'block')
|
||||||
|
e.style.display = 'none';
|
||||||
|
else
|
||||||
|
e.style.display = 'block';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<button onclick="toggle_visibility('answer');">ANSWER</button>
|
||||||
|
<div id="answer" style="display:none;">{{ today_article['answer'] }}</div><br/>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<p class="text-muted"><span class="badge bg-success">Notes:</span><br>No article is currently available for you. You can try again a few times or mark new words in the passage to improve your level.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="checkbox" onclick="toggleHighlighting()" checked/>生词高亮
|
<input type="checkbox" onclick="toggleHighlighting()" checked/>生词高亮
|
||||||
<input type="checkbox" onclick="onReadClick()" checked/>大声朗读
|
<input type="checkbox" onclick="onReadClick()" checked/>大声朗读
|
||||||
|
@ -60,7 +89,7 @@
|
||||||
<div class="sliderValue">
|
<div class="sliderValue">
|
||||||
<span id="rangeValue">1×</span>
|
<span id="rangeValue">1×</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="range" id="rangeComponent" min="0.5" max="2" value="1" step="0.25" "/>
|
<input type="range" id="rangeComponent" min="0.5" max="2" value="1" step="0.25"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p>
|
<p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from admin_service import ADMIN_NAME
|
||||||
from flask import *
|
from flask import *
|
||||||
|
|
||||||
# from app import Yaml
|
# from app import Yaml
|
||||||
|
@ -29,9 +29,10 @@ def user_reset(username):
|
||||||
:param username: 用户名
|
:param username: 用户名
|
||||||
:return: 返回页面内容
|
:return: 返回页面内容
|
||||||
'''
|
'''
|
||||||
session['old_articleID'] = session.get('articleID')
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
session['articleID'] = None
|
existing_articles = session.get("existing_articles")
|
||||||
|
existing_articles["index"] += 1
|
||||||
|
session["existing_articles"] = existing_articles
|
||||||
return redirect(url_for('user_bp.userpage', username=username))
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
else:
|
else:
|
||||||
return 'Under construction'
|
return 'Under construction'
|
||||||
|
@ -44,7 +45,9 @@ def user_back(username):
|
||||||
:return: 返回页面内容
|
:return: 返回页面内容
|
||||||
'''
|
'''
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
session['articleID'] = session.get('old_articleID')
|
existing_articles = session.get("existing_articles")
|
||||||
|
existing_articles["index"] -= 1
|
||||||
|
session["existing_articles"] = existing_articles
|
||||||
return redirect(url_for('user_bp.userpage', username=username))
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,7 +92,8 @@ def deleteword(username, word):
|
||||||
'''
|
'''
|
||||||
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
||||||
pickle_idea2.deleteRecord(user_freq_record, word)
|
pickle_idea2.deleteRecord(user_freq_record, word)
|
||||||
flash(f'<strong>{word}</strong> is no longer in your word list.')
|
# 模板userpage_get.html中删除单词是异步执行,而flash的信息后续是同步执行的,所以注释这段代码;同时如果这里使用flash但不提取信息,则会影响 signup.html的显示。bug复现:删除单词后,点击退出,点击注册,注册页面就会出现提示信息
|
||||||
|
# flash(f'{word} is no longer in your word list.')
|
||||||
return "success"
|
return "success"
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,11 +134,15 @@ def userpage(username):
|
||||||
words = ''
|
words = ''
|
||||||
for x in lst3:
|
for x in lst3:
|
||||||
words += x[0] + ' '
|
words += x[0] + ' '
|
||||||
|
existing_articles, today_article = get_today_article(user_freq_record, session.get('existing_articles'))
|
||||||
|
session['existing_articles'] = existing_articles
|
||||||
|
# 通过 today_article,加载前端的显示页面
|
||||||
return render_template('userpage_get.html',
|
return render_template('userpage_get.html',
|
||||||
|
admin_name=ADMIN_NAME,
|
||||||
username=username,
|
username=username,
|
||||||
session=session,
|
session=session,
|
||||||
flashed_messages=get_flashed_messages_if_any(),
|
# flashed_messages=get_flashed_messages(), 仅有删除单词的时候使用到flash,而删除单词是异步执行,这里的信息提示是同步执行,所以就没有存在的必要了
|
||||||
today_article=get_today_article(user_freq_record, session['articleID']),
|
today_article=today_article,
|
||||||
d_len=len(d),
|
d_len=len(d),
|
||||||
lst3=lst3,
|
lst3=lst3,
|
||||||
yml=Yaml.yml,
|
yml=Yaml.yml,
|
||||||
|
@ -173,15 +181,3 @@ def get_time():
|
||||||
'''
|
'''
|
||||||
return datetime.now().strftime('%Y%m%d%H%M') # upper to minutes
|
return datetime.now().strftime('%Y%m%d%H%M') # upper to minutes
|
||||||
|
|
||||||
def get_flashed_messages_if_any():
|
|
||||||
'''
|
|
||||||
在用户界面显示黄色提示信息
|
|
||||||
:return: 包含HTML标签的提示信息
|
|
||||||
'''
|
|
||||||
messages = get_flashed_messages()
|
|
||||||
s = ''
|
|
||||||
for message in messages:
|
|
||||||
s += '<div class="alert alert-warning" role="alert">'
|
|
||||||
s += f'Congratulations! {message}'
|
|
||||||
s += '</div>'
|
|
||||||
return s
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
###########################################################################
|
###########################################################################
|
||||||
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
# Written permission must be obtained from the author for commercial uses.
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
@ -70,7 +71,7 @@ def sort_in_ascending_order(lst):# 单词按频率降序排列
|
||||||
return lst2
|
return lst2
|
||||||
|
|
||||||
|
|
||||||
def make_html_page(lst, fname):
|
def make_html_page(lst, fname): # 只是在wordfreqCMD.py中的main函数中调用,所以不做修改
|
||||||
'''
|
'''
|
||||||
功能:把lst的信息存到fname中,以html格式。
|
功能:把lst的信息存到fname中,以html格式。
|
||||||
'''
|
'''
|
||||||
|
|
4
build.sh
4
build.sh
|
@ -3,6 +3,10 @@
|
||||||
DEPLOYMENT_DIR=/home/lanhui/englishpal2/EnglishPal
|
DEPLOYMENT_DIR=/home/lanhui/englishpal2/EnglishPal
|
||||||
cd $DEPLOYMENT_DIR
|
cd $DEPLOYMENT_DIR
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
# Stop service
|
# Stop service
|
||||||
sudo docker stop EnglishPal
|
sudo docker stop EnglishPal
|
||||||
sudo docker rm EnglishPal
|
sudo docker rm EnglishPal
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
Flask==1.1.2
|
Flask==1.1.2
|
||||||
selenium==3.141.0
|
selenium==3.141.0
|
||||||
PyYAML~=6.0
|
PyYAML~=6.0
|
||||||
|
pony==0.7.16
|
||||||
|
|
Loading…
Reference in New Issue