├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── LICENSE
├── README.md
├── WordReview
├── Makefile
├── WordReview
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── apps
│ ├── review
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── migrations
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── src
│ │ │ ├── init_db.py
│ │ │ └── spider.py
│ │ ├── templates
│ │ │ ├── calendar.pug
│ │ │ ├── homepage.pug
│ │ │ ├── import_db.pug
│ │ │ └── review.pug
│ │ ├── tests.py
│ │ ├── urls.py
│ │ └── views.py
│ └── src
│ │ └── util.py
├── config.py
├── config_sample.conf
├── installer.py
├── manage.py
├── static
│ ├── css
│ │ ├── base.css
│ │ ├── calendar.css
│ │ └── thirdParty
│ │ │ ├── bootstrap.min.css
│ │ │ └── bootstrap.min.css.map
│ ├── font
│ │ └── fontawesome
│ │ │ ├── FontAwesome.otf
│ │ │ ├── font-awesome.min.css
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ └── fontawesome-webfont.woff
│ ├── js
│ │ ├── calendar.js
│ │ ├── echarts.min.js
│ │ ├── homepage.js
│ │ ├── review.js
│ │ ├── thirdParty
│ │ │ ├── hotkeys.min.js
│ │ │ ├── jquery
│ │ │ │ ├── jquery-3.5.1.min.js
│ │ │ │ └── jquery.tmpl.min.js
│ │ │ └── layer
│ │ │ │ ├── layer.js
│ │ │ │ ├── mobile
│ │ │ │ ├── layer.js
│ │ │ │ └── need
│ │ │ │ │ └── layer.css
│ │ │ │ └── theme
│ │ │ │ └── default
│ │ │ │ ├── icon-ext.png
│ │ │ │ ├── icon.png
│ │ │ │ ├── layer.css
│ │ │ │ ├── loading-0.gif
│ │ │ │ ├── loading-1.gif
│ │ │ │ └── loading-2.gif
│ │ └── util.js
│ ├── media
│ │ ├── muyi.png
│ │ └── vocabulary.png
│ └── scss
│ │ └── review.scss
└── templates
│ └── base.pug
├── data
└── sample
│ ├── sample.csv
│ └── sample.xlsx
├── doc
├── CHANGELOG.md
├── database_init.md
├── demo.js
├── img
│ ├── demo1.png
│ ├── demo2.png
│ ├── demo_calendar.png
│ └── demo_homepage.png
└── install.md
├── extension
├── img
│ └── vocabulary.png
├── js
│ ├── background.js
│ ├── content-script.js
│ ├── jquery-3.5.1.min.js
│ ├── options.js
│ ├── popup.js
│ └── util.js
├── manifest.json
├── options.html
└── popup.html
├── index.html
└── requirements.txt
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: 报告问题
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 |
10 |
11 |
12 |
13 | - [ ] 我已阅读[READEME.md](https://github.com/Benature/WordReview/blob/ben/README.md)中的[问题自检](https://github.com/Benature/WordReview/blob/ben/README.md#%E9%97%AE%E9%A2%98%E8%87%AA%E6%A3%80)
14 |
15 | **描述问题**
16 | 请简明阐述您的问题
17 |
18 | **复现问题**
19 | 复现问题的步骤:
20 |
21 |
22 |
23 | 1.
24 | 2.
25 |
26 | ```
27 | 请将报错信息复制到此处
28 | ```
29 |
30 | **截图:**(可选)
31 |
32 |
33 |
34 |
35 | **操作环境(请提供以下完整数据):**
36 |
37 | - 操作系统:mac / windows
38 | - 使用方式:源码 / 可执行文件(即在 release 页面下载的)
39 | - 可执行文件版本(若是):如 0.2.0
40 | - 数据库类型:sqlite(默认) / mysql
41 | - 浏览器:Chrome / Firefox / Edge / etc.
42 |
43 | **额外信息:**(可选)
44 | 任何其他的您认为有助于能描述问题/解决问题的信息。
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: 新功能建议
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 |
10 |
11 | **功能描述**
12 |
13 |
14 | **希望这个功能可以解决:**(可选)
15 |
16 |
17 | **额外的信息**(可选)
18 | 任何其他的您认为能描述该功能的信息。
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | /.idea/
3 | /.vscode/
4 |
5 | **/__pycache__/
6 | **/.ipynb_checkpoints/
7 |
8 | # django
9 | **/migrations/*
10 | !**/migrations/__init__.py
11 | staticsfile/
12 | uwsgi.out
13 | logs/
14 | /media/
15 |
16 | # config
17 | WordReview/config.conf
18 |
19 | # PyInstaller
20 | WordReview/build/
21 | WordReview/dist/
22 | *.spec
23 |
24 | # local
25 | data/
26 | *.sqlite3
27 | /backup/
28 | /pypi/
29 | WordReview.wiki/
30 | *_ig.*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Word Review 单词复习
5 |
6 | 
7 | 
8 | 
9 | 
10 | 
11 |
12 |
13 |
14 |
15 | Django + MySQL + Pug + JS
16 |
17 | - Python 3.7+
18 | - Django 3
19 | - Mysql 8 / sqlite 3
20 |
21 | ---
22 |
23 | - DEMO
24 | - 二月的时候简单录了一个 DEMO 视频,上传到了[B 站](https://mp.weixin.qq.com/s/zOmpevAUafFY5kPGYr65uA),欢迎康康。
25 | - 还有一个[在线试玩](https://benature.github.io/WordReview/),可以先感受一下。
26 | _在线体验的版本对应`master`分支,现在默认显示的是`ben`分支(开发分支)_
27 |
31 | - 资瓷一下呗 😋
32 | 如果觉得还不错的话,不如在右上方点个 stars🌟 呗( ̄ ▽  ̄)~
33 | 如果童鞋有兴趣的话希望可以一起开发新功能呀 ٩(๑>◡<๑)۶
34 |
35 |
36 |
37 | - 回复慢 QAQ
38 | - 由于本人精力有线,回复可能不会很及时,(或者漏看了邮件),还请见谅orz
39 |
40 | ---
41 |
42 | [前言](#前言)
43 | [安装指引](#安装)
44 | [使用说明](#使用)
45 | [问题自检](#问题自检)
46 | [更新日志](#更新日志)
47 |
48 | ## 前言
49 |
50 | 此项目主要是将`Excel背单词`方法给 App 化,更符合用户操作习惯。
51 | 第一次听说`Excel背单词`这个方法是看了[红专学姐](https://www.zhihu.com/people/you-hong-you-zhuan-ai-dang-wu-si-qing-nian)的[文章](https://zhuanlan.zhihu.com/p/100104481),后来在[B 站](https://www.bilibili.com/video/av46223252/)看到了更详细的讲解,几天后这个项目便诞生了。
52 |
53 | 第一篇[介绍推送](https://mp.weixin.qq.com/s/zOmpevAUafFY5kPGYr65uA)微信公众号「恰好恰好」上发送了,这里就先不展开讲了。
54 |
55 | 功能特性太多,写在这就太长了,新开一页写[特性说明](https://benature.notion.site/Word-Review-c7e1ab07e7d54a249f461248426dfd08)。
56 |
57 |
58 |
59 |
单词复习页
60 |
61 |
62 |
63 |
64 | 艾宾浩斯日历 & 主页
65 |
66 |
67 |
68 |
69 |
70 |
71 | _蓝条是历史记忆率,绿条是上一轮的记忆率_
72 | _关于两种进度条的具体解释见[此处](#list-%E7%9A%84%E8%AE%B0%E5%BF%86%E7%8E%87)_
73 |
74 |
75 |
76 | ## 安装
77 |
78 | > 对于小白可能还需要一些预备说明,请看[这里](https://benature.notion.site/Word-Review-9046ae4330ff49198c39491602064f3e)
79 |
80 | 命令行输入
81 |
82 | ```shell
83 | git clone https://github.com/Benature/WordReview.git
84 | ```
85 |
86 | 或者点击右上角的`Clone or Download`的绿色按钮。
87 |
88 | 详细的安装指引写的有点长,请点击[这里](doc/install.md)查看,数据库初始化看[这里](doc/database_init.md)。
89 |
90 | > 如果你实在不想折腾配置的话,可以在[这里](https://github.com/Benature/WordReview/releases)直接安装可执行文件。(但不推荐)
91 |
92 | ## 使用
93 |
94 | ```shell
95 | conda activate # 小白流程不用这条命令
96 | python manage.py runserver
97 | ```
98 |
99 | 默认情况下会自动在默认浏览器打开,开始背单词之旅吧 🤓
100 |
101 | 当您想要更新代码的时候,请
102 |
103 | ```shell
104 | git pull
105 | python manage.py makemigrations
106 | python manage.py migrate
107 | ```
108 |
109 |
110 | ### 快捷键
111 |
112 | | 操作 | 快捷键 | 页面 | 状态 |
113 | | :--------------------: | :----------------------------------------------------------: | :------: | :--------------: |
114 | | 设为重难词 | Shift+H (Hard) | 复习页面 | 全局 |
115 | | 设为已掌握 | Shift+G (Get) | 复习页面 | 全局 |
116 | | 设为很熟悉 | Shift+F (Familiar) | 复习页面 | 全局 |
117 | | 设为太简单 | Shift+E (Easy) | 复习页面 | 全局 |
118 | | 进入笔记输入框 | N (Note) | 复习页面 | 全局 |
119 | | 跳转查看助记法(中) | T (Tips) / V (View) | 复习页面 | 全局 |
120 | | 跳转查看助记法(英) | M (Mnemonic) | 复习页面 | 全局 |
121 | | 跳转查看近义词 | S (Synonyms) | 复习页面 | 全局 |
122 | | 词卡前后切换 | <、> | 复习页面 | 全局 |
123 | | List 前后切换 | Shift+<、Shift+> | 复习页面 | 学习状态 |
124 | | 查看释义 | 空格 | 复习页面 | 复习状态 |
125 | | 切换至学习状态 | P (Preview) | 复习页面 | 复习状态 |
126 | | 触发重现模式 | R (Repeat) | 复习页面 | 复习状态 |
127 | | 触发输入模式(实验中) | I (Input) | 复习页面 | 复习状态 |
128 | | 我记得 | Shift+→ | 复习页面 | 复习状态 |
129 | | 不认识 | Shift+← | 复习页面 | 复习状态 |
130 | | 复制`WordSand`助记法 | C (Copy) | 复习页面 | 安装 Chrome 插件 |
131 | | 跳转到日历页面 | C (Calendar) | 主页 | - |
132 | | 跳转到昨日重现 | Y (Yesterday) | 主页 | - |
133 |
134 | ### 词根词缀词源拆词渲染
135 |
136 | 1. 【推荐】等号`=`与回车作为标记符,detain 为例:
137 |
138 | ```txt
139 | de=down
140 | tain
141 | ```
142 |
143 | 2. 以中文括号与`+`标识,temerity 为例:
144 |
145 | ```txt
146 | temer(轻率)+ity
147 | ```
148 |
149 | ### List 的记忆率
150 |
151 | - 蓝条:历史记忆率,对 List 内单词的总记忆率取平均
152 | - 绿条:上轮记忆率,按 List 内单词的 **最新两次** 记忆情况计算平均记忆率
153 |
154 | ### 单词的`flag`
155 |
156 | - 太简单:✅ 打钩,下次背词不再出现,统计记忆率时视为 `1`
157 | - 很熟悉:☁️ 浮云,下次背词不再出现,统计记忆率时视为 `1`
158 | - 已掌握:🟢 绿灯,下次背词仍然出现,统计记忆率时视为 `1`
159 | - 重难词:⭐️ 标星
160 |
161 | ---
162 |
163 |
164 | ### 问题自检
165 | 如果遇到问题,请先查看这几处是否有报错信息
166 | - 浏览器的 Console (F12)
167 | - 启动`python manage.py runserver`的命令行
168 |
169 | 如果导入数据出现问题,请先尝试导入本仓库提供的示例数据。如果示例数据导入成功,很有可能是自定义数据哪里有误。
170 |
171 | 请先尝试根据上方得到的信息自行检索尝试,如若仍未解决,可以 issue 提出。
172 |
173 |
174 | ### 更新日志
175 | 参见 [CHANGELOG.md](./doc/CHANGELOG.md)
176 |
177 | ### 赞赏
178 |
179 | 如果觉得有帮助的话,可以赏点让孩子在饭堂多打份肉呦,感谢~
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/WordReview/Makefile:
--------------------------------------------------------------------------------
1 | run: activate
2 | python manage.py runserver
3 |
4 | migrate:
5 | python manage.py makemigrations
6 | python manage.py migrate
7 |
8 | activate:
9 | conda activate word
--------------------------------------------------------------------------------
/WordReview/WordReview/__init__.py:
--------------------------------------------------------------------------------
1 | from config import config
2 | if config.get('custom', 'db_type') == 'mysql':
3 | import pymysql
4 | pymysql.install_as_MySQLdb()
5 | # import pymysql
6 | # pymysql.install_as_MySQLdb()
7 |
--------------------------------------------------------------------------------
/WordReview/WordReview/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for WordReview project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'WordReview.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/WordReview/WordReview/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for WordReview project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.0.3.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.0/ref/settings/
11 | """
12 |
13 | import os
14 | import sys
15 | from config import config
16 |
17 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19 | sys.path.insert(0, os.path.join(BASE_DIR, "apps"))
20 |
21 | # Quick-start development settings - unsuitable for production
22 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
23 |
24 | # SECURITY WARNING: keep the secret key used in production secret!
25 | SECRET_KEY = '6-1t%wpwkpxl=an0h%2_8^n0s^xpwq+3!a^#b=6ik9n1wki9f6'
26 |
27 | # SECURITY WARNING: don't run with debug turned on in production!
28 | DEBUG = True
29 |
30 | ALLOWED_HOSTS = []
31 |
32 |
33 | # Application definition
34 | sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
35 | INSTALLED_APPS = [
36 | 'django.contrib.admin',
37 | 'django.contrib.auth',
38 | 'django.contrib.contenttypes',
39 | 'django.contrib.sessions',
40 | 'django.contrib.messages',
41 | 'django.contrib.staticfiles',
42 | 'sass_processor',
43 | 'apps.review',
44 | ]
45 |
46 | MIDDLEWARE = [
47 | 'django.middleware.security.SecurityMiddleware',
48 | 'django.contrib.sessions.middleware.SessionMiddleware',
49 | 'django.middleware.common.CommonMiddleware',
50 | 'django.middleware.csrf.CsrfViewMiddleware',
51 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
52 | 'django.contrib.messages.middleware.MessageMiddleware',
53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
54 | ]
55 |
56 | ROOT_URLCONF = 'WordReview.urls'
57 |
58 | TEMPLATES = [
59 | {
60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
61 | 'DIRS': [
62 | os.path.join(BASE_DIR, "templates")
63 | ],
64 | # 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | ],
72 | # PyPugJS part:
73 | 'loaders': [
74 | ('pypugjs.ext.django.Loader', (
75 | 'django.template.loaders.filesystem.Loader',
76 | 'django.template.loaders.app_directories.Loader',
77 | ))
78 | ],
79 | 'builtins': [
80 | 'pypugjs.ext.django.templatetags',
81 | ],
82 | },
83 | },
84 | ]
85 |
86 | WSGI_APPLICATION = 'WordReview.wsgi.application'
87 |
88 |
89 | # Database
90 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
91 |
92 | db_type = config.get('custom', 'db_type')
93 | if db_type == 'mysql':
94 | DATABASES = {
95 | 'default': {
96 | 'ENGINE': 'django.db.backends.mysql',
97 | 'NAME': config.get('mysql', 'mysql_name', fallback='word_db'),
98 | 'USER': config.get('mysql', 'mysql_user', fallback='word_user'),
99 | 'PASSWORD': config.get('mysql', 'mysql_password', fallback='word2020'),
100 | 'HOST': config.get('mysql', 'mysql_host', fallback='localhost'),
101 | 'PORT': config.get('mysql', 'mysql_port', fallback=''),
102 | 'OPTIONS': {'charset': 'utf8mb4'},
103 | }
104 | }
105 | elif db_type == 'sqlite':
106 | DATABASES = {
107 | 'default': {
108 | 'ENGINE': 'django.db.backends.sqlite3',
109 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
110 | }
111 | }
112 | else:
113 | raise ValueError(f'请选择正确的数据库:`mysql`、`sqlite`,而非{db_type}')
114 |
115 | # Password validation
116 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
117 |
118 | AUTH_PASSWORD_VALIDATORS = [
119 | {
120 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
121 | },
122 | {
123 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
124 | },
125 | {
126 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
127 | },
128 | {
129 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
130 | },
131 | ]
132 |
133 |
134 | # Internationalization
135 | # https://docs.djangoproject.com/en/3.0/topics/i18n/
136 |
137 | LANGUAGE_CODE = 'zh-hans'
138 |
139 | TIME_ZONE = 'Asia/Shanghai'
140 |
141 | USE_I18N = True
142 |
143 | USE_L10N = True
144 |
145 | USE_TZ = True
146 |
147 |
148 | # Static files (CSS, JavaScript, Images)
149 | # https://docs.djangoproject.com/en/3.0/howto/static-files/
150 |
151 | STATIC_URL = '/static/'
152 | STATICFILES_DIRS = [
153 | os.path.join(BASE_DIR, "static"),
154 | ]
155 |
156 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticsfile')
157 | SASS_PROCESSOR_ROOT = STATIC_ROOT
158 | STATICFILES_FINDERS = [
159 | 'django.contrib.staticfiles.finders.FileSystemFinder',
160 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
161 | 'sass_processor.finders.CssFinder',
162 | ]
163 |
--------------------------------------------------------------------------------
/WordReview/WordReview/urls.py:
--------------------------------------------------------------------------------
1 | """WordReview URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import static
17 | from django.contrib import admin
18 | from django.urls import path, include
19 | from django.views.generic.base import RedirectView
20 | from WordReview import settings
21 |
22 | urlpatterns = [
23 | path('admin/', admin.site.urls),
24 | path('', include('apps.review.urls')),
25 | path(r'favicon.ico', RedirectView.as_view(
26 | url='/static/media/vocabulary.png')),
27 |
28 | ]
29 | urlpatterns += static.static(settings.STATIC_URL,
30 | document_root=settings.STATIC_ROOT)
31 |
--------------------------------------------------------------------------------
/WordReview/WordReview/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for WordReview project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'WordReview.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/WordReview/apps/review/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/apps/review/__init__.py
--------------------------------------------------------------------------------
/WordReview/apps/review/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/WordReview/apps/review/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ReviewConfig(AppConfig):
5 | name = 'review'
6 |
--------------------------------------------------------------------------------
/WordReview/apps/review/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/apps/review/migrations/__init__.py
--------------------------------------------------------------------------------
/WordReview/apps/review/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | import django.utils.timezone as timezone
3 |
4 | import uuid
5 |
6 | flag_choices = (
7 | (1, '太简单'),
8 | (0, 'default'),
9 | (-1, '重难词'),
10 | )
11 |
12 |
13 | class Review(models.Model):
14 | '''复习单词表'''
15 | word = models.CharField('英文单词', max_length=50, unique=False)
16 | # mean = models.CharField('中文释义', max_length=500, default='')
17 | total_num = models.IntegerField('复习总次数', default=0)
18 | forget_num = models.IntegerField('忘记次数', default=0)
19 | rate = models.FloatField(
20 | '单词遗忘率', default=-1, null=False)
21 | LIST = models.IntegerField('list', default=0)
22 | UNIT = models.IntegerField('unit', default=0)
23 | INDEX = models.IntegerField('index', default=0)
24 | BOOK = models.CharField('单词书', max_length=20, default='')
25 | history = models.CharField('记忆历史', max_length=100,
26 | default='') # 10100101
27 | flag = models.IntegerField('tag', default=0, choices=flag_choices)
28 |
29 | class Meta:
30 | db_table = 'review'
31 | ordering = ('LIST', 'UNIT', 'INDEX')
32 |
33 |
34 | class Books(models.Model):
35 | '''单词书清单'''
36 | index_choices = (
37 | (0, '从0开始'),
38 | (1, '从1开始'),
39 | )
40 | BOOK = models.CharField(
41 | '单词书', max_length=20, default='Unknow_Book', unique=True)
42 | BOOK_zh = models.CharField(
43 | '单词书中文', max_length=20, default='未知书本', unique=True)
44 | BOOK_abbr = models.CharField(
45 | '单词书简写', max_length=20, default='❔')
46 | uuid = models.UUIDField(
47 | 'uuid', default=uuid.uuid4, editable=False, unique=True)
48 | create_time = models.DateTimeField(
49 | '创建时间', default=timezone.now, editable=False)
50 | begin_index = models.IntegerField('列表开始是1还是0', default=0)
51 | hide = models.BooleanField('是否隐藏', default=False)
52 |
53 | class Meta:
54 | db_table = 'books'
55 | ordering = ('create_time',)
56 |
57 |
58 | class BookList(models.Model):
59 | '''单词书的 List 信息'''
60 | modify_time = models.DateTimeField('上次修改时间', auto_now=True)
61 | BOOK = models.CharField('单词书', max_length=20, default='UnknowBook')
62 | LIST = models.IntegerField('list', default=-1)
63 | # last_review_date = models.CharField('上次复习时间(艾宾浩斯安排)', max_length=10, default='')
64 | review_dates = models.CharField(
65 | '所有复习日期(仅记录艾宾浩斯复习曲线时间)', max_length=100, default='')
66 | review_dates_plus = models.CharField(
67 | '自愿复习日期(非艾宾浩斯复习安排)', max_length=1000, default='')
68 | list_uuid = models.UUIDField(
69 | 'uuid', default=uuid.uuid4, editable=False, unique=True)
70 | list_rate = models.FloatField('表记忆率', default=-1)
71 | recent_list_rate = models.FloatField('近期表记忆率', default=-1)
72 | review_word_counts = models.CharField(
73 | 'list 内单词复习次数(分号分隔)set', max_length=100, default='')
74 | word_num = models.IntegerField('list 内的单词数目', default=-1)
75 | ebbinghaus_counter = models.IntegerField('艾宾浩斯复习次数', default=0)
76 | unlearned_num = models.IntegerField('仍需复习单词数', default=-1)
77 |
78 | class Meta:
79 | db_table = 'book_list'
80 | ordering = ('BOOK', 'LIST', 'modify_time')
81 |
82 |
83 | class Words(models.Model):
84 | '''单纯的单词表'''
85 | modify_time = models.DateTimeField('上次修改时间', auto_now=True)
86 | word = models.CharField('英文单词', max_length=50, unique=True)
87 | mean = models.CharField('中文释义', max_length=500, default='')
88 | note = models.CharField('记忆法', max_length=200, default='')
89 | total_num = models.IntegerField('复习总次数', default=0)
90 | forget_num = models.IntegerField('忘记次数', default=0)
91 | last_forget_num = models.IntegerField('上次复习错误次数', default=0)
92 | sentence = models.CharField('例句', default='', max_length=800)
93 | rate = models.FloatField(
94 | '单词遗忘率', default=-1, null=False)
95 | history = models.CharField('记忆历史', max_length=100,
96 | default='') # 10100101
97 | flag = models.IntegerField('tag', default=0, choices=flag_choices)
98 | webster = models.BooleanField('是否被WebsterBuilder收录', default=False)
99 | mnemonic = models.CharField('助记法', max_length=500, default='')
100 | phonetic = models.CharField('音标', max_length=50, default='')
101 | antonym = models.CharField('反义词', max_length=500, default='')
102 | synonym = models.CharField('近义词', max_length=800, default='')
103 | derivative = models.CharField('派生词', max_length=300, default='')
104 |
105 | class Meta:
106 | db_table = 'words'
107 | ordering = ('word',)
108 |
--------------------------------------------------------------------------------
/WordReview/apps/review/src/init_db.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from pandas import read_excel
3 | # import pandas as pd
4 | import config
5 |
6 | # read_excel = pd.read_excel
7 | # bookName = config.BOOK # 请用英文
8 | # List_begin_num = config.begin_index # 或 1,看你的 List 是从 0 开始还是 1 开始
9 |
10 | '''
11 | 如果是 excel/csv,你应该有以下列
12 | word 单词
13 | mean 中文
14 | List
15 | Unit
16 | Index
17 | '''
18 |
19 | # path = config.excel_path
20 | # df = read_excel(path)
21 |
22 |
23 | def check_df(df):
24 | '''检查导入文件表头是否完整或符合名字要求'''
25 | cols = df.columns
26 | checklist = ['word', 'List', 'Index']
27 | vacant = []
28 | for ci in checklist:
29 | if ci not in cols:
30 | vacant.append(ci)
31 | return vacant
32 |
33 |
34 | def import_word(Review, df, bookName):
35 | '''导入 df 到 Review 数据库中'''
36 | print('begin import db Review')
37 | optional_keys = ['Unit']
38 | for key in optional_keys:
39 | if key not in df.columns:
40 | optional_keys.remove(key)
41 | print('optional keys', optional_keys)
42 |
43 | for i in range(0, (len(df))):
44 | dr = df.iloc[i]
45 | review_db = {
46 | 'word': dr['word'],
47 | 'LIST': dr['List'],
48 | # 'UNIT': dr['Unit'],
49 | 'INDEX': dr['Index'],
50 | 'BOOK': bookName,
51 | }
52 | for key in optional_keys:
53 | review_db[key.upper()] = dr[key]
54 | print(i, dr['word'])
55 |
56 | new_word = Review.objects.create(**review_db)
57 | new_word.save()
58 |
59 | print('finish import db Review')
60 |
61 |
62 | def init_db_booklist(BookList, Review, bookName, List_begin_num):
63 | '''导入单词书 List 信息'''
64 | print('begin import db BookList')
65 | for l in range(List_begin_num, List_begin_num + len(set(Review.objects.filter(BOOK=bookName).values_list('LIST')))):
66 | ld = Review.objects.filter(BOOK=bookName, LIST=l) # list data
67 | if len(ld) == 0:
68 | print(f"List{l} has no content")
69 | continue
70 | print(l)
71 | # rate = sum([r[0] if r[0] is not None else 1 for r in ld.values_list('rate')
72 | # ]) / len(ld) * 1
73 | # rate = 1 - rate if rate != 0.0 else 0
74 | data = {
75 | 'LIST': l,
76 | 'BOOK': bookName,
77 | # 'list_rate': rate,
78 | 'word_num': len(ld),
79 | # 'review_word_counts': ';'.join(
80 | # set([str(t[0]) for t in ld.values_list('total_num')])),
81 | }
82 | BookList.objects.create(**data)
83 | print('finish import db BookList')
84 |
85 |
86 | def init_db_words(Review, Words, df):
87 | print('begin import db Words')
88 | # path = config.excel_path
89 | # df = read_excel(path)
90 | optional_keys = ['sentence', 'mnemonic',
91 | 'phonetic', 'antonym', 'synonym', 'derivative']
92 | for key in optional_keys[::-1]:
93 | if key not in df.columns.tolist():
94 | optional_keys.remove(key)
95 | print('optional keys', optional_keys)
96 |
97 | for i in range(0, (len(df))):
98 | dr = df.iloc[i]
99 | try:
100 | word = Words.objects.get(word=dr['word'])
101 | print('skip because word already exists', dr['word'])
102 | continue
103 | except:
104 | data = {
105 | 'word': dr['word'],
106 | 'mean': dr['mean'],
107 | }
108 | for key in optional_keys:
109 | data[key] = dr[key]
110 | word = Words.objects.create(**data)
111 | print(word.word)
112 | word.save()
113 | print('finish import db Words')
114 |
115 |
116 | def init_db_books(Books, BOOK, BOOK_zh, BOOK_abbr, begin_index, hide=False):
117 | print('begin import db Books')
118 | data = {
119 | 'BOOK': BOOK,
120 | 'BOOK_zh': BOOK_zh,
121 | 'BOOK_abbr': BOOK_abbr,
122 | 'begin_index': begin_index,
123 | 'hide': hide,
124 | }
125 | Books.objects.create(**data).save()
126 | print('finish import db Books')
127 |
128 |
129 | def init_db(BOOK, BOOK_zh, BOOK_abbr, begin_index, excel_path, Books, Review, BookList, Words):
130 | df = read_excel(excel_path)
131 | vacant = check_df(df)
132 | if len(vacant) != 0:
133 | raise Exception(f"缺表头: {vacant}")
134 | init_db_words(Review, Words, df)
135 | import_word(Review, df, BOOK)
136 | init_db_booklist(BookList, Review, BOOK, begin_index)
137 | init_db_books(Books, BOOK, BOOK_zh, BOOK_abbr, begin_index)
138 |
139 |
140 | def update_db(Words):
141 | fail = []
142 | df = pd.read_csv('data/xxxx.csv')
143 | df = df.fillna('')
144 | for i, data in enumerate(df.iloc):
145 | # if i < 2500:
146 | # continue
147 | print(i, data['word'], end='|')
148 | # break
149 | try:
150 | word = Words.objects.get(word=data['word'])
151 | except:
152 | print(data['word'])
153 | print('\nunfound word', data['word'])
154 | fail.append(data['word'])
155 | # break
156 | continue
157 | word.antonym = data['antonym']
158 | word.synonym = data['synonyms']
159 | word.derivative = data['derivative']
160 | word.save()
161 | print(word.word)
162 | # break
163 | print('fail counts', len(fail), '/', len(df))
164 | print(set(fail))
165 |
--------------------------------------------------------------------------------
/WordReview/apps/review/src/spider.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from bs4 import BeautifulSoup as bs
3 | import re
4 |
5 |
6 | def crawl_other_dict(word, url):
7 | '''crawl data from other dictionary sites'''
8 | if url == "http://dict.cn/mini.php":
9 | try:
10 | soup = bs(requests.get(
11 | f"http://dict.cn/mini.php?q={word}").text, 'lxml')
12 | out = re.sub(r'|