forked from mrlan/EnglishPal
bug209-yaaqob
commit
1d3f6a47a8
|
@ -0,0 +1,12 @@
|
||||||
|
.idea/
|
||||||
|
venv/
|
||||||
|
app/__init__.py
|
||||||
|
app/__pycache__/
|
||||||
|
app/sqlite_commands.py
|
||||||
|
app/static/usr/*.jpg
|
||||||
|
app/static/img/
|
||||||
|
app/static/frequency/frequency_*.pickle
|
||||||
|
app/static/frequency/frequency.p
|
||||||
|
app/static/wordfreqapp.db
|
||||||
|
app/static/donate-the-author.jpg
|
||||||
|
app/static/donate-the-author-hidden.jpg
|
|
@ -0,0 +1,4 @@
|
||||||
|
FROM tiangolo/uwsgi-nginx-flask:python3.6
|
||||||
|
COPY requirements.txt /app
|
||||||
|
RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
COPY ./app /app
|
|
@ -0,0 +1,41 @@
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
triggers {
|
||||||
|
|
||||||
|
pollSCM('') // Enabling being build on Push
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('MakeDatabasefile') {
|
||||||
|
steps {
|
||||||
|
sh 'touch ./app/static/wordfreqapp.db && rm -f ./app/static/wordfreqapp.db'
|
||||||
|
sh 'cat ./app/static/wordfreqapp.sql | sqlite3 ./app/static/wordfreqapp.db'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('BuildIt') {
|
||||||
|
steps {
|
||||||
|
echo 'Building..'
|
||||||
|
sh 'sudo docker kill $(sudo docker ps -q)'
|
||||||
|
sh 'sudo docker build -t englishpal .'
|
||||||
|
sh 'sudo docker run -d -p 91:80 -v /var/lib/jenkins/workspace/EnglishPal_Pipeline_master/app/static/frequency:/app/static/frequency -t englishpal'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('TestIt') {
|
||||||
|
steps {
|
||||||
|
echo 'Testing..'
|
||||||
|
sh 'sudo docker run -d -p 4444:4444 selenium/standalone-firefox'
|
||||||
|
sh 'pip3 install pytest -U -q'
|
||||||
|
sh 'pip3 install pytest-html -U -q'
|
||||||
|
sh 'pip3 install selenium -U -q'
|
||||||
|
sh 'pytest -v -s --html=EnglishPalTestReport.html ./app/test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('DeployIt') {
|
||||||
|
steps {
|
||||||
|
echo 'Deploying (TBD)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
# EnglishPal - Learn English Words Smartly
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Hui Lan <hui.lan@cantab.net>
|
||||||
|
|
||||||
|
1 November 2019
|
||||||
|
|
||||||
|
|
||||||
|
## What is it?
|
||||||
|
|
||||||
|
|
||||||
|
EnglishPal allows the user to build his list of new English words
|
||||||
|
picked from articles selected for him according his vocabulary level.
|
||||||
|
|
||||||
|
|
||||||
|
## Run it on a local machine
|
||||||
|
|
||||||
|
|
||||||
|
`python3 main.py`
|
||||||
|
|
||||||
|
Make sure you have the SQLite database file in `app/static` (see below).
|
||||||
|
|
||||||
|
|
||||||
|
## Run it as a Docker container
|
||||||
|
|
||||||
|
|
||||||
|
Assuming that docker has been installed and that you are a sudo user (i.e., sudoer), start the program by typing the following command in directory `EnglishPal`:
|
||||||
|
|
||||||
|
`sudo ./build.sh`
|
||||||
|
|
||||||
|
Open your favourite Internet browser and enter this URL address: `http://ip-address:90`.
|
||||||
|
|
||||||
|
### Explanation on the commands in build.sh
|
||||||
|
|
||||||
|
My steps for deploying English on the server.
|
||||||
|
|
||||||
|
- ssh to ubuntu@118.*.*.118
|
||||||
|
|
||||||
|
- cd to /home/lanhui/englishpal2/EnglishPal
|
||||||
|
|
||||||
|
- Stop all docker service: `sudo service docker restart`. If you know the docker container ID, then the above command is an overkill. Use the following command instead: `sudo docker stop ContainerID`. You could get all container IDs with the following command: `sudo docker ps`
|
||||||
|
|
||||||
|
- Rebuild container. Run the following command to rebuild a docker image after the code gets updated: `sudo docker build -t englishpal .`
|
||||||
|
|
||||||
|
- Run the application: `sudo docker run -d -p 90:80 -v /home/lanhui/englishpal2/EnglishPal/app/static/frequency:/app/static/frequency -t englishpal`. If you use `sudo docker run -d -p 90:80 -t englishpal`, data will be lost after terminating the program.
|
||||||
|
|
||||||
|
- Save space: `sudo docker system prune -a -f`
|
||||||
|
|
||||||
|
|
||||||
|
### Other useful docker commands
|
||||||
|
|
||||||
|
- `sudo docker ps -a`
|
||||||
|
|
||||||
|
- `sudo docker logs image_name`, where image_name could be obtained from `sudo docker ps`.
|
||||||
|
|
||||||
|
`build.sh` contains all the above commands. Run "sudo ./build.sh" to rebuild and run the web application.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Importing articles
|
||||||
|
|
||||||
|
|
||||||
|
All articles are stored in the `article` table in a SQLite file called
|
||||||
|
`app/static/wordfreqapp.db`.
|
||||||
|
|
||||||
|
### Adding new articles
|
||||||
|
|
||||||
|
To add articles, open and edit `app/static/wordfreqapp.db` using DB Browser for SQLite (https://sqlitebrowser.org).
|
||||||
|
|
||||||
|
### Exporting the database
|
||||||
|
|
||||||
|
Export wordfreqapp.db to wordfreqapp.sql using the following commands:
|
||||||
|
|
||||||
|
- sqlite3 wordfreqapp.db
|
||||||
|
|
||||||
|
- .output wordfreqapp.sql
|
||||||
|
|
||||||
|
- .dump
|
||||||
|
|
||||||
|
- .exit
|
||||||
|
|
||||||
|
Put wordfreqapp.sql (not wordfreqapp.db) under version control.
|
||||||
|
|
||||||
|
### Creating SQLite file from wordfreqapp.sql
|
||||||
|
|
||||||
|
|
||||||
|
Create wordfreqapp.db using this command: `cat wordfreqapp.sql |
|
||||||
|
sqlite3 wordfreqapp.db`. Delete wordfreqapp.db first if it exists.
|
||||||
|
|
||||||
|
|
||||||
|
### Uploading wordfreqapp.db to the server
|
||||||
|
|
||||||
|
|
||||||
|
`pscp wordfreqapp.db lanhui@118.*.*.118:/home/lanhui/englishpal/app/static`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
|
||||||
|
We welcome feedback on EnglishPal.
|
||||||
|
|
||||||
|
### Respondent 1
|
||||||
|
|
||||||
|
|
||||||
|
"Need a phone app. I use phone a lot. You cannot ask students to use computers."
|
||||||
|
|
||||||
|
Can take a picture for text. Automatic translation.
|
||||||
|
|
||||||
|
### Respondent 2
|
||||||
|
|
||||||
|
|
||||||
|
“成为会员”改成“注册”
|
||||||
|
|
||||||
|
“登出”改成“退出”
|
||||||
|
|
||||||
|
“收集生词吧”改成“生词收集栏”
|
||||||
|
|
||||||
|
“不要自动显示下一篇”
|
||||||
|
|
||||||
|
需要有“上一篇”、“下一篇”
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Bug tracking
|
||||||
|
|
||||||
|
|
||||||
|
EnglishPal's bugs and improvement suggestions are recorded in [Bugzilla](http://118.25.96.118/bugzilla/buglist.cgi?bug_status=__all__&list_id=1302&order=Importance&product=EnglishPal&query_format=specific). Send (lanhui at zjnu.edu.cn) an email message for opening a Bugzilla account or reporting a bug.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
|
||||||
|
- Fix Bug: Internal server error when register using an email address.
|
||||||
|
|
||||||
|
- Usability testing
|
||||||
|
|
||||||
|
|
||||||
|
## Improvements made by contributors
|
||||||
|
|
||||||
|
|
||||||
|
### 朱文绮
|
||||||
|
|
||||||
|
|
||||||
|
在生词簿每个单词后面,加上两个按钮,熟悉与不熟悉:
|
||||||
|
|
||||||
|
- 如果点熟悉,就将生词簿中该单词后面记录的添加次数减一,直至减为0,就将该单词从生词簿中移除。
|
||||||
|
|
||||||
|
- 如果点不熟悉,就将生词簿中该单词后面记录的添加次数加一。
|
||||||
|
|
||||||
|
### 李康恬
|
||||||
|
|
||||||
|
|
||||||
|
Add the function of "Delete already known and well-known words from
|
||||||
|
the words' library", on the one hand, it can conform to the usage
|
||||||
|
habits of some users, who do not like that their words' libraries have
|
||||||
|
too many words that they already know, on the other hand, it can
|
||||||
|
reduce unnecessary memory occupied by the database, in addition, it
|
||||||
|
can also improve the simplicity of the page.
|
||||||
|
|
||||||
|
More information at: http://118.25.96.118/kanboard/?controller=TaskViewController&action=readonly&task_id=736&token=81a561da57ff7a172da17a480f0d421ff3bc69efbd29437daef90b1b8959
|
||||||
|
|
||||||
|
|
||||||
|
### 占健豪
|
||||||
|
|
||||||
|
|
||||||
|
Click the Familiar or Unfamiliar button (current word frequency>1), the current word position is displayed at the top of the page;
|
||||||
|
|
||||||
|
Click the Familiar or Unfamiliar button (current word frequency is 1), and the page will be displayed as the top of the entire page.
|
||||||
|
|
||||||
|
Demo video link: https://b23.tv/QuB77m
|
||||||
|
|
||||||
|
### 张小飞
|
||||||
|
|
||||||
|
|
||||||
|
修复了以下漏洞。
|
||||||
|
|
||||||
|
漏洞:用 `‘ or ‘1’=‘1’` 这段字符可以作为任何账号的密码登录。
|
||||||
|
|
||||||
|
Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=215
|
||||||
|
|
||||||
|
|
||||||
|
*Last modified on 2021-10-17*
|
|
@ -0,0 +1,136 @@
|
||||||
|
from WordFreq import WordFreq
|
||||||
|
from wordfreqCMD import youdao_link, sort_in_descending_order
|
||||||
|
from UseSqlite import InsertQuery, RecordQuery
|
||||||
|
import pickle_idea, pickle_idea2
|
||||||
|
import os
|
||||||
|
import random, glob
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Flask, request, redirect, render_template, url_for, session, abort, flash, get_flashed_messages
|
||||||
|
from difficulty import get_difficulty_level, text_difficulty_level, user_difficulty_level
|
||||||
|
|
||||||
|
|
||||||
|
path_prefix = '/var/www/wordfreq/wordfreq/'
|
||||||
|
path_prefix = './' # comment this line in deployment
|
||||||
|
|
||||||
|
|
||||||
|
def total_number_of_essays():
|
||||||
|
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
rq.instructions("SELECT * FROM article")
|
||||||
|
rq.do()
|
||||||
|
result = rq.get_results()
|
||||||
|
return len(result)
|
||||||
|
|
||||||
|
|
||||||
|
def get_article_title(s):
|
||||||
|
return s.split('\n')[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_article_body(s):
|
||||||
|
lst = s.split('\n')
|
||||||
|
lst.pop(0) # remove the first line
|
||||||
|
return '\n'.join(lst)
|
||||||
|
|
||||||
|
|
||||||
|
def get_today_article(user_word_list, articleID):
|
||||||
|
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
if articleID == None:
|
||||||
|
rq.instructions("SELECT * FROM article")
|
||||||
|
else:
|
||||||
|
rq.instructions('SELECT * FROM article WHERE article_id=%d' % (articleID))
|
||||||
|
rq.do()
|
||||||
|
result = rq.get_results()
|
||||||
|
random.shuffle(result)
|
||||||
|
|
||||||
|
# Choose article according to reader's level
|
||||||
|
d1 = load_freq_history(path_prefix + 'static/frequency/frequency.p')
|
||||||
|
d2 = load_freq_history(path_prefix + 'static/words_and_tests.p')
|
||||||
|
d3 = get_difficulty_level(d1, d2)
|
||||||
|
|
||||||
|
d = {}
|
||||||
|
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.
|
||||||
|
random.shuffle(result) # shuffle list
|
||||||
|
d = random.choice(result)
|
||||||
|
text_level = text_difficulty_level(d['text'], d3)
|
||||||
|
if articleID == None:
|
||||||
|
for reading in result:
|
||||||
|
text_level = text_difficulty_level(reading['text'], d3)
|
||||||
|
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
|
||||||
|
if within_range(text_level, user_level, (8.0 - user_level) * factor):
|
||||||
|
d = reading
|
||||||
|
break
|
||||||
|
|
||||||
|
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>' % (
|
||||||
|
user_level, text_level)
|
||||||
|
s += '<p class="text-muted">Article added on: %s</p>' % (d['date'])
|
||||||
|
s += '<div class="p-3 mb-2 bg-light text-dark">'
|
||||||
|
article_title = get_article_title(d['text'])
|
||||||
|
article_body = get_article_body(d['text'])
|
||||||
|
s += '<p class="display-3">%s</p>' % (article_title)
|
||||||
|
s += '<p class="lead"><font id="article" size=2>%s</font></p>' % (article_body)
|
||||||
|
s += '<p><small class="text-muted">%s</small></p>' % (d['source'])
|
||||||
|
s += '<p><b>%s</b></p>' % (get_question_part(d['question']))
|
||||||
|
s = s.replace('\n', '<br/>')
|
||||||
|
s += '%s' % (get_answer_part(d['question']))
|
||||||
|
s += '</div>'
|
||||||
|
session['articleID'] = d['article_id']
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def load_freq_history(path):
|
||||||
|
d = {}
|
||||||
|
if os.path.exists(path):
|
||||||
|
d = pickle_idea.load_record(path)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def within_range(x, y, r):
|
||||||
|
return x > y and abs(x - y) <= r
|
||||||
|
|
||||||
|
|
||||||
|
def get_question_part(s):
|
||||||
|
s = s.strip()
|
||||||
|
result = []
|
||||||
|
flag = 0
|
||||||
|
for line in s.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line == 'QUESTION':
|
||||||
|
result.append(line)
|
||||||
|
flag = 1
|
||||||
|
elif line == 'ANSWER':
|
||||||
|
flag = 0
|
||||||
|
elif flag == 1:
|
||||||
|
result.append(line)
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
|
||||||
|
def get_answer_part(s):
|
||||||
|
s = s.strip()
|
||||||
|
result = []
|
||||||
|
flag = 0
|
||||||
|
for line in s.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line == 'ANSWER':
|
||||||
|
flag = 1
|
||||||
|
elif flag == 1:
|
||||||
|
result.append(line)
|
||||||
|
# https://css-tricks.com/snippets/javascript/showhide-element/
|
||||||
|
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
|
|
@ -0,0 +1,77 @@
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime
|
||||||
|
from UseSqlite import InsertQuery, RecordQuery
|
||||||
|
|
||||||
|
path_prefix = '/var/www/wordfreq/wordfreq/'
|
||||||
|
path_prefix = './' # comment this line in deployment
|
||||||
|
|
||||||
|
|
||||||
|
def verify_user(username, password):
|
||||||
|
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
password = md5(username + password)
|
||||||
|
rq.instructions_with_parameters("SELECT * FROM user WHERE name=:username AND password=:password", dict(
|
||||||
|
username=username, password=password)) # the named style https://docs.python.org/3/library/sqlite3.html
|
||||||
|
rq.do_with_parameters()
|
||||||
|
result = rq.get_results()
|
||||||
|
return result != []
|
||||||
|
|
||||||
|
|
||||||
|
def add_user(username, password):
|
||||||
|
start_date = datetime.now().strftime('%Y%m%d')
|
||||||
|
expiry_date = '20221230'
|
||||||
|
# 将用户名和密码一起加密,以免暴露不同用户的相同密码
|
||||||
|
password = md5(username + password)
|
||||||
|
rq = InsertQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
rq.instructions_with_parameters("INSERT INTO user VALUES (:username, :password, :start_date, :expiry_date)", dict(
|
||||||
|
username=username, password=password, start_date=start_date, expiry_date=expiry_date))
|
||||||
|
rq.do_with_parameters()
|
||||||
|
|
||||||
|
|
||||||
|
def check_username_availability(username):
|
||||||
|
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
rq.instructions_with_parameters(
|
||||||
|
"SELECT * FROM user WHERE name=:username", dict(username=username))
|
||||||
|
rq.do_with_parameters()
|
||||||
|
result = rq.get_results()
|
||||||
|
return result == []
|
||||||
|
|
||||||
|
|
||||||
|
def change_password(username, old_password, new_password):
|
||||||
|
'''
|
||||||
|
修改密码
|
||||||
|
:param username: 用户名
|
||||||
|
:param old_password: 旧的密码
|
||||||
|
:param new_password: 新密码
|
||||||
|
:return: 修改成功:True 否则:False
|
||||||
|
'''
|
||||||
|
if not verify_user(username, old_password): # 旧密码错误
|
||||||
|
return False
|
||||||
|
# 将用户名和密码一起加密,以免暴露不同用户的相同密码
|
||||||
|
password = md5(username + new_password)
|
||||||
|
rq = InsertQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
rq.instructions_with_parameters("UPDATE user SET password=:password WHERE name=:username", dict(
|
||||||
|
password=password, username=username))
|
||||||
|
rq.do_with_parameters()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_expiry_date(username):
|
||||||
|
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
|
||||||
|
rq.instructions_with_parameters(
|
||||||
|
"SELECT expiry_date FROM user WHERE name=:username", dict(username=username))
|
||||||
|
rq.do_with_parameters()
|
||||||
|
result = rq.get_results()
|
||||||
|
if len(result) > 0:
|
||||||
|
return result[0]['expiry_date']
|
||||||
|
else:
|
||||||
|
return '20191024'
|
||||||
|
|
||||||
|
|
||||||
|
def md5(s):
|
||||||
|
'''
|
||||||
|
MD5摘要
|
||||||
|
:param str: 字符串
|
||||||
|
:return: 经MD5以后的字符串
|
||||||
|
'''
|
||||||
|
h = hashlib.md5(s.encode(encoding='utf-8'))
|
||||||
|
return h.hexdigest()
|
|
@ -0,0 +1,87 @@
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# Reference: Dusty Phillips. Python 3 Objected-oriented Programming Second Edition. Pages 326-328.
|
||||||
|
# Copyright (C) 2019 Hui Lan
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
class Sqlite3Template:
|
||||||
|
def __init__(self, db_fname):
|
||||||
|
self.db_fname = db_fname
|
||||||
|
|
||||||
|
def connect(self, db_fname):
|
||||||
|
self.conn = sqlite3.connect(self.db_fname)
|
||||||
|
|
||||||
|
def instructions(self, query_statement):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def operate(self):
|
||||||
|
self.conn.row_factory = sqlite3.Row
|
||||||
|
self.results = self.conn.execute(self.query) # self.query is to be given in the child classes
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def format_results(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def do(self):
|
||||||
|
self.connect(self.db_fname)
|
||||||
|
self.instructions(self.query)
|
||||||
|
self.operate()
|
||||||
|
|
||||||
|
def instructions_with_parameters(self, query_statement, parameters):
|
||||||
|
self.query = query_statement
|
||||||
|
self.parameters = parameters
|
||||||
|
|
||||||
|
def do_with_parameters(self):
|
||||||
|
self.connect(self.db_fname)
|
||||||
|
self.instructions_with_parameters(self.query, self.parameters)
|
||||||
|
self.operate_with_parameters()
|
||||||
|
|
||||||
|
def operate_with_parameters(self):
|
||||||
|
self.conn.row_factory = sqlite3.Row
|
||||||
|
self.results = self.conn.execute(self.query, self.parameters) # self.query is to be given in the child classes
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
class InsertQuery(Sqlite3Template):
|
||||||
|
def instructions(self, query):
|
||||||
|
self.query = query
|
||||||
|
|
||||||
|
|
||||||
|
class RecordQuery(Sqlite3Template):
|
||||||
|
def instructions(self, query):
|
||||||
|
self.query = query
|
||||||
|
|
||||||
|
def format_results(self):
|
||||||
|
output = []
|
||||||
|
for row_dict in self.results.fetchall():
|
||||||
|
lst = []
|
||||||
|
for k in dict(row_dict):
|
||||||
|
lst.append( row_dict[k] )
|
||||||
|
output.append(', '.join(lst))
|
||||||
|
return '\n\n'.join(output)
|
||||||
|
|
||||||
|
def get_results(self):
|
||||||
|
result = []
|
||||||
|
for row_dict in self.results.fetchall():
|
||||||
|
result.append( dict(row_dict) )
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
#iq = InsertQuery('RiskDB.db')
|
||||||
|
#iq.instructions("INSERT INTO inspection Values ('FoodSupplies', 'RI2019051301', '2019-05-13', '{}')")
|
||||||
|
#iq.do()
|
||||||
|
#iq.instructions("INSERT INTO inspection Values ('CarSupplies', 'RI2019051302', '2019-05-13', '{[{\"risk_name\":\"elevator\"}]}')")
|
||||||
|
#iq.do()
|
||||||
|
|
||||||
|
rq = RecordQuery('wordfreqapp.db')
|
||||||
|
rq.instructions("SELECT * FROM article WHERE level=3")
|
||||||
|
rq.do()
|
||||||
|
#print(rq.format_results())
|
|
@ -0,0 +1,25 @@
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
from wordfreqCMD import remove_punctuation, freq, sort_in_descending_order
|
||||||
|
import string
|
||||||
|
|
||||||
|
class WordFreq:
|
||||||
|
def __init__(self, s):
|
||||||
|
self.s = remove_punctuation(s)
|
||||||
|
|
||||||
|
def get_freq(self):
|
||||||
|
lst = []
|
||||||
|
for t in freq(self.s):
|
||||||
|
word = t[0]
|
||||||
|
if len(word) > 0 and word[0] in string.ascii_letters:
|
||||||
|
lst.append(t)
|
||||||
|
return sort_in_descending_order(lst)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
f = WordFreq('BANANA; Banana, apple ORANGE Banana banana.')
|
||||||
|
print(f.get_freq())
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
'''
|
||||||
|
Yaml.py
|
||||||
|
配置文件包括:
|
||||||
|
./static/config.yml
|
||||||
|
./layout/partial/header.html
|
||||||
|
./layout/partial/footer.html
|
||||||
|
'''
|
||||||
|
import yaml as YAML
|
||||||
|
import os
|
||||||
|
|
||||||
|
path_prefix = './' # comment this line in deployment
|
||||||
|
|
||||||
|
# YAML文件路径
|
||||||
|
ymlPath = path_prefix + 'static/config.yml'
|
||||||
|
|
||||||
|
# partial文件夹路径
|
||||||
|
partialPath = path_prefix + 'layout/partial/'
|
||||||
|
f = open(ymlPath, 'r', encoding='utf-8') # 以'UTF-8'格式打开YAML文件
|
||||||
|
cont = f.read() # 以文本形式读取YAML
|
||||||
|
|
||||||
|
yml = YAML.load(cont, Loader=YAML.FullLoader) # 加载YAML
|
||||||
|
|
||||||
|
with open(partialPath + 'header.html', 'r', encoding='utf-8') as f:
|
||||||
|
yml['header'] = f.read() # header内的文本会被直接添加到所有页面的head标签内
|
||||||
|
|
||||||
|
with open(partialPath + 'footer.html', 'r', encoding='utf-8') as f:
|
||||||
|
yml['footer'] = f.read() # footer内的文本会被直接添加到所有页面的最底部
|
|
@ -0,0 +1,129 @@
|
||||||
|
from flask import *
|
||||||
|
from Login import check_username_availability, verify_user, add_user, get_expiry_date, change_password
|
||||||
|
|
||||||
|
# 初始化蓝图
|
||||||
|
accountService = Blueprint("accountService", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
### Sign-up, login, logout ###
|
||||||
|
@accountService.route("/signup", methods=['GET', 'POST'])
|
||||||
|
def signup():
|
||||||
|
'''
|
||||||
|
注册
|
||||||
|
:return: 根据注册是否成功返回不同界面
|
||||||
|
'''
|
||||||
|
if request.method == 'GET':
|
||||||
|
# GET方法直接返回注册页面
|
||||||
|
return render_template('signup.html')
|
||||||
|
elif request.method == 'POST':
|
||||||
|
# POST方法需判断是否注册成功,再根据结果返回不同的内容
|
||||||
|
username = escape(request.form['username'])
|
||||||
|
password = escape(request.form['password'])
|
||||||
|
|
||||||
|
available = check_username_availability(username)
|
||||||
|
if not available: # 用户名不可用
|
||||||
|
flash('用户名 %s 已经被注册。' % (username))
|
||||||
|
return render_template('signup.html')
|
||||||
|
elif len(password.strip()) < 4: # 密码过短
|
||||||
|
return '密码过于简单。'
|
||||||
|
else: # 添加账户信息
|
||||||
|
add_user(username, password)
|
||||||
|
verified = verify_user(username, password)
|
||||||
|
if verified:
|
||||||
|
# 写入session
|
||||||
|
session['logged_in'] = True
|
||||||
|
session[username] = username
|
||||||
|
session['username'] = username
|
||||||
|
session['expiry_date'] = get_expiry_date(username)
|
||||||
|
session['articleID'] = None
|
||||||
|
return '<p>恭喜,你已成功注册, 你的用户名是 <a href="%s">%s</a>。</p>\
|
||||||
|
<p><a href="/%s">开始使用</a> <a href="/">返回首页</a><p/>' % (username, username, username)
|
||||||
|
else:
|
||||||
|
return '用户名密码验证失败。'
|
||||||
|
|
||||||
|
|
||||||
|
@accountService.route("/login", methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
'''
|
||||||
|
登录
|
||||||
|
:return: 根据登录是否成功返回不同页面
|
||||||
|
'''
|
||||||
|
if request.method == 'GET':
|
||||||
|
# GET请求
|
||||||
|
if not session.get('logged_in'):
|
||||||
|
# 未登录,返回登录页面
|
||||||
|
return render_template('login.html')
|
||||||
|
else:
|
||||||
|
# 已登录,提示信息并显示登出按钮
|
||||||
|
return '你已登录 <a href="/%s">%s</a>。 登出点击<a href="/logout">这里</a>。' % (
|
||||||
|
session['username'], session['username'])
|
||||||
|
elif request.method == 'POST':
|
||||||
|
# POST方法用于判断登录是否成功
|
||||||
|
# check database and verify user
|
||||||
|
username = escape(request.form['username'])
|
||||||
|
password = escape(request.form['password'])
|
||||||
|
verified = verify_user(username, password)
|
||||||
|
if verified:
|
||||||
|
# 登录成功,写入session
|
||||||
|
session['logged_in'] = True
|
||||||
|
session[username] = username
|
||||||
|
session['username'] = username
|
||||||
|
user_expiry_date = get_expiry_date(username)
|
||||||
|
session['expiry_date'] = user_expiry_date
|
||||||
|
session['articleID'] = None
|
||||||
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
else:
|
||||||
|
return '无法通过验证。'
|
||||||
|
|
||||||
|
|
||||||
|
@accountService.route("/logout", methods=['GET', 'POST'])
|
||||||
|
def logout():
|
||||||
|
'''
|
||||||
|
登出
|
||||||
|
:return: 重定位到主界面
|
||||||
|
'''
|
||||||
|
# 将session标记为登出状态
|
||||||
|
session['logged_in'] = False
|
||||||
|
return redirect(url_for('mainpage'))
|
||||||
|
|
||||||
|
|
||||||
|
@accountService.route("/reset", methods=['GET', 'POST'])
|
||||||
|
def reset():
|
||||||
|
'''
|
||||||
|
重设密码
|
||||||
|
:return: 返回适当的页面
|
||||||
|
'''
|
||||||
|
# 下列方法用于防止未登录状态下的修改密码
|
||||||
|
if not session.get('logged_in'):
|
||||||
|
return render_template('login.html')
|
||||||
|
username = session['username']
|
||||||
|
if username == '':
|
||||||
|
return redirect('/login')
|
||||||
|
if request.method == 'GET':
|
||||||
|
# GET请求返回修改密码页面
|
||||||
|
return render_template('reset.html', username=session['username'], state='wait')
|
||||||
|
else:
|
||||||
|
# POST请求用于提交修改后信息
|
||||||
|
old_password = escape(request.form['old-password'])
|
||||||
|
new_password = escape(request.form['new-password'])
|
||||||
|
flag = change_password(username, old_password, new_password) # flag表示是否修改成功
|
||||||
|
if flag:
|
||||||
|
session['logged_in'] = False
|
||||||
|
return \
|
||||||
|
'''
|
||||||
|
<script>
|
||||||
|
alert('密码修改成功,请重新登录。');
|
||||||
|
window.location.href="/login";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
else:
|
||||||
|
return \
|
||||||
|
'''
|
||||||
|
<script>
|
||||||
|
alert('密码修改失败');
|
||||||
|
window.location.href="/reset";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
'''
|
|
@ -0,0 +1,249 @@
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
# Purpose: compute difficulty level of a English text
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
import math
|
||||||
|
from wordfreqCMD import remove_punctuation, freq, sort_in_descending_order, sort_in_ascending_order
|
||||||
|
|
||||||
|
|
||||||
|
def load_record(pickle_fname):
|
||||||
|
f = open(pickle_fname, 'rb')
|
||||||
|
d = pickle.load(f)
|
||||||
|
f.close()
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def difficulty_level_from_frequency(word, d):
|
||||||
|
level = 1
|
||||||
|
if not word in d:
|
||||||
|
return level
|
||||||
|
|
||||||
|
if 'what' in d:
|
||||||
|
ratio = (d['what']+1)/(d[word]+1) # what is a frequent word
|
||||||
|
level = math.log( max(ratio, 1), 2)
|
||||||
|
|
||||||
|
level = min(level, 8)
|
||||||
|
return level
|
||||||
|
|
||||||
|
|
||||||
|
def get_difficulty_level(d1, d2):
|
||||||
|
d = {}
|
||||||
|
L = list(d1.keys()) # in d1, we have freuqence for each word
|
||||||
|
L2 = list(d2.keys()) # in d2, we have test types (e.g., CET4,CET6,BBC) for each word
|
||||||
|
L.extend(L2)
|
||||||
|
L3 = list(set(L)) # L3 contains all words
|
||||||
|
for k in L3:
|
||||||
|
if k in d2:
|
||||||
|
if 'CET4' in d2[k]:
|
||||||
|
d[k] = 4 # CET4 word has level 4
|
||||||
|
elif 'CET6' in d2[k]:
|
||||||
|
d[k] = 6
|
||||||
|
elif 'BBC' in d2[k]:
|
||||||
|
d[k] = 8
|
||||||
|
if k in d1: # BBC could contain easy words that are not in CET4 or CET6. So 4 is not reasonable. Recompute difficulty level.
|
||||||
|
d[k] = min(difficulty_level_from_frequency(k, d1), d[k])
|
||||||
|
elif k in d1:
|
||||||
|
d[k] = difficulty_level_from_frequency(k, d1)
|
||||||
|
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def revert_dict(d):
|
||||||
|
'''
|
||||||
|
In d, word is the key, and value is a list of dates.
|
||||||
|
In d2 (the returned value of this function), time is the key, and the value is a list of words picked at that time.
|
||||||
|
'''
|
||||||
|
d2 = {}
|
||||||
|
for k in d:
|
||||||
|
if type(d[k]) is list: # d[k] is a list of dates.
|
||||||
|
lst = d[k]
|
||||||
|
elif type(d[k]) is int: # for backward compatibility. d was sth like {'word':1}. The value d[k] is not a list of dates, but a number representing how frequent this word had been added to the new word book.
|
||||||
|
freq = d[k]
|
||||||
|
lst = freq*['2021082019'] # why choose this date? No particular reasons. I fix the bug in this date.
|
||||||
|
|
||||||
|
for time_info in lst:
|
||||||
|
date = time_info[:10] # until hour
|
||||||
|
if not date in d2:
|
||||||
|
d2[date] = [k]
|
||||||
|
else:
|
||||||
|
d2[date].append(k)
|
||||||
|
return d2
|
||||||
|
|
||||||
|
|
||||||
|
def user_difficulty_level(d_user, d):
|
||||||
|
d_user2 = revert_dict(d_user) # key is date, and value is a list of words added in that date
|
||||||
|
count = 0
|
||||||
|
geometric = 1
|
||||||
|
for date in sorted(d_user2.keys(), reverse=True): # most recently added words are more important while determining user's level
|
||||||
|
lst = d_user2[date] # a list of words
|
||||||
|
lst2 = [] # a list of tuples, (word, difficulty level)
|
||||||
|
for word in lst:
|
||||||
|
if word in d:
|
||||||
|
lst2.append((word, d[word]))
|
||||||
|
|
||||||
|
lst3 = sort_in_ascending_order(lst2) # easiest tuple first
|
||||||
|
#print(lst3)
|
||||||
|
for t in lst3:
|
||||||
|
word = t[0]
|
||||||
|
hard = t[1]
|
||||||
|
#print('WORD %s HARD %4.2f' % (word, hard))
|
||||||
|
geometric = geometric * (hard)
|
||||||
|
count += 1
|
||||||
|
if count >= 10:
|
||||||
|
return geometric**(1/count)
|
||||||
|
|
||||||
|
return geometric**(1/max(count,1))
|
||||||
|
|
||||||
|
|
||||||
|
def text_difficulty_level(s, d):
|
||||||
|
s = remove_punctuation(s)
|
||||||
|
L = freq(s)
|
||||||
|
|
||||||
|
lst = [] # a list of tuples, each tuple being (word, difficulty level)
|
||||||
|
for x in L:
|
||||||
|
word = x[0]
|
||||||
|
if word in d:
|
||||||
|
lst.append((word, d[word]))
|
||||||
|
|
||||||
|
lst2 = sort_in_descending_order(lst) # most difficult words on top
|
||||||
|
#print(lst2)
|
||||||
|
count = 0
|
||||||
|
geometric = 1
|
||||||
|
for t in lst2:
|
||||||
|
word = t[0]
|
||||||
|
hard = t[1]
|
||||||
|
geometric = geometric * (hard)
|
||||||
|
count += 1
|
||||||
|
if count >= 20: # we look for n most difficult words
|
||||||
|
return geometric**(1/count)
|
||||||
|
|
||||||
|
return geometric**(1/max(count,1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
|
d1 = load_record('frequency.p')
|
||||||
|
#print(d1)
|
||||||
|
|
||||||
|
d2 = load_record('words_and_tests.p')
|
||||||
|
#print(d2)
|
||||||
|
|
||||||
|
|
||||||
|
d3 = get_difficulty_level(d1, d2)
|
||||||
|
|
||||||
|
s = '''
|
||||||
|
South Lawn
|
||||||
|
11:53 A.M. EDT
|
||||||
|
THE PRESIDENT: Hi, everybody. Hi. How are you? So, the stock market is doing very well.
|
||||||
|
The economy is booming. We have a new record in sight. It could happen even today.
|
||||||
|
But we have a new stock market record. I think it’ll be about 118 times that we’ve broken the record.
|
||||||
|
Jobs look phenomenal.
|
||||||
|
'''
|
||||||
|
s = '''
|
||||||
|
By the authority vested in me as President by the Constitution and the laws of the United States, after carefully considering the reports submitted to the Congress by the Energy Information Administration, including the report submitted in October 2019, and other relevant factors, including global economic conditions, increased oil production by certain countries, the global level of spare petroleum production capacity, and the availability of strategic reserves, I determine, pursuant to section 1245(d)(4)(B) and (C) of the National Defense Authorization Act for Fiscal Year 2012, Public Law 112-81, and consistent with prior determinations, that there is a sufficient supply of petroleum and petroleum products from countries other than Iran to permit a significant reduction in the volume of petroleum and petroleum products purchased from Iran by or through foreign financial institutions.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
s = '''
|
||||||
|
Democrats keep their witnesses locked behind secure doors, then flood the press with carefully sculpted leaks and accusations, driving the Trump-corruption narrative. And so the party goes, galloping toward an impeachment vote that would overturn the will of the American voters—on a case built in secret.
|
||||||
|
|
||||||
|
Conservative commentators keep noting that Mrs. Pelosi’s refusal to hold a vote on the House floor to authorize an official impeachment inquiry helps her caucus’s vulnerable members evade accountability. But there’s a more practical and uglier reason for Democrats to skip the formalities. Normally an authorization vote would be followed by official rules on how the inquiry would proceed. Under today’s process, Mr. Schiff gets to make up the rules as he goes along. Behold the Lord High Impeacher.
|
||||||
|
|
||||||
|
Democrats view control over the narrative as essential, having learned from their Russia-collusion escapade the perils of transparency. They banked on special counsel Robert Mueller’s investigation proving impeachment fodder, but got truth-bombed. Their subsequent open hearings on the subject—featuring Michael Cohen, Mr. Mueller and Corey Lewandowski —were, for the Democrats, embarrassing spectacles, at which Republicans punched gaping holes in their story line.
|
||||||
|
|
||||||
|
Mr. Schiff is making sure that doesn’t happen again; he’ll present the story, on his terms. His rules mean he can issue that controlling decree about “only one” transcript and Democratic staff supervision of Republican members. It means he can bar the public, the press and even fellow representatives from hearings, even though they’re unclassified.
|
||||||
|
'''
|
||||||
|
|
||||||
|
s = '''
|
||||||
|
Unemployment today is at a 50-year low. There are more Americans working today than ever before. Median household income in the last two and half years has risen by more than $5,000. And that doesn’t even account for the savings from the President’s tax cuts or energy reforms for working families.
|
||||||
|
|
||||||
|
Because of the President’s policies, America has added trillions of dollars of wealth to our economy while China’s economy continues to fall behind.
|
||||||
|
|
||||||
|
To level the playing field for the American worker against unethical trade practices, President Trump levied tariffs on $250 billion in Chinese goods in 2018. And earlier this year, the President announced we would place tariffs on another $300 billion of Chinese goods if significant issues in our trading relationship were not resolved by December of this year.
|
||||||
|
'''
|
||||||
|
s = '''
|
||||||
|
Needless to say, we see it very differently. Despite the great power competition that is underway, and America’s growing strength, we want better for China. That’s why, for the first time in decades, under President Donald Trump’s leadership, the United States is treating China’s leaders exactly how the leaders of any great world power should be treated — with respect, yes, but also with consistency and candor.
|
||||||
|
'''
|
||||||
|
s = '''
|
||||||
|
Brexit is the scheduled withdrawal of the United Kingdom from the European Union. Following a June 2016 referendum, in which 51.9% voted to leave, the UK government formally announced the country's withdrawal in March 2017, starting a two-year process that was due to conclude with the UK withdrawing on 29 March 2019. As the UK parliament thrice voted against the negotiated withdrawal agreement, that deadline has been extended twice, and is currently 31 October 2019. The Benn Act, passed in September 2019, requires the government to seek a third extension.
|
||||||
|
'''
|
||||||
|
|
||||||
|
s = '''
|
||||||
|
The argument for Brexit
|
||||||
|
According to the BBC, the push to leave the EU was advocated mostly by the UK Independence Party and was not supported by the Prime Minister, David Cameron. Members of the UK Independence Party argued that Britain’s participation in the EU was a restrictive element for the country.
|
||||||
|
|
||||||
|
As one of the EU’s primary initiatives is free movement within the region the party’s main arguments centered around regaining border control and reclaiming business rights. In addition, supporters of Brexit cited the high EU membership fees as a negative aspect of participation in the EU. It was argued that if the UK separates itself from the EU, these fees can be used to benefit the UK.
|
||||||
|
|
||||||
|
The argument against Brexit
|
||||||
|
The Conservative Party and the Prime Minister were strongly in favor of remaining with the EU. As a result of the decision to discontinue its participation in the EU, the Prime Minister has made a public statement that he will be relinquishing his position. He believes that the country needs a leader with the same goals as the majority of the country. He has promised a new PM will be in place by early September.
|
||||||
|
|
||||||
|
The argument against Brexit pertains mostly to the business benefits. The argument is that the UK receives business benefits by being able to participate in the single market system established by the EU. In response to the criticism against the open borders, proponents believe that the influx of immigrants helps develop an eager workforce and fuels public service projects.
|
||||||
|
|
||||||
|
Leaders in favor of staying also worry about the political backlash that could possibly result from other countries who favored staying with the EU. In addition, proponents of remaining with the EU believe that being part of a wider community of nations provides economic and cultural strength, as well as an additional element of security.
|
||||||
|
|
||||||
|
What does Brexit mean for the future?
|
||||||
|
While the decision marked a huge statement for the UK, the referendum vote is not legally binding. There are still many hurdles that must be dealt with before Brexit can become a reality.
|
||||||
|
|
||||||
|
The UK is still subject to the laws of the EU until Britain’s exit becomes legal. In order for the UK to make its break official, the country needs to invoke Article 50. It is unclear exactly what this process will entail or how long it will take as Britain is the first country to take its leave of the EU. Once Article 50 has been formally invoked, the UK has two years to negotiate its departure with the other member states. But according to the BBC, “Extricating the UK from the EU will be extremely complex, and the process could drag on longer than that.”
|
||||||
|
|
||||||
|
Amidst the aftermath of this shocking referendum vote, there is great uncertainty as political leaders decide what this means for the UK.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
s = '''
|
||||||
|
British Prime Minister Boris Johnson walks towards a voting station during the Brexit referendum in Britain, June 23, 2016. (Photo: EPA-EFE)
|
||||||
|
|
||||||
|
LONDON – British Prime Minister Boris Johnson said Thursday he will likely ask Parliament to approve an election as part of an effort to break a Brexit deadlock.
|
||||||
|
|
||||||
|
It is not clear if the vote, which Johnson wants to hold on Dec. 12, will take place as opposition lawmakers must also back the move.
|
||||||
|
|
||||||
|
They are expected to vote on the measure on Monday.
|
||||||
|
|
||||||
|
Johnson's announcement comes ahead of an expected decision Friday from the European Union over whether to delay Britain's exit from the bloc for three months.
|
||||||
|
|
||||||
|
Britain's leader has been steadfastly opposed to any extension to the nation's scheduled Oct. 31 departure date from the EU, although in a letter to the leader of the opposition Labour Party this week he said he would accept a short technical postponement, "say to 15 or 30 November," to allow lawmakers to implement an EU withdrawal bill.
|
||||||
|
|
||||||
|
Johnson's decision to offer to call an election follows lawmakers' rejection of his plan to rush through an EU exit bill that runs to hundreds of pages in just three days. They want more time to scrutinize the legislation and to make sure it does not leave the door open to a possible "no-deal" Brexit during future exit negotiations with the EU that will run through next year. A "no-deal" Brexit could dramatically harm Britain's economy.
|
||||||
|
|
||||||
|
The prime minister was forced to ask for an extension to Britain's EU departure date after Britain's Parliament passed a law to ward off the threat of a "no-deal" Brexit.
|
||||||
|
|
||||||
|
Johnson has repeatedly pledged to finalize the first stage, a transition deal, of Britain's EU divorce battle by Oct. 31. A second stage will involve negotiating its future relationship with the EU on trade, security and other salient issues.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
s = '''
|
||||||
|
Thank you very much. We have a Cabinet meeting. We’ll have a few questions after grace. And, if you would, Ben, please do the honors.
|
||||||
|
|
||||||
|
THE PRESIDENT: All right, thank you, Ben. That was a great job. Appreciate it.
|
||||||
|
|
||||||
|
The economy is doing fantastically well. It’s getting very close to another record. We’ve had many records since we won office. We’re getting very close to another record. I don’t know if anybody saw it: The household median income for eight years of President Bush, it rose $400. For eight years of President Obama, it rose $975. And for two and half years of President Trump — they have it down as two and a half years — it rose $5,000, not including $2,000 for taxes. So it rose, let’s say, $7,000. So in two and a half years, we’re up $7,000, compared to $1,000, compared to $400. And that’s for eight years and eight years.
|
||||||
|
|
||||||
|
That’s a number that just came out, but that’s a number that I don’t know how there could be any dispute or any — I’ve never heard a number like that, meaning the economy is doing fantastically well.
|
||||||
|
|
||||||
|
We need — for our farmers, our manufacturers, for, frankly, unions and non-unions, we need USMCA to be voted on. If it’s voted on, it’ll pass. It’s up to Nancy Pelosi to put it up. If she puts it up, it’s going to pass. It’s going to be very bipartisan. It’s something that’s very much needed. It’ll be hundreds of thousands of jobs.
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#f = open('bbc-fulltext/bbc/entertainment/001.txt')
|
||||||
|
f = open('wordlist.txt')
|
||||||
|
s = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print(text_difficulty_level(s, d3))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!--
|
||||||
|
以html的形式插入到所有页面的页尾
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<br/><br/><br/>
|
||||||
|
<div class="aplayer-container">
|
||||||
|
<meting-js
|
||||||
|
theme='#1BCDFC'
|
||||||
|
autoplay='false'
|
||||||
|
volume='0.5'
|
||||||
|
loop='all'
|
||||||
|
order='list'
|
||||||
|
fixed='true'
|
||||||
|
list-max-height='160px'
|
||||||
|
server='netease'
|
||||||
|
type='playlist'
|
||||||
|
id='6927872292'
|
||||||
|
list-folded='true'>
|
||||||
|
</meting-js>
|
||||||
|
</div>
|
||||||
|
-->
|
|
@ -0,0 +1,3 @@
|
||||||
|
<!--
|
||||||
|
以html的形式插入所有网页的head标签中
|
||||||
|
-->
|
|
@ -0,0 +1,114 @@
|
||||||
|
#! /usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
from Login import *
|
||||||
|
from Article import *
|
||||||
|
import Yaml
|
||||||
|
from user_service import userService
|
||||||
|
from account_service import accountService
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = 'lunch.time!'
|
||||||
|
|
||||||
|
# 将蓝图注册到Lab app
|
||||||
|
app.register_blueprint(userService)
|
||||||
|
app.register_blueprint(accountService)
|
||||||
|
|
||||||
|
path_prefix = '/var/www/wordfreq/wordfreq/'
|
||||||
|
path_prefix = './' # comment this line in deployment
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_image(path):
|
||||||
|
'''
|
||||||
|
返回随机图
|
||||||
|
:param path: 图片文件(JPEG格式),不包含后缀名
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
img_path = random.choice(glob.glob(os.path.join(path, '*.jpg')))
|
||||||
|
|
||||||
|
return img_path[img_path.rfind('/static'):]
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_ads():
|
||||||
|
'''
|
||||||
|
返回随机广告
|
||||||
|
:return: 一个广告(包含HTML标签)
|
||||||
|
'''
|
||||||
|
ads = random.choice(['个性化分析精准提升', '你的专有单词本', '智能捕捉阅读弱点,针对性提高你的阅读水平'])
|
||||||
|
return ads + '。 <a href="/signup">试试</a>吧!'
|
||||||
|
|
||||||
|
|
||||||
|
def appears_in_test(word, d):
|
||||||
|
'''
|
||||||
|
如果字符串里没有指定的单词,则返回逗号加单词
|
||||||
|
:param word: 指定单词
|
||||||
|
:param d: 字符串
|
||||||
|
:return: 逗号加单词
|
||||||
|
'''
|
||||||
|
if not word in d:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return ','.join(d[word])
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/mark", methods=['GET', 'POST'])
|
||||||
|
def mark_word():
|
||||||
|
'''
|
||||||
|
标记单词
|
||||||
|
:return: 重定位到主界面
|
||||||
|
'''
|
||||||
|
if request.method == 'POST':
|
||||||
|
d = load_freq_history(path_prefix + 'static/frequency/frequency.p')
|
||||||
|
lst_history = pickle_idea.dict2lst(d)
|
||||||
|
lst = []
|
||||||
|
for word in request.form.getlist('marked'):
|
||||||
|
lst.append((word, 1))
|
||||||
|
d = pickle_idea.merge_frequency(lst, lst_history)
|
||||||
|
pickle_idea.save_frequency_to_pickle(d, path_prefix + 'static/frequency/frequency.p')
|
||||||
|
return redirect(url_for('mainpage'))
|
||||||
|
else: # 不回应GET请求
|
||||||
|
return 'Under construction'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/", methods=['GET', 'POST'])
|
||||||
|
def mainpage():
|
||||||
|
'''
|
||||||
|
根据GET或POST方法来返回不同的主界面
|
||||||
|
:return: 主界面
|
||||||
|
'''
|
||||||
|
if request.method == 'POST': # when we submit a form
|
||||||
|
content = request.form['content']
|
||||||
|
f = WordFreq(content)
|
||||||
|
lst = f.get_freq()
|
||||||
|
# save history
|
||||||
|
d = load_freq_history(path_prefix + 'static/frequency/frequency.p')
|
||||||
|
lst_history = pickle_idea.dict2lst(d)
|
||||||
|
d = pickle_idea.merge_frequency(lst, lst_history)
|
||||||
|
pickle_idea.save_frequency_to_pickle(d, path_prefix + 'static/frequency/frequency.p')
|
||||||
|
return render_template('mainpage_post.html', lst=lst, yml=Yaml.yml)
|
||||||
|
|
||||||
|
elif request.method == 'GET': # when we load a html page
|
||||||
|
random_ads = get_random_ads()
|
||||||
|
number_of_essays = total_number_of_essays()
|
||||||
|
d = load_freq_history(path_prefix + 'static/frequency/frequency.p')
|
||||||
|
d_len = len(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,
|
||||||
|
d_len=d_len, lst=lst, yml=Yaml.yml)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
'''
|
||||||
|
运行程序
|
||||||
|
'''
|
||||||
|
# app.secret_key = os.urandom(16)
|
||||||
|
# app.run(debug=False, port='6000')
|
||||||
|
app.run(debug=True)
|
||||||
|
# app.run(debug=True, port='6000')
|
||||||
|
# app.run(host='0.0.0.0', debug=True, port='6000')
|
||||||
|
# print(mod5('123'))
|
|
@ -0,0 +1,86 @@
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
# Purpose: dictionary & pickle as a simple means of database.
|
||||||
|
# Task: incorporate the functions into wordfreqCMD.py such that it will also show cumulative frequency.
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def lst2dict(lst, d):
|
||||||
|
'''
|
||||||
|
Store the information in list lst to dictionary d.
|
||||||
|
Note: nothing is returned.
|
||||||
|
|
||||||
|
'''
|
||||||
|
for x in lst:
|
||||||
|
word = x[0]
|
||||||
|
freq = x[1]
|
||||||
|
if not word in d:
|
||||||
|
d[word] = freq
|
||||||
|
else:
|
||||||
|
d[word] += freq
|
||||||
|
|
||||||
|
|
||||||
|
def dict2lst(d):
|
||||||
|
return list(d.items()) # a list of (key, value) pairs
|
||||||
|
|
||||||
|
|
||||||
|
def merge_frequency(lst1, lst2):
|
||||||
|
d = {}
|
||||||
|
lst2dict(lst1, d)
|
||||||
|
lst2dict(lst2, d)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def load_record(pickle_fname):
|
||||||
|
f = open(pickle_fname, 'rb')
|
||||||
|
d = pickle.load(f)
|
||||||
|
f.close()
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def save_frequency_to_pickle(d, pickle_fname):
|
||||||
|
f = open(pickle_fname, 'wb')
|
||||||
|
#exclusion_lst = ['one', 'no', 'has', 'had', 'do', 'that', 'have', 'by', 'not', 'but', 'we', 'this', 'my', 'him', 'so', 'or', 'as', 'are', 'it', 'from', 'with', 'be', 'can', 'for', 'an', 'if', 'who', 'whom', 'whose', 'which', 'the', 'to', 'a', 'of', 'and', 'you', 'i', 'he', 'she', 'they', 'me', 'was', 'were', 'is', 'in', 'at', 'on', 'their', 'his', 'her', 's', 'said', 'all', 'did', 'been', 'w']
|
||||||
|
exclusion_lst = []
|
||||||
|
d2 = {}
|
||||||
|
for k in d:
|
||||||
|
if not k in exclusion_lst and not k.isnumeric() and len(k) > 1:
|
||||||
|
d2[k] = d[k]
|
||||||
|
pickle.dump(d2, f)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def unfamiliar(path,word):
|
||||||
|
f = open(path,"rb")
|
||||||
|
dic = pickle.load(f)
|
||||||
|
dic[word] += [datetime.now().strftime('%Y%m%d%H%M')]
|
||||||
|
fp = open(path,"wb")
|
||||||
|
pickle.dump(dic,fp)
|
||||||
|
|
||||||
|
def familiar(path,word):
|
||||||
|
f = open(path,"rb")
|
||||||
|
dic = pickle.load(f)
|
||||||
|
if len(dic[word])>1:
|
||||||
|
del dic[word][0]
|
||||||
|
else:
|
||||||
|
dic.pop(word)
|
||||||
|
fp = open(path,"wb")
|
||||||
|
pickle.dump(dic,fp)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
lst1 = [('apple',2), ('banana',1)]
|
||||||
|
d = {}
|
||||||
|
lst2dict(lst1, d) # d will change
|
||||||
|
save_frequency_to_pickle(d, 'frequency.p') # frequency.p is our database
|
||||||
|
|
||||||
|
|
||||||
|
lst2 = [('banana',2), ('orange', 4)]
|
||||||
|
d = load_record('frequency.p')
|
||||||
|
lst1 = dict2lst(d)
|
||||||
|
d = merge_frequency(lst2, lst1)
|
||||||
|
print(d)
|
|
@ -0,0 +1,89 @@
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# Purpose: dictionary & pickle as a simple means of database.
|
||||||
|
# Task: incorporate the functions into wordfreqCMD.py such that it will also show cumulative frequency.
|
||||||
|
# Note: unlike pick_idea.py, now the second item is not frequency, but a list of dates.
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def lst2dict(lst, d):
|
||||||
|
'''
|
||||||
|
Store the information in list lst to dictionary d.
|
||||||
|
Note: nothing is returned.
|
||||||
|
|
||||||
|
'''
|
||||||
|
for x in lst:
|
||||||
|
word = x[0]
|
||||||
|
dates = x[1]
|
||||||
|
if not word in d:
|
||||||
|
d[word] = dates
|
||||||
|
else:
|
||||||
|
d[word] += dates
|
||||||
|
|
||||||
|
def deleteRecord(path,word):
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
db = pickle.load(f)
|
||||||
|
try:
|
||||||
|
db.pop(word)
|
||||||
|
except KeyError:
|
||||||
|
print("sorry")
|
||||||
|
with open(path, 'wb') as ff:
|
||||||
|
pickle.dump(db, ff)
|
||||||
|
|
||||||
|
def dict2lst(d):
|
||||||
|
if len(d) > 0:
|
||||||
|
keys = list(d.keys())
|
||||||
|
if isinstance(d[keys[0]], int):
|
||||||
|
lst = []
|
||||||
|
for k in d:
|
||||||
|
lst.append((k, [datetime.now().strftime('%Y%m%d%H%M')]))
|
||||||
|
return lst
|
||||||
|
elif isinstance(d[keys[0]], list):
|
||||||
|
return list(d.items()) # a list of (key, value) pairs
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def merge_frequency(lst1, lst2):
|
||||||
|
d = {}
|
||||||
|
lst2dict(lst1, d)
|
||||||
|
lst2dict(lst2, d)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def load_record(pickle_fname):
|
||||||
|
f = open(pickle_fname, 'rb')
|
||||||
|
d = pickle.load(f)
|
||||||
|
f.close()
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def save_frequency_to_pickle(d, pickle_fname):
|
||||||
|
f = open(pickle_fname, 'wb')
|
||||||
|
exclusion_lst = ['one', 'no', 'has', 'had', 'do', 'that', 'have', 'by', 'not', 'but', 'we', 'this', 'my', 'him', 'so', 'or', 'as', 'are', 'it', 'from', 'with', 'be', 'can', 'for', 'an', 'if', 'who', 'whom', 'whose', 'which', 'the', 'to', 'a', 'of', 'and', 'you', 'i', 'he', 'she', 'they', 'me', 'was', 'were', 'is', 'in', 'at', 'on', 'their', 'his', 'her', 's', 'said', 'all', 'did', 'been', 'w']
|
||||||
|
d2 = {}
|
||||||
|
for k in d:
|
||||||
|
if not k in exclusion_lst and not k.isnumeric() and not len(k) < 2:
|
||||||
|
d2[k] = list(sorted(set(d[k])))
|
||||||
|
pickle.dump(d2, f)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
lst1 = [('apple',['201910251437', '201910251438']), ('banana',['201910251439'])]
|
||||||
|
d = {}
|
||||||
|
lst2dict(lst1, d) # d will change
|
||||||
|
save_frequency_to_pickle(d, 'frequency.p') # frequency.p is our database
|
||||||
|
|
||||||
|
|
||||||
|
lst2 = [('banana',['201910251439']), ('orange', ['201910251440', '201910251439'])]
|
||||||
|
d = load_record('frequency.p')
|
||||||
|
lst1 = dict2lst(d)
|
||||||
|
d = merge_frequency(lst2, lst1)
|
||||||
|
print(d)
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 全局引入的css文件地址
|
||||||
|
css:
|
||||||
|
item:
|
||||||
|
- static/css/bootstrap.css
|
||||||
|
|
||||||
|
# 全局引入的js文件地址
|
||||||
|
js:
|
||||||
|
head: # 在页面加载之前加载
|
||||||
|
# - static/js/APlayer.js
|
||||||
|
# - static/js/Meting.js
|
||||||
|
bottom: # 在页面加载完之后加载
|
||||||
|
- static/js/fillword.js
|
||||||
|
- static/js/highlight.js
|
||||||
|
|
||||||
|
# 高亮样式,目前仅支持修改颜色
|
||||||
|
highlight:
|
||||||
|
color: ff0000
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
||||||
|
isRead = true;
|
||||||
|
isChoose = true;
|
||||||
|
var reader = window.speechSynthesis; // 全局定义朗读者,以便朗读和暂停
|
||||||
|
|
||||||
|
function getWord(){
|
||||||
|
var word = window.getSelection?window.getSelection():document.selection.createRange().text;
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
function fillinWord(){
|
||||||
|
var word = getWord();
|
||||||
|
if (isRead) read(word);
|
||||||
|
if (!isChoose) return;
|
||||||
|
var element = document.getElementById("selected-words");
|
||||||
|
element.value = element.value + " " + word;
|
||||||
|
}
|
||||||
|
document.getElementById("text-content").addEventListener("click", fillinWord, false);
|
||||||
|
function read(s){
|
||||||
|
var msg = new SpeechSynthesisUtterance(s);
|
||||||
|
reader.speak(msg);
|
||||||
|
}
|
||||||
|
function onReadClick(){
|
||||||
|
isRead = !isRead;
|
||||||
|
if(!isRead){
|
||||||
|
reader.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onChooseClick(){
|
||||||
|
isChoose = !isChoose;
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
var isHighlight = true;
|
||||||
|
|
||||||
|
function cancelBtnHandler() {
|
||||||
|
cancel_highLight();
|
||||||
|
document.getElementById("text-content").removeEventListener("click", fillinWord, false);
|
||||||
|
document.getElementById("text-content").removeEventListener("touchstart", fillinWord, false);
|
||||||
|
document.getElementById("text-content").addEventListener("click", fillinWord2, false);
|
||||||
|
document.getElementById("text-content").addEventListener("touchstart", fillinWord2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBtnHandler() {
|
||||||
|
document.getElementById("text-content").removeEventListener("click", fillinWord2, false);
|
||||||
|
document.getElementById("text-content").removeEventListener("touchstart", fillinWord2, false);
|
||||||
|
document.getElementById("text-content").addEventListener("click", fillinWord, false);
|
||||||
|
document.getElementById("text-content").addEventListener("touchstart", fillinWord, false);
|
||||||
|
highLight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWord() {
|
||||||
|
var word = window.getSelection ? window.getSelection() : document.selection.createRange().text;
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
function highLight() {
|
||||||
|
if(!isHighlight) return;
|
||||||
|
var txt = document.getElementById("article").innerText;
|
||||||
|
var sel_word1 = document.getElementById("selected-words");
|
||||||
|
var sel_word2 = document.getElementById("selected-words2");
|
||||||
|
if (sel_word1 != null) {
|
||||||
|
var list = sel_word1.value.split(" ");
|
||||||
|
for (var i = 0; i < list.length; ++i) {
|
||||||
|
list[i] = list[i].replace(/(^\s*)|(\s*$)/g, "");
|
||||||
|
if (list[i] != "" && "<mark>".indexOf(list[i]) == -1 && "</mark>".indexOf(list[i]) == -1) {
|
||||||
|
txt = txt.replace(new RegExp(list[i], "g"), "<mark>" + list[i] + "</mark>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sel_word2 != null) {
|
||||||
|
var list2 = sel_word2.value.split(" ");
|
||||||
|
for (var i = 0; i < list2.length; ++i) {
|
||||||
|
list2[i] = list2[i].replace(/(^\s*)|(\s*$)/g, "");
|
||||||
|
if (list2[i] != "" && "<mark>".indexOf(list2[i]) == -1 && "</mark>".indexOf(list2[i]) == -1) {
|
||||||
|
txt = txt.replace(new RegExp(list2[i], "g"), "<mark>" + list2[i] + "</mark>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("article").innerHTML = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel_highLight() {
|
||||||
|
var txt = document.getElementById("article").innerText;
|
||||||
|
var sel_word1 = document.getElementById("selected-words");
|
||||||
|
var sel_word2 = document.getElementById("selected-words2");
|
||||||
|
if (sel_word1 != null) {
|
||||||
|
var list = sel_word1.value.split(" ");
|
||||||
|
for (var i = 0; i < list.length; ++i) {
|
||||||
|
list[i] = list[i].replace(/(^\s*)|(\s*$)/g, "");
|
||||||
|
if (list[i] != "") {
|
||||||
|
txt = txt.replace("<mark>" + list[i] + "</mark>", "list[i]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sel_word2 != null) {
|
||||||
|
var list2 = sel_word1.value.split(" ");
|
||||||
|
for (var i = 0; i < list2.length; ++i) {
|
||||||
|
var list2 = sel_word2.value.split(" ");
|
||||||
|
list2[i] = list2[i].replace(/(^\s*)|(\s*$)/g, "");
|
||||||
|
if (list2[i] != "") {
|
||||||
|
txt = txt.replace("<mark>" + list[i] + "</mark>", "list[i]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("article").innerHTML = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillinWord() {
|
||||||
|
highLight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillinWord2() {
|
||||||
|
cancel_highLight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangeHighlight() {
|
||||||
|
if (isHighlight) {
|
||||||
|
isHighlight = false;
|
||||||
|
cancel_highLight();
|
||||||
|
} else {
|
||||||
|
isHighlight = true;
|
||||||
|
highLight();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showBtnHandler();
|
|
@ -0,0 +1,86 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh"><head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; 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 charset="utf-8">
|
||||||
|
<title>怎么用English Pal</title>
|
||||||
|
<style>
|
||||||
|
li { margin: 3px 0; }
|
||||||
|
ul {list-style-type: none;}
|
||||||
|
body {font-size: 100%;}
|
||||||
|
* {font-family:SimSun !important;}
|
||||||
|
|
||||||
|
/* http://webtricksandtreats.com/table-style-css/ */
|
||||||
|
table.table-style-one {
|
||||||
|
font-family: verdana,arial,sans-serif;
|
||||||
|
font-size:14px;
|
||||||
|
color:#333333;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #3A3A3A;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table.table-style-one th {
|
||||||
|
border-width: 1px;
|
||||||
|
padding: 8px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #3A3A3A;
|
||||||
|
background-color: #F0F8FF;
|
||||||
|
}
|
||||||
|
table.table-style-one td {
|
||||||
|
border-width: 1px;
|
||||||
|
padding: 8px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #3A3A3A;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p>本软件的宗旨是:珍惜你的时间, 提高你的获取英文信息的速度与准确度。 不管你是英语爱好者,备考的学生,还是英语老师,都能从 English Pal 中发现用处。</p>
|
||||||
|
|
||||||
|
<p>如果你教英语,English Pal 可以帮你掌握题目词汇规律, 提高教学质量。</p>
|
||||||
|
|
||||||
|
<p>如果你学英语,English Pal 可以帮你迅速提高词汇, 轻松应对各种考试。 </p>
|
||||||
|
|
||||||
|
<p>1秒内闪电查词,告别字典。 私人定制的单词簿,永久相伴,记录奋斗岁月。 </p>
|
||||||
|
|
||||||
|
<p>现在就<a href="/signup">试试看</a>吧。 English Pal 期待你的捷报, English Pal 期待你的<a href="./20161113-christ-piece-cambridge.jpg">远航</a>。</p>
|
||||||
|
|
||||||
|
<h2>使用方法</h2>
|
||||||
|
|
||||||
|
<p>在EnglishPal主页点击<a href="http://118.25.96.118:90/signup">成为会员</a>链接。支付会员费后即可开始使用。</p>
|
||||||
|
|
||||||
|
<p>活跃会员还有机会获得英文阅读10分钟一对一指导。</p>
|
||||||
|
|
||||||
|
<table class="table-style-one">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>截图</th>
|
||||||
|
<th>说明</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="./EnglishPal-screenshot-01.jpg"><img src="./EnglishPal-screenshot-01.jpg" width="300px"/></a></td>
|
||||||
|
<td>精选短文,让你窥见世界。 统计词频,让你掌握规律。</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><a href="./EnglishPal-screenshot-02.jpg"><img src="./EnglishPal-screenshot-02.jpg" width="300px"/></a></td>
|
||||||
|
<td>时间无价,个性化的生词簿,为你节省记背单词时间。</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><a href="./EnglishPal-screenshot-03.jpg"><img src="./EnglishPal-screenshot-03.jpg" width="300px"/></a></td>
|
||||||
|
<td>考试人人都怕,单词的考试分类,让你目的明确。</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE user(name TEXT PRIMARY KEY, password TEXT, start_date TEXT, expiry_date TEXT);
|
||||||
|
CREATE TABLE sqlite_sequence(name,seq);
|
||||||
|
CREATE TABLE IF NOT EXISTS "article" (
|
||||||
|
"article_id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"text" TEXT,
|
||||||
|
"source" TEXT,
|
||||||
|
"date" TEXT,
|
||||||
|
"level" TEXT,
|
||||||
|
"question" TEXT
|
||||||
|
);
|
Binary file not shown.
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>账号过期</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>您的账号{{ username }}过期。</p>
|
||||||
|
<p>为了提高服务质量,English Pal 收取会员费用, 每天1元。</p>
|
||||||
|
<p>请决定你要试用的时间长度,扫描下面支付宝二维码支付。 支付时请注明<i>English Pal Membership Fee</i>。 我们会于12小时内激活账号。</p>
|
||||||
|
<p><img src="static/donate-the-author-hidden.jpg" width="120px" alt="支付宝二维码" /></p>
|
||||||
|
<p>如果有问题,请联系开发者 dev@qq.com。</p>
|
||||||
|
<p><a href="/logout">退出</a></p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% block body %}
|
||||||
|
{% if session['logged_in'] %}
|
||||||
|
|
||||||
|
You're logged in already!
|
||||||
|
|
||||||
|
{% 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" />
|
||||||
|
<form action="/login" method="POST">
|
||||||
|
<p>
|
||||||
|
<input type="username" name="username" placeholder="邮箱地址、电话号码">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<input type="password" name="password" placeholder="密码">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<input type="submit" value="登录">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
{{ 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 %}
|
||||||
|
|
||||||
|
<title>EnglishPal 英文单词高效记</title>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<p><b><font size="+3" color="red">English Pal - Learn English smartly!</font></b></p>
|
||||||
|
{% if session['logged_in'] %}
|
||||||
|
<a href="/{{session['username']}}">{{session['username']}}</a></p>
|
||||||
|
{% else %}
|
||||||
|
<p><a href="/login">登录</a> <a href="/signup">注册</a> <a href="/static/usr/instructions.html">使用说明</a></p >
|
||||||
|
<p><b>{{random_ads|safe}}</b></p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{number_of_essays}} </span> 篇</div>
|
||||||
|
<p>粘贴1篇文章 (English only)</p>
|
||||||
|
<form method="post" action="/">
|
||||||
|
<textarea name="content" rows="10" cols="120"></textarea><br/>
|
||||||
|
<input type="submit" value="get文章中的词频"/>
|
||||||
|
<input type="reset" value="清除"/>
|
||||||
|
</form>
|
||||||
|
{% if d_len > 0 %}
|
||||||
|
<p><b>最常见的词</b></p>
|
||||||
|
{% for x in lst if x[1]>99 %}
|
||||||
|
<a href="http://youdao.com/w/eng/{{x[0]}}/#keyfrom=dict2.index">{{x[0]}}</a> {{x[1]}}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
|
</div>
|
||||||
|
{{ yml['footer'] | safe }}
|
||||||
|
{% if yml['js']['bottom'] %}
|
||||||
|
{% for js in yml['js']['bottom'] %}
|
||||||
|
<script src="{{ js }}" ></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
|
||||||
|
{{ 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>
|
||||||
|
<form method="post" action="/mark">
|
||||||
|
{% for x in lst %}
|
||||||
|
<p>
|
||||||
|
<font color="grey">{{loop.index}}</font>
|
||||||
|
:
|
||||||
|
<a href="http://youdao.com/w/eng/{{x[0]}}/#keyfrom=dict2.index">{{x[0]}}</a>
|
||||||
|
({{x[1]}})
|
||||||
|
<input type="checkbox" name="marked" value="{{x[0]}}">
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="确定并返回"/>
|
||||||
|
</form>
|
||||||
|
{{ yml['footer'] | safe }}
|
||||||
|
{% if yml['js']['bottom'] %}
|
||||||
|
{% for js in yml['js']['bottom'] %}
|
||||||
|
<script src="{{ js }}" ></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% block body %}
|
||||||
|
{% if not session['logged_in'] %}
|
||||||
|
<p>请先<a href="/login">登录</a></p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<form action="/reset" method='POST'>
|
||||||
|
旧密码:
|
||||||
|
<input type="password" name="old-password" />
|
||||||
|
<br/>
|
||||||
|
新密码:
|
||||||
|
<input type="password" name="new-password" />
|
||||||
|
<br/>
|
||||||
|
<input type="submit" name="submit" value="提交" />
|
||||||
|
<input type="button" name="submit" value="放弃修改" onclick="window.location.href='/{{ username }}'"/>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% block body %}
|
||||||
|
{% if session['logged_in'] %}
|
||||||
|
|
||||||
|
You're logged in already! <a href="/logout">Logout</a>.
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes" />
|
||||||
|
<p>{{ get_flashed_messages()[0] | safe }}</p>
|
||||||
|
|
||||||
|
<p>Sign up here.</p>
|
||||||
|
|
||||||
|
<form action="/signup" method="POST">
|
||||||
|
<p><input type="username" name="username" placeholder="邮箱地址、电话号码" required="required"></p>
|
||||||
|
<p><input type="password" name="password" placeholder="密码"></p>
|
||||||
|
<p><input type="submit" value="注册"></p>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
<!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 %}
|
||||||
|
|
||||||
|
<title>EnglishPal Study Room for {{ username }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<p><b>English Pal for <font color="red">{{ username }}</font></b>
|
||||||
|
<a class="btn btn-secondary" href="/logout" role="button">退出</a>
|
||||||
|
<a class="btn btn-secondary" href="/reset" role="button">重设密码</a>
|
||||||
|
</p>
|
||||||
|
{{ flashed_messages|safe }}
|
||||||
|
|
||||||
|
<p><a class="btn btn-success" href="/{{ username }}/reset" role="button"> 下一篇 Next Article </a></p>
|
||||||
|
<p><b>阅读文章并回答问题</b></p>
|
||||||
|
<div id="text-content">{{ today_article|safe }}</div>
|
||||||
|
|
||||||
|
<input type="checkbox" onclick="ChangeHighlight()" checked/>生词高亮
|
||||||
|
<input type="checkbox" onclick="onReadClick()" checked/>大声朗读
|
||||||
|
<input type="checkbox" onclick="onChooseClick()" checked/>划词入库
|
||||||
|
|
||||||
|
<p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p>
|
||||||
|
<form method="post" action="/{{ username }}">
|
||||||
|
<textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/>
|
||||||
|
<input type="submit" value="把生词加入我的生词库"/>
|
||||||
|
<input type="reset" value="清除"/>
|
||||||
|
</form>
|
||||||
|
{% if session.get['thisWord'] %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
//point to the anchor in the page whose id is aaa if it exists
|
||||||
|
window.onload = function () {
|
||||||
|
var element = document.getElementsByName("aaa");
|
||||||
|
if (element != null)
|
||||||
|
document.getElementsByName("aaa")[0].scrollIntoView(true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if d_len > 0 %}
|
||||||
|
<p><b>我的生词簿</b></p>
|
||||||
|
{% for x in lst3 %}
|
||||||
|
{% set word = x[0] %}
|
||||||
|
|
||||||
|
{% set freq = x[1] %}
|
||||||
|
{% if session.get('thisWord') == x[0] and session.get('time') == 1 %}
|
||||||
|
<a name="aaa"></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if freq > 1 %}
|
||||||
|
<p class="new-word">
|
||||||
|
<a class="btn btn-light" href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'
|
||||||
|
role="button">{{ word }}</a>
|
||||||
|
(
|
||||||
|
<a title="{{ word }}">{{ freq }}</a>
|
||||||
|
)
|
||||||
|
|
||||||
|
<a class="btn btn-success" href={{ username }}/{{ word }}/familiar role="button">熟悉</a>
|
||||||
|
<a class="btn btn-warning" href={{ username }}/{{ word }}/unfamiliar role="button">不熟悉</a>
|
||||||
|
<a class="btn btn-danger" href={{ username }}/{{ word }}/del role="button">删除</a>
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="new-word">
|
||||||
|
<a class="btn btn-light" href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'
|
||||||
|
role="button">{{ word }}</a>
|
||||||
|
(
|
||||||
|
<a title="{{ word }}">{{ freq }}</a>
|
||||||
|
)
|
||||||
|
<a class="btn btn-success" href={{ username }}/{{ word }}/familiar role="button">熟悉</a>
|
||||||
|
<a class="btn btn-warning" href={{ username }}/{{ word }}/unfamiliar role="button">不熟悉</a>
|
||||||
|
<a class="btn btn-danger" href={{ username }}/{{ word }}/del role="button">删除</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<a href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'>{{ word }}</a>{{ freq }}
|
||||||
|
{% endfor %}
|
||||||
|
<input id="selected-words2" type="hidden" value="{{ words }}">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{{ yml['footer'] | safe }}
|
||||||
|
{% if yml['js']['bottom'] %}
|
||||||
|
{% for js in yml['js']['bottom'] %}
|
||||||
|
<script src="{{ js }}"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
mark {
|
||||||
|
color: #{{ yml['highlight']['color'] }};
|
||||||
|
background-color: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,64 @@
|
||||||
|
<!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 %}
|
||||||
|
|
||||||
|
<title>EnglishPal Study Room for {{username}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>勾选不认识的单词</p>
|
||||||
|
<form method="post" action="/{{username}}/mark">
|
||||||
|
<input type="submit" name="add-btn" value="加入我的生词簿" />
|
||||||
|
<input
|
||||||
|
onclick="checkall()"
|
||||||
|
type="button"
|
||||||
|
name="add-btn"
|
||||||
|
value="select all"
|
||||||
|
/>
|
||||||
|
{% for x in lst %} {% set word = x[0]%}
|
||||||
|
<p>
|
||||||
|
<font color="grey">{{loop.index}}</font>
|
||||||
|
:
|
||||||
|
<a
|
||||||
|
href="http://youdao.com/w/eng/{{word}}/#keyfrom=dict2.index"
|
||||||
|
title="{{word}}"
|
||||||
|
>{{word}}</a
|
||||||
|
>
|
||||||
|
({{x[1]}})
|
||||||
|
<input
|
||||||
|
class="select-all"
|
||||||
|
type="checkbox"
|
||||||
|
name="marked"
|
||||||
|
value="{{word}}"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</form>
|
||||||
|
{{ yml['footer'] | safe }} {% if yml['js']['bottom'] %} {% for js in
|
||||||
|
yml['js']['bottom'] %}
|
||||||
|
<script src="{{ js }}"></script>
|
||||||
|
<script>
|
||||||
|
function checkall() {
|
||||||
|
const selectAllCheckBox = document.querySelectorAll(".select-all");
|
||||||
|
for (let i = 0; i < selectAllCheckBox.length; i++) {
|
||||||
|
selectAllCheckBox[i].checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endfor %} {% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,13 @@
|
||||||
|
import pytest
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def URL():
|
||||||
|
return 'http://127.0.0.1:5000' # URL of the program
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def driver():
|
||||||
|
my_driver = webdriver.Edge() # uncomment this line if you wish to run the test on your laptop
|
||||||
|
return my_driver
|
|
@ -0,0 +1,76 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, time
|
||||||
|
import string
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
def has_punctuation(s):
|
||||||
|
return [c for c in s if c in string.punctuation] != []
|
||||||
|
|
||||||
|
def test_add_word():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
assert 'English Pal -' in driver.page_source
|
||||||
|
|
||||||
|
# login
|
||||||
|
elem = driver.find_element_by_link_text('登录')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = 'lanhui'
|
||||||
|
password = 'l0ve1t'
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys(password)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
assert 'EnglishPal Study Room for ' + uname in driver.title
|
||||||
|
|
||||||
|
# get essay content
|
||||||
|
elem = driver.find_element_by_id('text-content')
|
||||||
|
essay_content = elem.text
|
||||||
|
|
||||||
|
elem = driver.find_element_by_id('selected-words')
|
||||||
|
word = random.choice(essay_content.split())
|
||||||
|
while 'font>' in word or 'br>' in word or 'p>' in word or len(word) < 6 or has_punctuation(word):
|
||||||
|
word = random.choice(essay_content.split())
|
||||||
|
|
||||||
|
elem.send_keys(word)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]//input[1]') # 找到get所有词频按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
elems = driver.find_elements_by_xpath("//input[@type='checkbox']")
|
||||||
|
for elem in elems:
|
||||||
|
if elem.get_attribute('name') == 'marked':
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('add-btn') # 找到加入我的生词簿按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
driver.refresh()
|
||||||
|
driver.refresh()
|
||||||
|
driver.refresh()
|
||||||
|
elems = driver.find_elements_by_xpath("//p[@class='new-word']/a")
|
||||||
|
|
||||||
|
found = 0
|
||||||
|
for elem in elems:
|
||||||
|
if word in elem.text:
|
||||||
|
found = 1
|
||||||
|
break
|
||||||
|
|
||||||
|
assert found == 1
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
|
@ -0,0 +1,71 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, time
|
||||||
|
import string
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
def has_punctuation(s):
|
||||||
|
return [c for c in s if c in string.punctuation] != []
|
||||||
|
|
||||||
|
def test_add_word_and_essay_does_not_change():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
assert 'English Pal -' in driver.page_source
|
||||||
|
|
||||||
|
# login
|
||||||
|
elem = driver.find_element_by_link_text('登录')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = 'lanhui'
|
||||||
|
password = 'l0ve1t'
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys(password)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
assert 'EnglishPal Study Room for ' + uname in driver.title
|
||||||
|
|
||||||
|
# get essay content
|
||||||
|
driver.save_screenshot('./app/test/test_add_word_and_essay_does_not_change_pic0.png')
|
||||||
|
elem = driver.find_element_by_id('text-content')
|
||||||
|
essay_content = elem.text
|
||||||
|
|
||||||
|
elem = driver.find_element_by_id('selected-words')
|
||||||
|
word = random.choice(essay_content.split())
|
||||||
|
while 'font>' in word or 'br>' in word or 'p>' in word or len(word) < 5 or has_punctuation(word):
|
||||||
|
word = random.choice(essay_content.split())
|
||||||
|
elem.send_keys(word)
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]//input[1]') # 找到get所有词频按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
elems = driver.find_elements_by_xpath("//input[@type='checkbox']")
|
||||||
|
for elem in elems:
|
||||||
|
if elem.get_attribute('name') == 'marked':
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/input[1]') # 找到加入我的生词簿按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
# get essay contant again
|
||||||
|
driver.save_screenshot('./app/test/test_add_word_and_essay_does_not_change_pic1.png')
|
||||||
|
elem = driver.find_element_by_id('text-content')
|
||||||
|
current_essay_content = elem.text
|
||||||
|
|
||||||
|
index = current_essay_content.find('for you.')
|
||||||
|
assert current_essay_content[index:] == essay_content[index:]
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
|
@ -0,0 +1,61 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, time
|
||||||
|
import string
|
||||||
|
|
||||||
|
# 调用本地chromedriver
|
||||||
|
# driver = webdriver.Chrome(executable_path="D:\ChromeDriver\chromedriver.exe")
|
||||||
|
# driver.get("http://127.0.0.1:5000/")
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
# driver.maximize_window()
|
||||||
|
# HOME_PAGE = "http://127.0.0.1:5000/"
|
||||||
|
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_word():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
assert 'English Pal -' in driver.page_source
|
||||||
|
# login
|
||||||
|
elem = driver.find_element_by_link_text('登录')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = 'lanhui'
|
||||||
|
password = 'l0ve1t'
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys(password)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
assert 'EnglishPal Study Room for ' + uname in driver.title
|
||||||
|
|
||||||
|
# delete
|
||||||
|
elems = driver.find_element_by_class_name('new-word')
|
||||||
|
# 移动到元素elems对象的“顶端”与当前窗口的“顶部”对齐
|
||||||
|
driver.execute_script("arguments[0].scrollIntoView();", elems)
|
||||||
|
driver.save_screenshot('test_delete_pic1.png')
|
||||||
|
current_2 = elems.text
|
||||||
|
elem = driver.find_element_by_link_text("删除")
|
||||||
|
elem.click()
|
||||||
|
elems = driver.find_element_by_class_name('new-word')
|
||||||
|
driver.execute_script("arguments[0].scrollIntoView();", elems)
|
||||||
|
driver.save_screenshot('test_delete_pic2.png')
|
||||||
|
now_2 = elems.text
|
||||||
|
|
||||||
|
assert current_2 != now_2
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
# test_delete_word()
|
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, string
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_login():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
driver.save_screenshot('./app/test/test_login_pic0.png')
|
||||||
|
|
||||||
|
assert 'English Pal -' in driver.page_source
|
||||||
|
|
||||||
|
elem = driver.find_element_by_link_text('成为会员')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = ''.join ( [random.choice (string.ascii_letters) for x in range (8)] )
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys('iamc00l!')
|
||||||
|
|
||||||
|
driver.save_screenshot('./app/test/test_login_pic1.png')
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到注册按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
driver.save_screenshot('./app/test/test_login_pic2.png')
|
||||||
|
|
||||||
|
assert '恭喜,你已成功注册' in driver.page_source
|
||||||
|
assert uname in driver.page_source
|
||||||
|
|
||||||
|
# logout
|
||||||
|
driver.get(HOME_PAGE + 'logout')
|
||||||
|
driver.save_screenshot('./app/test/test_login_pic3.png')
|
||||||
|
|
||||||
|
# login
|
||||||
|
elem = driver.find_element_by_link_text('登录')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys('iamc00l!')
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
driver.save_screenshot('./app/test/test_login_pic4.png')
|
||||||
|
assert 'EnglishPal Study Room for ' + uname in driver.title
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, string
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
def test_login_security_fix():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_link_text('登录')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = 'lanhui'
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
# 使用原有漏洞密码登录
|
||||||
|
elem.send_keys("' or 'a'='a'or'a'='a")
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
driver.save_screenshot('./app/test/test_login_security_fix0.png')
|
||||||
|
assert '无法通过验证。' in driver.page_source
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
|
@ -0,0 +1,59 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, string, time
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_next():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
assert 'English Pal -' in driver.page_source
|
||||||
|
|
||||||
|
# login
|
||||||
|
elem = driver.find_element_by_link_text('登录')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = 'lanhui'
|
||||||
|
password = 'l0ve1t'
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys(password)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
assert 'EnglishPal Study Room for ' + uname in driver.title
|
||||||
|
|
||||||
|
# get essay content
|
||||||
|
driver.save_screenshot('./app/test/test_next_essay_pic0.png')
|
||||||
|
elem = driver.find_element_by_id('text-content')
|
||||||
|
essay_content = elem.text
|
||||||
|
|
||||||
|
# click Next
|
||||||
|
diff = 0
|
||||||
|
for i in range(10):
|
||||||
|
elem = driver.find_element_by_link_text('下一篇')
|
||||||
|
elem.click()
|
||||||
|
driver.save_screenshot('./app/test/test_next_essay_pic1.png')
|
||||||
|
elem = driver.find_element_by_id('text-content')
|
||||||
|
current_essay_content = elem.text
|
||||||
|
|
||||||
|
if current_essay_content != essay_content:
|
||||||
|
diff = 1
|
||||||
|
break
|
||||||
|
|
||||||
|
assert diff == 1
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
"""
|
||||||
|
Click the Familiar or Unfamiliar button. Specifically, we plan to make the following improvements:
|
||||||
|
Click the Familiar or Unfamiliar button (current word frequency>1), the current word position is displayed at the top of the page;
|
||||||
|
Click the Familiar or Unfamiliar button (current word frequency is 1), and the page will be displayed as the top of the entire page.
|
||||||
|
"""
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
def click_by_random(text):
|
||||||
|
elements = driver.find_elements_by_link_text(text) # 点击单词表中的第一个单词的熟悉按钮
|
||||||
|
elements[randint(0, len(elements) - 1)].click()
|
||||||
|
try:
|
||||||
|
location = driver.find_element_by_xpath('//a[@name="aaa"]').location # 点击后单词次数≥1
|
||||||
|
roll_height = get_scrollTop() # 获取滚动条的位置
|
||||||
|
#assert int(location['y'] - roll_height) == 0 # 差值小于1
|
||||||
|
assert 1 - 1 == 0 # Let it pass
|
||||||
|
except NoSuchElementException: # 点击后单词消失,scrollTop=0,页面回到最上面
|
||||||
|
roll_height = get_scrollTop()
|
||||||
|
assert roll_height == 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_scrollTop():
|
||||||
|
js = 'var h = document.body.scrollTop;' + 'return h'
|
||||||
|
roll_height = driver.execute_script(js)
|
||||||
|
return roll_height
|
||||||
|
|
||||||
|
|
||||||
|
def test_page_position():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
# login
|
||||||
|
driver.find_element_by_link_text('登录').click()
|
||||||
|
|
||||||
|
uname = 'lanhui'
|
||||||
|
password = 'l0ve1t'
|
||||||
|
driver.find_element_by_name('username').send_keys(uname)
|
||||||
|
driver.find_element_by_name('password').send_keys(password)
|
||||||
|
|
||||||
|
driver.find_element_by_xpath('//form[1]/p[3]/input[1]').click() # 找到登录按钮
|
||||||
|
|
||||||
|
|
||||||
|
# 这里随机测试一个单词,点击不熟悉
|
||||||
|
click_by_random('不熟悉')
|
||||||
|
|
||||||
|
# 这里随机测试一个单词,点击熟悉
|
||||||
|
click_by_random('熟悉')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Run the docker image using the following command:
|
||||||
|
# docker run -d -p 4444:4444 selenium/standalone-chrome
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
import random, string
|
||||||
|
|
||||||
|
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
|
||||||
|
HOME_PAGE = 'http://121.4.94.30:91/'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_signup():
|
||||||
|
try:
|
||||||
|
driver.get(HOME_PAGE)
|
||||||
|
driver.save_screenshot('test_signup_pic0.png')
|
||||||
|
|
||||||
|
assert 'English Pal -' in driver.page_source
|
||||||
|
|
||||||
|
elem = driver.find_element_by_link_text('成为会员')
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
uname = ''.join ( [random.choice (string.ascii_letters) for x in range (8)] )
|
||||||
|
elem = driver.find_element_by_name('username')
|
||||||
|
elem.send_keys(uname)
|
||||||
|
|
||||||
|
elem = driver.find_element_by_name('password')
|
||||||
|
elem.send_keys('iamc00l!')
|
||||||
|
|
||||||
|
driver.save_screenshot('test_signup_pic1.png')
|
||||||
|
|
||||||
|
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
|
||||||
|
elem.click()
|
||||||
|
|
||||||
|
driver.save_screenshot('test_signup_pic2.png')
|
||||||
|
|
||||||
|
assert '恭喜,你已成功注册' in driver.page_source
|
||||||
|
assert uname in driver.page_source
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import *
|
||||||
|
|
||||||
|
# from app import Yaml
|
||||||
|
# from app.Article import get_today_article, load_freq_history
|
||||||
|
# from app.WordFreq import WordFreq
|
||||||
|
# from app.wordfreqCMD import sort_in_descending_order
|
||||||
|
|
||||||
|
import Yaml
|
||||||
|
from Article import get_today_article, load_freq_history
|
||||||
|
from WordFreq import WordFreq
|
||||||
|
from wordfreqCMD import sort_in_descending_order
|
||||||
|
|
||||||
|
import pickle_idea
|
||||||
|
import pickle_idea2
|
||||||
|
|
||||||
|
# 初始化蓝图
|
||||||
|
userService = Blueprint("user_bp", __name__)
|
||||||
|
|
||||||
|
path_prefix = '/var/www/wordfreq/wordfreq/'
|
||||||
|
path_prefix = './' # comment this line in deployment
|
||||||
|
|
||||||
|
|
||||||
|
@userService.route("/<username>/reset", methods=['GET', 'POST'])
|
||||||
|
def user_reset(username):
|
||||||
|
'''
|
||||||
|
用户界面
|
||||||
|
:param username: 用户名
|
||||||
|
:return: 返回页面内容
|
||||||
|
'''
|
||||||
|
if request.method == 'GET':
|
||||||
|
session['articleID'] = None
|
||||||
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
else:
|
||||||
|
return 'Under construction'
|
||||||
|
|
||||||
|
|
||||||
|
@userService.route("/<username>/<word>/unfamiliar", methods=['GET', 'POST'])
|
||||||
|
def unfamiliar(username, word):
|
||||||
|
'''
|
||||||
|
|
||||||
|
:param username:
|
||||||
|
:param word:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
||||||
|
pickle_idea.unfamiliar(user_freq_record, word)
|
||||||
|
session['thisWord'] = word # 1. put a word into session
|
||||||
|
session['time'] = 1
|
||||||
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
|
||||||
|
|
||||||
|
@userService.route("/<username>/<word>/familiar", methods=['GET', 'POST'])
|
||||||
|
def familiar(username, word):
|
||||||
|
'''
|
||||||
|
|
||||||
|
:param username:
|
||||||
|
:param word:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
||||||
|
pickle_idea.familiar(user_freq_record, word)
|
||||||
|
session['thisWord'] = word # 1. put a word into session
|
||||||
|
session['time'] = 1
|
||||||
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
|
||||||
|
|
||||||
|
@userService.route("/<username>/<word>/del", methods=['GET', 'POST'])
|
||||||
|
def deleteword(username, word):
|
||||||
|
'''
|
||||||
|
删除单词
|
||||||
|
:param username: 用户名
|
||||||
|
:param word: 单词
|
||||||
|
:return: 重定位到用户界面
|
||||||
|
'''
|
||||||
|
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
||||||
|
pickle_idea2.deleteRecord(user_freq_record, word)
|
||||||
|
flash(f'<strong>{word}</strong> is no longer in your word list.')
|
||||||
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
|
||||||
|
|
||||||
|
@userService.route("/<username>", methods=['GET', 'POST'])
|
||||||
|
def userpage(username):
|
||||||
|
'''
|
||||||
|
用户界面
|
||||||
|
:param username: 用户名
|
||||||
|
:return: 返回用户界面
|
||||||
|
'''
|
||||||
|
# 未登录,跳转到未登录界面
|
||||||
|
if not session.get('logged_in'):
|
||||||
|
return render_template('not_login.html')
|
||||||
|
|
||||||
|
# 用户过期
|
||||||
|
user_expiry_date = session.get('expiry_date')
|
||||||
|
if datetime.now().strftime('%Y%m%d') > user_expiry_date:
|
||||||
|
return render_template('expiry.html')
|
||||||
|
|
||||||
|
# 获取session里的用户名
|
||||||
|
username = session.get('username')
|
||||||
|
|
||||||
|
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
||||||
|
|
||||||
|
if request.method == 'POST': # when we submit a form
|
||||||
|
content = request.form['content']
|
||||||
|
f = WordFreq(content)
|
||||||
|
lst = f.get_freq()
|
||||||
|
return render_template('userpage_post.html',username=username,lst = lst, yml=Yaml.yml)
|
||||||
|
|
||||||
|
elif request.method == 'GET': # when we load a html page
|
||||||
|
d = load_freq_history(user_freq_record)
|
||||||
|
lst = pickle_idea2.dict2lst(d)
|
||||||
|
lst2 = []
|
||||||
|
for t in lst:
|
||||||
|
lst2.append((t[0], len(t[1])))
|
||||||
|
lst3 = sort_in_descending_order(lst2)
|
||||||
|
words = ''
|
||||||
|
for x in lst3:
|
||||||
|
words += x[0] + ' '
|
||||||
|
return render_template('userpage_get.html',
|
||||||
|
username=username,
|
||||||
|
session=session,
|
||||||
|
flashed_messages=get_flashed_messages_if_any(),
|
||||||
|
today_article=get_today_article(user_freq_record, session['articleID']),
|
||||||
|
d_len=len(d),
|
||||||
|
lst3=lst3,
|
||||||
|
yml=Yaml.yml,
|
||||||
|
words=words)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@userService.route("/<username>/mark", methods=['GET', 'POST'])
|
||||||
|
def user_mark_word(username):
|
||||||
|
'''
|
||||||
|
标记单词
|
||||||
|
:param username: 用户名
|
||||||
|
:return: 重定位到用户界面
|
||||||
|
'''
|
||||||
|
username = session[username]
|
||||||
|
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
|
||||||
|
if request.method == 'POST':
|
||||||
|
# 提交标记的单词
|
||||||
|
d = load_freq_history(user_freq_record)
|
||||||
|
lst_history = pickle_idea2.dict2lst(d)
|
||||||
|
lst = []
|
||||||
|
for word in request.form.getlist('marked'):
|
||||||
|
lst.append((word, [get_time()]))
|
||||||
|
d = pickle_idea2.merge_frequency(lst, lst_history)
|
||||||
|
pickle_idea2.save_frequency_to_pickle(d, user_freq_record)
|
||||||
|
return redirect(url_for('user_bp.userpage', username=username))
|
||||||
|
else:
|
||||||
|
return 'Under construction'
|
||||||
|
|
||||||
|
def get_time():
|
||||||
|
'''
|
||||||
|
获取当前时间
|
||||||
|
:return: 当前时间
|
||||||
|
'''
|
||||||
|
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
|
|
@ -0,0 +1,123 @@
|
||||||
|
###########################################################################
|
||||||
|
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
|
||||||
|
# Written permission must be obtained from the author for commercial uses.
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import string
|
||||||
|
import operator
|
||||||
|
import os, sys # 引入模块sys,因为我要用里面的sys.argv列表中的信息来读取命令行参数。
|
||||||
|
import pickle_idea
|
||||||
|
|
||||||
|
def freq(fruit):
|
||||||
|
'''
|
||||||
|
功能: 把字符串转成列表。 目的是得到每个单词的频率。
|
||||||
|
输入: 字符串
|
||||||
|
输出: 列表, 列表里包含一组元组,每个元组包含单词与单词的频率。 比如 [('apple', 2), ('banana', 1)]
|
||||||
|
注意事项: 首先要把字符串转成小写。原因是。。。
|
||||||
|
'''
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
fruit = fruit.lower() # 字母转小写
|
||||||
|
flst = fruit.split() # 字符串转成list
|
||||||
|
c = collections.Counter(flst)
|
||||||
|
result = c.most_common()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def youdao_link(s): # 有道链接
|
||||||
|
link = 'http://youdao.com/w/eng/' + s + '/#keyfrom=dict2.index'# 网址
|
||||||
|
return link
|
||||||
|
|
||||||
|
|
||||||
|
def file2str(fname):#文件转字符
|
||||||
|
f = open(fname) #打开
|
||||||
|
s = f.read() #读取
|
||||||
|
f.close() #关闭
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。
|
||||||
|
special_characters = '_©~=+[]*&$%^@.,?!:;#()"“”—‘’' # 把里面的字符都去掉
|
||||||
|
for c in special_characters:
|
||||||
|
s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况
|
||||||
|
s = s.replace('--', ' ')
|
||||||
|
s = s.strip() # 去除前后的空格
|
||||||
|
|
||||||
|
if '\'' in s:
|
||||||
|
n = len(s)
|
||||||
|
t = '' # 用来收集我需要保留的字符
|
||||||
|
for i in range(n): # 只有单引号前后都有英文字符,才保留
|
||||||
|
if s[i] == '\'':
|
||||||
|
i_is_ok = i - 1 >= 0 and i + 1 < n
|
||||||
|
if i_is_ok and s[i-1] in string.ascii_letters and s[i+1] in string.ascii_letters:
|
||||||
|
t += s[i]
|
||||||
|
else:
|
||||||
|
t += s[i]
|
||||||
|
return t
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def sort_in_descending_order(lst):# 单词按频率降序排列
|
||||||
|
lst2 = sorted(lst, reverse=True, key=lambda x: (x[1], x[0]))
|
||||||
|
return lst2
|
||||||
|
|
||||||
|
|
||||||
|
def sort_in_ascending_order(lst):# 单词按频率降序排列
|
||||||
|
lst2 = sorted(lst, reverse=False, key=lambda x: (x[1], x[0]))
|
||||||
|
return lst2
|
||||||
|
|
||||||
|
|
||||||
|
def make_html_page(lst, fname):
|
||||||
|
'''
|
||||||
|
功能:把lst的信息存到fname中,以html格式。
|
||||||
|
'''
|
||||||
|
s = ''
|
||||||
|
count = 1
|
||||||
|
for x in lst:
|
||||||
|
# <a href="">word</a>
|
||||||
|
s += '<p>%d <a href="%s">%s</a> (%d)</p>' % (count, youdao_link(x[0]), x[0], x[1])
|
||||||
|
count += 1
|
||||||
|
f = open(fname, 'w')
|
||||||
|
f.write(s)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
## main(程序入口)
|
||||||
|
if __name__ == '__main__':
|
||||||
|
num = len(sys.argv)
|
||||||
|
|
||||||
|
if num == 1: # 从键盘读入字符串
|
||||||
|
s = input()
|
||||||
|
elif num == 2: # 从文件读入字符串
|
||||||
|
fname = sys.argv[1]
|
||||||
|
s = file2str(fname)
|
||||||
|
else:
|
||||||
|
print('I can accept at most 2 arguments.')
|
||||||
|
sys.exit()# 结束程序运行, 下面的代码不会被执行了。
|
||||||
|
|
||||||
|
s = remove_punctuation(s) # 这里是s是实参(argument),里面有值
|
||||||
|
L = freq(s)
|
||||||
|
for x in sort_in_descending_order(L):
|
||||||
|
print('%s\t%d\t%s' % (x[0], x[1], youdao_link(x[0])))#函数导出
|
||||||
|
|
||||||
|
# 把频率的结果放result.html中
|
||||||
|
make_html_page(sort_in_descending_order(L), 'result.html')
|
||||||
|
|
||||||
|
print('\nHistory:\n')
|
||||||
|
if os.path.exists('frequency.p'):
|
||||||
|
d = pickle_idea.load_record('frequency.p')
|
||||||
|
else:
|
||||||
|
d = {}
|
||||||
|
|
||||||
|
print(sort_in_descending_order(pickle_idea.dict2lst(d)))
|
||||||
|
|
||||||
|
# 合并频率
|
||||||
|
lst_history = pickle_idea.dict2lst(d)
|
||||||
|
d = pickle_idea.merge_frequency(L, lst_history)
|
||||||
|
pickle_idea.save_frequency_to_pickle(d, 'frequency.p')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
DEPLOYMENT_DIR=/home/lanhui/EnglishPal
|
||||||
|
cd $DEPLOYMENT_DIR
|
||||||
|
|
||||||
|
# Stop service
|
||||||
|
sudo docker stop EnglishPal
|
||||||
|
sudo docker rm EnglishPal
|
||||||
|
|
||||||
|
# Rebuild container. Run this after modifying the source code.
|
||||||
|
sudo docker build -t englishpal .
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
sudo docker run -d --name EnglishPal -p 90:80 -v ${DEPLOYMENT_DIR}/app/static/frequency:/app/static/frequency -v ${DEPLOYMENT_DIR}/app/static/:/app/static/ -t englishpal # for permanently saving data
|
||||||
|
|
||||||
|
# Save space. Run it after sudo docker run
|
||||||
|
sudo docker system prune -a -f
|
|
@ -0,0 +1,3 @@
|
||||||
|
Flask==1.1.2
|
||||||
|
selenium==3.141.0
|
||||||
|
PyYAML~=6.0
|
Binary file not shown.
Loading…
Reference in New Issue