├── app
├── __init__.py
├── local_settings.py.sample
├── static
│ ├── css
│ │ ├── index.css
│ │ ├── login.css
│ │ ├── base.css
│ │ └── jquery.dataTables.css
│ └── js
│ │ ├── popper.min.js
│ │ ├── bootstrap.min.js
│ │ └── jquery-3.4.1.slim.min.js
├── templates
│ ├── course_student.html
│ ├── user_list.html
│ ├── new_content.html
│ ├── cluster.html
│ ├── login.html
│ ├── course.html
│ ├── course_list.html
│ ├── register.html
│ ├── index.html
│ ├── profile.html
│ ├── base.html
│ └── macro.html
├── fake_db.py
├── main.py
└── model.py
├── env-var.sh
├── requirements.txt
├── LICENSE
├── README.md
└── .gitignore
/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/env-var.sh:
--------------------------------------------------------------------------------
1 | # For development only
2 | export FLASK_APP=main.py
3 | export FLASK_DEBUG=1
4 |
5 |
--------------------------------------------------------------------------------
/app/local_settings.py.sample:
--------------------------------------------------------------------------------
1 | MYSQL_HOST = 'localhost'
2 | MYSQL_USER = 'root'
3 | MYSQL_PASSWORD = '1234'
4 | MYSQL_DB = 'lms'
5 |
--------------------------------------------------------------------------------
/app/static/css/index.css:
--------------------------------------------------------------------------------
1 | .course-link {
2 | text-decoration: none;
3 | }
4 |
5 | .course-link:hover {
6 | text-decoration: none;
7 | }
--------------------------------------------------------------------------------
/app/static/css/login.css:
--------------------------------------------------------------------------------
1 | .form-signin {
2 | width: 100%;
3 | max-width: 430px;
4 | padding: 15px;
5 | margin: 100px auto;
6 | text-align: center;
7 | }
--------------------------------------------------------------------------------
/app/static/css/base.css:
--------------------------------------------------------------------------------
1 | .navbar-right-ul {
2 | text-align: right;
3 | }
4 |
5 | .flash {
6 | margin-top: 20px;
7 | }
8 |
9 | .my-container {
10 | margin-bottom: 100px;
11 | }
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | click==7.1.2
2 | Faker==5.6.1
3 | Flask==1.1.2
4 | Flask-Login==0.5.0
5 | itsdangerous==1.1.0
6 | Jinja2==2.11.2
7 | MarkupSafe==1.1.1
8 | mysql-connector-python==8.0.23
9 | protobuf==3.14.0
10 | python-dateutil==2.8.1
11 | six==1.15.0
12 | text-unidecode==1.3
13 | Werkzeug==1.0.1
14 |
--------------------------------------------------------------------------------
/app/templates/course_student.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import table, page_header %}
4 |
5 | {% block title %}
6 | LMS - Course Students
7 | {% endblock title %}
8 |
9 | {% block content %}
10 | {{ page_header("Student List") }}
11 | {{ table(li=student_list, button=false) }}
12 | {% endblock content %}
13 |
14 | {% block script %}
15 | {{ super() }}
16 |
21 | {% endblock script %}
--------------------------------------------------------------------------------
/app/templates/user_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import table, page_header %}
4 |
5 | {% block title %}
6 | LMS - Users
7 | {% endblock title %}
8 |
9 | {% block content %}
10 | {{ page_header("User List") }}
11 | {{ table(li=user_list, button_url="profile", button_text="Edit", button_color="primary") }}
12 | {% endblock content %}
13 |
14 | {% block script %}
15 | {{ super() }}
16 |
21 | {% endblock script %}
--------------------------------------------------------------------------------
/app/fake_db.py:
--------------------------------------------------------------------------------
1 | from faker import Faker
2 | import model
3 |
4 | fake = Faker()
5 |
6 | for i in range(10000):
7 | profile = fake.profile()
8 | success, message = model.create_user(
9 | Username=profile["name"].replace(" ", "_"),
10 | Password="rootroot",
11 | FirstName=''.join(profile["name"].split(" ")[:-1]),
12 | LastName=profile["name"].split(" ")[-1],
13 | PhoneNumber=fake.phone_number(),
14 | Email=profile["mail"],
15 | Faculty=profile['job'],
16 | Institution=profile['company'],
17 | Address=profile['address']
18 | )
19 | if not success:
20 | print(message)
21 |
--------------------------------------------------------------------------------
/app/templates/new_content.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import page_header, input, text_area %}
4 |
5 | {% block title %}
6 | LMS - New Content
7 | {% endblock title %}
8 |
9 | {% block content %}
10 |
11 | {{ page_header("New Content") }}
12 |
13 |
18 |
19 |
20 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/cluster.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import page_header, table %}
4 |
5 | {% block title %}
6 | LMS - Clusters
7 | {% endblock title %}
8 |
9 | {% block content %}
10 | {{ page_header("Cluster List") }}
11 |
21 | {{ table(li=cluster_list, button=false) }}
22 | {% endblock content %}
23 |
24 | {% block script %}
25 | {{ super() }}
26 |
31 | {% endblock script %}
--------------------------------------------------------------------------------
/app/templates/login.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block navbar %}
4 | {% endblock navbar %}
5 |
6 | {% block stylesheet %}
7 | {{ super() }}
8 |
9 | {% endblock stylesheet %}
10 |
11 | {% block title %}
12 | LMS - Login
13 | {% endblock title %}
14 |
15 | {% block content %}
16 |
28 | {% endblock content %}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 | Copyright (c) 2021 Seyed Mohammad Mousavi
3 |
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/app/templates/course.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import page_header %}
4 |
5 | {% block title %}
6 | LMS - {{ course_data["Course Name"] }}
7 | {% endblock title %}
8 |
9 | {% block content %}
10 | {% if is_teacher %}
11 |
19 | {% endif %}
20 |
21 | {{ page_header(course_data["Course Name"]) }}
22 |
23 | {% for content in content_list %}
24 |
25 |
26 |
27 | {{ content["Title"] }}
28 |
29 |
30 | {{ content["TextContent"] }}
31 |
32 |
33 | Date: {{ content["CreatedAt"] }}
34 |
35 |
36 |
37 | {% endfor %}
38 |
39 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/course_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import page_header, table, input, input_select %}
4 |
5 | {% block title %}
6 | LMS - Courses
7 | {% endblock title %}
8 |
9 | {% block content %}
10 | {{ page_header("Course List") }}
11 |
27 | {{ table(li=course_list, button=false) }}
28 | {% endblock content %}
29 |
30 | {% block script %}
31 | {{ super() }}
32 |
37 | {% endblock script %}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learning Management System
2 | This project was developed with flask as the Database and Internet Engineering
3 | final project, because of the limited time and the purpose of this project
4 | which was to work with databases in a website, I did not consider some security
5 | features which should be considered in the production phase.
6 |
7 | For running this on your computer first make sure you have `python3.6` or later
8 | , then install `virtualenv` package.
9 | ```
10 | pip install virtualenv
11 | ```
12 |
13 | Create a virtual environment in main directory of the project (same folder as
14 | this file) preferably with a name like `venv`, `env`, `.venv` or `.env` so
15 | `.gitignore` file can ignore it without any modification, I assumed you're
16 | gonna use `.venv`.
17 | ```
18 | virtualenv .venv
19 | ```
20 |
21 | Activate your virtual environment:
22 | ```
23 | source .venv/bin/activate
24 | ```
25 |
26 | Or if you're still using windows:
27 | ```
28 | .\venv\Scripts\activate
29 | ```
30 |
31 | Then install all of the project's dependencies without affecting anything on
32 | your computer.
33 | ```
34 | pip install -r requirements.txt
35 | ```
36 |
37 | You should set 2 environment variable, `FLASK_APP` and `FLASK_DEBUG`,
38 | in GNU/Linux or macOS:
39 | ```
40 | export FLASK_APP=main.py
41 | export FLASK_DEBUG=1
42 | ```
43 | In windows:
44 | ```
45 | set FLASK_APP=main.py
46 | set FLASK_DEBUG=1
47 | ```
48 | `FLASK_APP` is the name of the flask app file, and `FLASK_DEBUG` should be 0 or
49 | 1, if it's 1 we have access to hot reload and some more features in development
50 | phase.
51 |
52 | In the app directory copy `local_settings.py.sample` to `local_settings.py` and
53 | provide required variables for connecting to the database, keep in mind that
54 | for security reasons this file is ignored by `.gitignore`.
55 |
56 | In the app directory run the server with this command:
57 | ```
58 | flask run
59 | ```
60 |
--------------------------------------------------------------------------------
/app/templates/register.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block navbar %}
4 | {% endblock navbar %}
5 |
6 | {% block stylesheet %}
7 | {{ super() }}
8 |
9 | {% endblock stylesheet %}
10 |
11 | {% block title %}
12 | LMS - Register
13 | {% endblock title %}
14 |
15 | {% block content %}
16 |
49 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block stylesheet %}
4 | {{ super() }}
5 |
6 | {% endblock stylesheet %}
7 |
8 | {% block title %}
9 | LMS - Home
10 | {% endblock title %}
11 |
12 | {% block content %}
13 | {% if session.User.is_admin or session.User.is_manager %}
14 |
15 |
18 | {% if session.User.is_admin %}
19 |
22 | {% endif %}
23 |
26 |
27 | {% endif %}
28 |
29 | {% for course in student_course_list %}
30 |
43 | {% endfor %}
44 |
45 | {% for course in teacher_course_list %}
46 |
59 | {% endfor %}
60 |
61 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/profile.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% from 'macro.html' import input, page_header, input_select, table %}
4 |
5 | {% block title %}
6 | LMS - {{ user['Username'] }} Profile
7 | {% endblock title %}
8 |
9 | {% block content %}
10 | {{ page_header("Edit Profile") }}
11 |
12 | {{ input(name="Username", label="Username", value=user["Username"], required=true) }}
13 | {{ input(name="FirstName", label="First Name", value=user["FirstName"]) }}
14 | {{ input(name="LastName", label="Last Name", value=user["LastName"]) }}
15 | {{ input(name="PhoneNumber", label="Phone Number", value=user["PhoneNumber"]) }}
16 | {{ input(name="Email", label="Email", value=user["Email"]) }}
17 | {{ input(name="Faculty", label="Faculty", value=user["Faculty"]) }}
18 | {{ input(name="Institution", label="Institution", value=user["Institution"]) }}
19 | {{ input(name="Address", label="Address", value=user["Address"]) }}
20 | Submit
21 |
22 |
23 | {{ page_header("Add as Student to Course") }}
24 |
25 |
26 |
27 | {{ input_select(name="CourseID", li=course_list, value="ID", value_name="Name", label="Course", required=true, default_value_name="Select Course", have_label=false) }}
28 |
29 |
30 | Submit
31 |
32 |
33 |
34 | {% if student_course_list %}
35 | {{ table(li=student_course_list, button=false) }}
36 | {% endif %}
37 |
38 | {{ page_header("Add as Manager to Cluster") }}
39 |
40 |
41 |
42 | {{ input_select(name="ClusterID", li=cluster_list, value="Cluster ID", value_name="Cluster Name", label="Cluster", required=true, default_value_name="Select Cluster", have_label=false) }}
43 |
44 |
45 | Submit
46 |
47 |
48 |
49 | {% if user_cluster_list %}
50 | {{ table(li=user_cluster_list, button=false) }}
51 | {% endif %}
52 |
53 | {% endblock content %}
54 |
55 | {% block script %}
56 | {{ super() }}
57 |
62 | {% endblock script %}
--------------------------------------------------------------------------------
/app/templates/base.html:
--------------------------------------------------------------------------------
1 | {% from 'macro.html' import flash %}
2 |
3 |
4 |
5 |
6 |
7 | {% block stylesheet %}
8 |
9 |
10 |
11 | {% endblock stylesheet %}
12 | {% block title %}{% endblock title %}
13 |
14 |
15 | {% block navbar %}
16 |
17 | LMS
18 |
20 |
21 |
22 |
43 |
44 | {% endblock navbar %}
45 |
46 |
47 | {% block flash %}
48 | {% with messages = get_flashed_messages(with_categories=true) %}
49 | {% if messages %}
50 | {% for category, message in messages %}
51 | {{ flash(message, category) }}
52 | {% endfor %}
53 | {% endif %}
54 | {% endwith %}
55 | {% endblock flash %}
56 |
57 | {% block content %}
58 | {% endblock content %}
59 |
60 | {% block script %}
61 |
62 |
63 |
64 |
65 | {% endblock script %}
66 |
67 |
--------------------------------------------------------------------------------
/app/templates/macro.html:
--------------------------------------------------------------------------------
1 | {% macro flash(message, category='primary') -%}
2 |
3 | {{ message }}
4 |
5 | ×
6 |
7 |
8 | {%- endmacro %}
9 |
10 |
11 | {% macro page_header(text) %}
12 |
13 | {{ text }}
14 |
15 | {% endmacro %}
16 |
17 |
18 | {% macro table(li, button=true, button_text='Edit', button_url="", button_color='primary', button_head='Action') %}
19 | {% if li %}
20 |
21 |
22 |
23 | {% for key, value in li[0].items() %}
24 | {% if button and (key == 'ID' or key == 'Id' or key == 'id') %}
25 |
26 | {{ button_head }}
27 |
28 | {% else %}
29 | {{ key }}
30 | {% endif %}
31 | {% endfor %}
32 |
33 |
34 |
35 | {% for dict in li %}
36 |
37 | {% for key, value in dict.items() %}
38 | {% if button and (key == 'ID' or key == 'Id' or key == 'id') %}
39 |
40 | {{ button_text }}
41 |
42 | {% else %}
43 | {{ value }}
44 | {% endif %}
45 | {% endfor %}
46 |
47 | {% endfor %}
48 |
49 |
50 | {% endif %}
51 | {% endmacro %}
52 |
53 | {% macro input(name, label, have_label=true, placeholder="", required=false, type="text", value="") %}
54 | {% set unique_id = "id" + range(0, 1000000) | random | string %}
55 | {% if have_label %}
56 |
57 | {{label}}
58 | {% if required %}
59 |
60 | {% else %}
61 |
62 | {% endif %}
63 |
64 | {% else %}
65 | {% if required %}
66 |
67 | {% else %}
68 |
69 | {% endif %}
70 | {% endif %}
71 | {% endmacro %}
72 |
73 | {% macro text_area(name, label, have_label=true, placeholder="", required=false, type="text", value="", rows="5") %}
74 | {% set unique_id = "id" + range(0, 1000000) | random | string %}
75 | {% if have_label %}
76 |
77 | {{label}}
78 | {% if required %}
79 |
80 | {% else %}
81 |
82 | {% endif %}
83 |
84 | {% else %}
85 | {% if required %}
86 |
87 | {% else %}
88 |
89 | {% endif %}
90 | {% endif %}
91 | {% endmacro %}
92 |
93 | {% macro input_select(name, li, value, value_name, label, have_label=true, required=false, default_value_name="") %}
94 | {% set unique_id = "id" + range(0, 1000000) | random | string %}
95 | {% if have_label %}
96 |
97 | {{ label }}
98 |
99 | {{ default_value_name }}
100 | {% for dict in li %}
101 | {{ dict[value_name] }}
102 | {% endfor %}
103 |
104 |
105 | {% else %}
106 |
107 | {{ default_value_name }}
108 | {% for dict in li %}
109 | {{ dict[value_name] }}
110 | {% endfor %}
111 |
112 | {% endif %}
113 | {% endmacro %}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/vim,linux,macos,emacs,windows,visualstudiocode,python,flask
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=vim,linux,macos,emacs,windows,visualstudiocode,python,flask
4 |
5 | ### Emacs ###
6 | # -*- mode: gitignore; -*-
7 | *~
8 | \#*\#
9 | /.emacs.desktop
10 | /.emacs.desktop.lock
11 | *.elc
12 | auto-save-list
13 | tramp
14 | .\#*
15 |
16 | # Org-mode
17 | .org-id-locations
18 | *_archive
19 |
20 | # flymake-mode
21 | *_flymake.*
22 |
23 | # eshell files
24 | /eshell/history
25 | /eshell/lastdir
26 |
27 | # elpa packages
28 | /elpa/
29 |
30 | # reftex files
31 | *.rel
32 |
33 | # AUCTeX auto folder
34 | /auto/
35 |
36 | # cask packages
37 | .cask/
38 | dist/
39 |
40 | # Flycheck
41 | flycheck_*.el
42 |
43 | # server auth directory
44 | /server/
45 |
46 | # projectiles files
47 | .projectile
48 |
49 | # directory configuration
50 | .dir-locals.el
51 |
52 | # network security
53 | /network-security.data
54 |
55 |
56 | ### Flask ###
57 | instance/*
58 | !instance/.gitignore
59 | .webassets-cache
60 |
61 | ### Flask.Python Stack ###
62 | # Byte-compiled / optimized / DLL files
63 | __pycache__/
64 | *.py[cod]
65 | *$py.class
66 |
67 | # C extensions
68 | *.so
69 |
70 | # Distribution / packaging
71 | .Python
72 | build/
73 | develop-eggs/
74 | downloads/
75 | eggs/
76 | .eggs/
77 | lib/
78 | lib64/
79 | parts/
80 | sdist/
81 | var/
82 | wheels/
83 | pip-wheel-metadata/
84 | share/python-wheels/
85 | *.egg-info/
86 | .installed.cfg
87 | *.egg
88 | MANIFEST
89 |
90 | # PyInstaller
91 | # Usually these files are written by a python script from a template
92 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
93 | *.manifest
94 | *.spec
95 |
96 | # Installer logs
97 | pip-log.txt
98 | pip-delete-this-directory.txt
99 |
100 | # Unit test / coverage reports
101 | htmlcov/
102 | .tox/
103 | .nox/
104 | .coverage
105 | .coverage.*
106 | .cache
107 | nosetests.xml
108 | coverage.xml
109 | *.cover
110 | *.py,cover
111 | .hypothesis/
112 | .pytest_cache/
113 | pytestdebug.log
114 |
115 | # Translations
116 | *.mo
117 | *.pot
118 |
119 | # Django stuff:
120 | *.log
121 | local_settings.py
122 | db.sqlite3
123 | db.sqlite3-journal
124 |
125 | # Flask stuff:
126 | instance/
127 |
128 | # Scrapy stuff:
129 | .scrapy
130 |
131 | # Sphinx documentation
132 | docs/_build/
133 | doc/_build/
134 |
135 | # PyBuilder
136 | target/
137 |
138 | # Jupyter Notebook
139 | .ipynb_checkpoints
140 |
141 | # IPython
142 | profile_default/
143 | ipython_config.py
144 |
145 | # pyenv
146 | .python-version
147 |
148 | # pipenv
149 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
150 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
151 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
152 | # install all needed dependencies.
153 | #Pipfile.lock
154 |
155 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
156 | __pypackages__/
157 |
158 | # Celery stuff
159 | celerybeat-schedule
160 | celerybeat.pid
161 |
162 | # SageMath parsed files
163 | *.sage.py
164 |
165 | # Environments
166 | .env
167 | .venv
168 | env/
169 | venv/
170 | ENV/
171 | env.bak/
172 | venv.bak/
173 | pythonenv*
174 |
175 | # Spyder project settings
176 | .spyderproject
177 | .spyproject
178 |
179 | # Rope project settings
180 | .ropeproject
181 |
182 | # mkdocs documentation
183 | /site
184 |
185 | # mypy
186 | .mypy_cache/
187 | .dmypy.json
188 | dmypy.json
189 |
190 | # Pyre type checker
191 | .pyre/
192 |
193 | # pytype static type analyzer
194 | .pytype/
195 |
196 | # profiling data
197 | .prof
198 |
199 | ### Linux ###
200 |
201 | # temporary files which can be created if a process still has a handle open of a deleted file
202 | .fuse_hidden*
203 |
204 | # KDE directory preferences
205 | .directory
206 |
207 | # Linux trash folder which might appear on any partition or disk
208 | .Trash-*
209 |
210 | # .nfs files are created when an open file is removed but is still being accessed
211 | .nfs*
212 |
213 | ### macOS ###
214 | # General
215 | .DS_Store
216 | .AppleDouble
217 | .LSOverride
218 |
219 | # Icon must end with two \r
220 | Icon
221 |
222 | # Thumbnails
223 | ._*
224 |
225 | # Files that might appear in the root of a volume
226 | .DocumentRevisions-V100
227 | .fseventsd
228 | .Spotlight-V100
229 | .TemporaryItems
230 | .Trashes
231 | .VolumeIcon.icns
232 | .com.apple.timemachine.donotpresent
233 |
234 | # Directories potentially created on remote AFP share
235 | .AppleDB
236 | .AppleDesktop
237 | Network Trash Folder
238 | Temporary Items
239 | .apdisk
240 |
241 | ### Python ###
242 | # Byte-compiled / optimized / DLL files
243 |
244 | # C extensions
245 |
246 | # Distribution / packaging
247 |
248 | # PyInstaller
249 | # Usually these files are written by a python script from a template
250 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
251 |
252 | # Installer logs
253 |
254 | # Unit test / coverage reports
255 |
256 | # Translations
257 |
258 | # Django stuff:
259 |
260 | # Flask stuff:
261 |
262 | # Scrapy stuff:
263 |
264 | # Sphinx documentation
265 |
266 | # PyBuilder
267 |
268 | # Jupyter Notebook
269 |
270 | # IPython
271 |
272 | # pyenv
273 |
274 | # pipenv
275 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
276 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
277 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
278 | # install all needed dependencies.
279 |
280 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
281 |
282 | # Celery stuff
283 |
284 | # SageMath parsed files
285 |
286 | # Environments
287 |
288 | # Spyder project settings
289 |
290 | # Rope project settings
291 |
292 | # mkdocs documentation
293 |
294 | # mypy
295 |
296 | # Pyre type checker
297 |
298 | # pytype static type analyzer
299 |
300 | # profiling data
301 |
302 | ### Vim ###
303 | # Swap
304 | [._]*.s[a-v][a-z]
305 | !*.svg # comment out if you don't need vector files
306 | [._]*.sw[a-p]
307 | [._]s[a-rt-v][a-z]
308 | [._]ss[a-gi-z]
309 | [._]sw[a-p]
310 |
311 | # Session
312 | Session.vim
313 | Sessionx.vim
314 |
315 | # Temporary
316 | .netrwhist
317 | # Auto-generated tag files
318 | tags
319 | # Persistent undo
320 | [._]*.un~
321 |
322 | ### VisualStudioCode ###
323 | .vscode/*
324 | !.vscode/tasks.json
325 | !.vscode/launch.json
326 | *.code-workspace
327 |
328 | ### VisualStudioCode Patch ###
329 | # Ignore all local history of files
330 | .history
331 | .ionide
332 |
333 | ### Windows ###
334 | # Windows thumbnail cache files
335 | Thumbs.db
336 | Thumbs.db:encryptable
337 | ehthumbs.db
338 | ehthumbs_vista.db
339 |
340 | # Dump file
341 | *.stackdump
342 |
343 | # Folder config file
344 | [Dd]esktop.ini
345 |
346 | # Recycle Bin used on file shares
347 | $RECYCLE.BIN/
348 |
349 | # Windows Installer files
350 | *.cab
351 | *.msi
352 | *.msix
353 | *.msm
354 | *.msp
355 |
356 | # Windows shortcuts
357 | *.lnk
358 |
359 | # End of https://www.toptal.com/developers/gitignore/api/vim,linux,macos,emacs,windows,visualstudiocode,python,flask
360 |
--------------------------------------------------------------------------------
/app/main.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, request, redirect, flash, url_for, session
2 | from flask_login import (
3 | LoginManager,
4 | UserMixin,
5 | current_user,
6 | login_required,
7 | login_user,
8 | logout_user,
9 | )
10 | from . import model
11 |
12 |
13 | app = Flask(__name__)
14 | app.config["SECRET_KEY"] = "Wfd8do6H7d74vdesbuRLlMFiAeXeJ7r"
15 | # Flask login
16 | login_manager = LoginManager()
17 | login_manager.init_app(app)
18 | login_manager.login_view = "login"
19 | login_manager.login_message_category = "danger"
20 |
21 |
22 | class User(UserMixin):
23 | def __init__(self, id):
24 | self.id = id
25 |
26 | def is_admin(self):
27 | return bool(session["User"]["is_admin"])
28 |
29 | def __repr__(self):
30 | return f"<{self.id}>"
31 |
32 |
33 | @login_manager.user_loader
34 | def load_user(userid):
35 | return User(userid)
36 |
37 |
38 | @app.route("/")
39 | @login_required
40 | def index():
41 | success, message, student_course_list = model.get_student_course_list(
42 | current_user.id
43 | )
44 | if not success:
45 | flash(message, "warning")
46 | return redirect(url_for("index"))
47 | success, message, teacher_course_list = model.get_teacher_course_list(
48 | current_user.id
49 | )
50 | if not success:
51 | flash(message, "warning")
52 | return redirect(url_for("index"))
53 | return render_template(
54 | "index.html",
55 | teacher_course_list=teacher_course_list,
56 | student_course_list=student_course_list,
57 | )
58 |
59 |
60 | @app.route("/course/")
61 | @login_required
62 | def course(CourseID):
63 | success, message, is_teacher = model.is_teacher(current_user.id, CourseID)
64 | if not success:
65 | flash(message, "warning")
66 | return redirect(url_for("index"))
67 | success, message, course_data = model.get_course(CourseID)
68 | if not success:
69 | flash(message, "warning")
70 | return redirect(url_for("index"))
71 | if not course_data:
72 | flash("This course does not exist or you don't have access to it", "warning")
73 | return redirect(url_for("index"))
74 | success, message, content_list = model.get_content_list(CourseID)
75 | if not success:
76 | flash(message, "warning")
77 | return redirect(url_for("index"))
78 | return render_template(
79 | "course.html",
80 | is_teacher=is_teacher,
81 | course_data=course_data,
82 | content_list=content_list,
83 | )
84 |
85 |
86 | @app.route("/login/", methods=["GET", "POST"])
87 | def login():
88 | if current_user.is_authenticated:
89 | flash("You are currently logged in", "primary")
90 | return redirect(url_for("index"))
91 | if request.method == "POST":
92 | Username = request.form.get("Username", "")
93 | Password = request.form.get("Password", "")
94 | success, message, raw_user = model.check_login(Username, Password)
95 | if success:
96 | login_user(User(raw_user["ID"]))
97 | session["User"] = raw_user
98 | success2, message2, is_manager = model.is_manager(current_user.id)
99 | if success2:
100 | session["User"]["is_manager"] = is_manager
101 | else:
102 | flash(message2, "warning")
103 | return redirect(url_for("login"))
104 | return redirect("/")
105 | else:
106 | flash(message, "warning")
107 | return redirect(url_for("login"))
108 | return render_template("login.html")
109 |
110 |
111 | @app.route("/logout/")
112 | @login_required
113 | def logout():
114 | logout_user()
115 | flash("You have logged out successfully", "success")
116 | return redirect(url_for("login"))
117 |
118 |
119 | @app.route("/register/", methods=["GET", "POST"])
120 | def register():
121 | if request.method == "POST":
122 | Username = request.form.get("Username", "")
123 | Password = request.form.get("Password", "")
124 | LastName = request.form.get("LastName", "")
125 | FirstName = request.form.get("FirstName", "")
126 | PhoneNumber = request.form.get("PhoneNumber", "")
127 | Email = request.form.get("Email", "")
128 | Faculty = request.form.get("Faculty", "")
129 | Institution = request.form.get("Institution", "")
130 | Address = request.form.get("Address", "")
131 | success, message = model.create_user(
132 | Username=Username,
133 | Password=Password,
134 | LastName=LastName,
135 | FirstName=FirstName,
136 | PhoneNumber=PhoneNumber,
137 | Email=Email,
138 | Faculty=Faculty,
139 | Institution=Institution,
140 | Address=Address,
141 | )
142 | if success:
143 | flash(
144 | "Your account has been created successfully, you can login now.",
145 | "success",
146 | )
147 | return redirect(url_for("login"))
148 | flash(message, "warning")
149 | return render_template("register.html")
150 |
151 |
152 | @app.route("/users/")
153 | @login_required
154 | def user_list():
155 | success, message, user_list = model.get_user_list()
156 | if not success:
157 | flash(message, "warning")
158 | return redirect(url_for("index"))
159 | return render_template("user_list.html", user_list=user_list)
160 |
161 |
162 | @app.route("/profile//", methods=["GET", "POST"])
163 | @login_required
164 | def profile(ID):
165 | if request.method == "POST":
166 | Username = request.form.get("Username", "")
167 | LastName = request.form.get("LastName", "")
168 | FirstName = request.form.get("FirstName", "")
169 | PhoneNumber = request.form.get("PhoneNumber", "")
170 | Email = request.form.get("Email", "")
171 | Faculty = request.form.get("Faculty", "")
172 | Institution = request.form.get("Institution", "")
173 | Address = request.form.get("Address", "")
174 | success, message = model.edit_user_profile(
175 | ID=ID,
176 | Username=Username,
177 | LastName=LastName,
178 | FirstName=FirstName,
179 | PhoneNumber=PhoneNumber,
180 | Email=Email,
181 | Faculty=Faculty,
182 | Institution=Institution,
183 | Address=Address,
184 | )
185 | if success:
186 | flash(
187 | "Profile updated successfully.",
188 | "success",
189 | )
190 | return redirect(url_for("profile", ID=ID))
191 | flash(message, "warning")
192 | success, message, raw_user = model.get_user_profile(ID)
193 | if not success:
194 | flash(
195 | "No such user exists, or you don't have access to it's profile.", "warning"
196 | )
197 | return redirect(url_for("index"))
198 | success, message, course_list = model.get_all_course_list()
199 | if not success:
200 | flash(message, "warning")
201 | return redirect(url_for("index"))
202 | success, message, student_course_list = model.get_student_course_list(ID)
203 | if not success:
204 | flash(message, "warning")
205 | return redirect(url_for("index"))
206 | success, message, cluster_list = model.get_cluster_list(
207 | current_user.id, current_user.is_admin()
208 | )
209 | if not success:
210 | flash(message, "warning")
211 | return redirect(url_for("index"))
212 | success, message, user_cluster_list = model.get_cluster_list(ID)
213 | if not success:
214 | flash(message, "warning")
215 | return redirect(url_for("index"))
216 | return render_template(
217 | "profile.html",
218 | user=raw_user,
219 | course_list=course_list,
220 | student_course_list=student_course_list,
221 | cluster_list=cluster_list,
222 | user_cluster_list=user_cluster_list,
223 | )
224 |
225 |
226 | @app.route("/clusters/", methods=["GET", "POST"])
227 | @login_required
228 | def cluster_list():
229 | if request.method == "POST":
230 | Name = request.form.get("Name", "DEFAULT")
231 | success, message = model.create_cluster(Name)
232 | if success:
233 | flash(message, "success")
234 | return redirect(url_for("cluster_list"))
235 | flash(message, "warning")
236 | return redirect(url_for("cluster_list"))
237 | success, message, cluster_list = model.get_cluster_list(
238 | current_user.id, current_user.is_admin()
239 | )
240 | if not success:
241 | flash(message, "warning")
242 | return redirect(url_for("cluster_list"))
243 | return render_template("cluster.html", cluster_list=cluster_list)
244 |
245 |
246 | @app.route("/make_manager//", methods=["POST"])
247 | @login_required
248 | def make_manager(ManagerID):
249 | ClusterID = request.form.get("ClusterID", "")
250 | success, message = model.create_manager_cluster(ManagerID, ClusterID)
251 | if success:
252 | flash(message, "success")
253 | else:
254 | flash(message, "warning")
255 | return redirect(url_for("profile", ID=ManagerID))
256 |
257 |
258 | @app.route("/courses/", methods=["GET", "POST"])
259 | @login_required
260 | def course_list():
261 | if request.method == "POST":
262 | Name = request.form.get("Name", "DEFAULT")
263 | ClusterID = request.form.get("ClusterID", "DEFAULT")
264 | TeacherID = request.form.get("TeacherID", "DEFAULT")
265 | success, message = model.create_course(Name, TeacherID, ClusterID)
266 | if success:
267 | flash(message, "success")
268 | return redirect(url_for("course_list"))
269 | flash(message, "warning")
270 | return redirect(url_for("course_list"))
271 | success, message, course_list = model.get_all_course_list()
272 | if not success:
273 | flash(message, "warning")
274 | return redirect(url_for("course_list"))
275 | success, message, cluster_list = model.get_cluster_list(
276 | current_user.id, current_user.is_admin()
277 | )
278 | if not success:
279 | flash(message, "warning")
280 | return redirect(url_for("index"))
281 | success, message, user_list = model.get_user_list()
282 | if not success:
283 | flash(message, "warning")
284 | return redirect(url_for("index"))
285 | return render_template(
286 | "course_list.html",
287 | course_list=course_list,
288 | cluster_list=cluster_list,
289 | user_list=user_list,
290 | )
291 |
292 |
293 | @app.route("/participate/", methods=["POST"])
294 | @login_required
295 | def participate(StudentID):
296 | CourseID = request.form.get("CourseID", "")
297 | success, message = model.create_student_course(StudentID, CourseID)
298 | if success:
299 | flash(message, "success")
300 | else:
301 | flash(message, "warning")
302 | return redirect(url_for("profile", ID=StudentID))
303 |
304 |
305 | @app.route("/students//")
306 | @login_required
307 | def student_list(CourseID):
308 | success, message, student_list = model.get_course_student_list(CourseID)
309 | if not success:
310 | flash(message, "warning")
311 | return redirect(url_for("course", CourseID=CourseID))
312 | return render_template("course_student.html", student_list=student_list)
313 |
314 |
315 | @app.route("/newcontent//", methods=["GET", "POST"])
316 | @login_required
317 | def new_content(CourseID):
318 | if request.method == "POST":
319 | Title = request.form.get("Title", "")
320 | TextContent = request.form.get("TextContent", "")
321 | success, message = model.create_content(CourseID, Title, TextContent)
322 | if success:
323 | flash("Content created successfully.", "success")
324 | return redirect(url_for("course", CourseID=CourseID))
325 | flash(message, "warning")
326 | return redirect(url_for("course", CourseID=CourseID))
327 | return render_template("new_content.html", CourseID=CourseID)
328 |
--------------------------------------------------------------------------------
/app/model.py:
--------------------------------------------------------------------------------
1 | from mysql.connector import connect, Error
2 | from werkzeug.security import generate_password_hash, check_password_hash
3 | from . import local_settings
4 |
5 |
6 | def get_conn():
7 | conn = connect(
8 | host=local_settings.MYSQL_HOST,
9 | user=local_settings.MYSQL_USER,
10 | password=local_settings.MYSQL_PASSWORD,
11 | database=local_settings.MYSQL_DB,
12 | )
13 | return conn
14 |
15 |
16 | def create_user(
17 | Username,
18 | Password,
19 | is_student="DEFAULT",
20 | is_teacher="DEFAULT",
21 | is_manager="DEFAULT",
22 | is_admin="DEFAULT",
23 | LastName="DEFAULT",
24 | FirstName="DEFAULT",
25 | PhoneNumber="DEFAULT",
26 | Email="DEFAULT",
27 | Faculty="DEFAULT",
28 | Institution="DEFAULT",
29 | Address="DEFAULT",
30 | ):
31 | """
32 | CREATE TABLE Users (
33 | ID INT NOT NULL AUTO_INCREMENT,
34 | Username VARCHAR(64) NOT NULL UNIQUE,
35 | Password VARCHAR(255) NOT NULL,
36 | is_student BOOLEAN DEFAULT false,
37 | is_teacher BOOLEAN DEFAULT false,
38 | is_manager BOOLEAN DEFAULT false,
39 | is_admin BOOLEAN DEFAULT false,
40 | LastName VARCHAR(255),
41 | FirstName VARCHAR(255),
42 | PhoneNumber CHAR(13),
43 | Email VARCHAR(320),
44 | Faculty VARCHAR(255),
45 | Institution VARCHAR(255),
46 | Address VARCHAR(2048),
47 | PRIMARY KEY (ID)
48 | );
49 |
50 | """
51 | if len(Username) < 4:
52 | error_message = "Username should be at least 4 characters long"
53 | return (False, error_message)
54 | if len(Password) < 6:
55 | error_message = "Password should be at least 6 characters long"
56 | return (False, error_message)
57 | conn = get_conn()
58 | cur = conn.cursor()
59 | Password = generate_password_hash(Password)
60 | sql_str = f"""
61 | INSERT INTO Users
62 | (Username, Password, is_student, is_teacher, is_manager, is_admin,
63 | LastName, FirstName, PhoneNumber, Email, Faculty, Institution, Address)
64 | VALUES
65 | ('{Username}', '{Password}', {is_student}, {is_teacher},
66 | {is_manager},{is_admin}, '{LastName}', '{FirstName}', '{PhoneNumber}',
67 | '{Email}', '{Faculty}', '{Institution}', '{Address}')
68 | """
69 | try:
70 | cur.execute(sql_str)
71 | conn.commit()
72 | except Error as e:
73 | cur.close()
74 | conn.close()
75 | return (False, str(e))
76 | cur.close()
77 | conn.close()
78 | return (True, f"User with username: {Username} created successfully")
79 |
80 |
81 | def check_login(Username, Password):
82 | conn = get_conn()
83 | cur = conn.cursor(dictionary=True)
84 | sql_str = f"""
85 | SELECT ID, Username, Password, FirstName, LastName,
86 | is_student, is_teacher, is_manager, is_admin
87 | FROM Users WHERE
88 | Username = '{Username}'
89 | """
90 | try:
91 | cur.execute(sql_str)
92 | except Error as e:
93 | cur.close()
94 | conn.close()
95 | return (False, str(e), None)
96 | raw_user = cur.fetchone()
97 | cur.close()
98 | conn.close()
99 | if raw_user:
100 | if check_password_hash(raw_user.pop("Password"), Password):
101 | return (True, "Correct username and password", raw_user)
102 | return (False, "Wrong username or password", raw_user)
103 |
104 |
105 | def get_user_list():
106 | conn = get_conn()
107 | cur = conn.cursor(dictionary=True)
108 | sql_str = """
109 | SELECT Username, FirstName AS 'First Name', LastName AS 'Last Name',
110 | PhoneNumber AS 'Phone Number', Email, Faculty, ID
111 | FROM Users
112 | """
113 | try:
114 | cur.execute(sql_str)
115 | except Error as e:
116 | cur.close()
117 | conn.close()
118 | return (False, str(e), None)
119 | user_list = cur.fetchall()
120 | cur.close()
121 | conn.close()
122 | return (True, "User list retrieved from db, successfully", user_list)
123 |
124 |
125 | def get_user_profile(ID):
126 | conn = get_conn()
127 | cur = conn.cursor(dictionary=True)
128 | sql_str = f"""
129 | SELECT *
130 | FROM Users
131 | WHERE ID={ID}
132 | """
133 | try:
134 | cur.execute(sql_str)
135 | except Error as e:
136 | cur.close()
137 | conn.close()
138 | return (False, str(e), None)
139 | raw_user = cur.fetchone()
140 | cur.close()
141 | conn.close()
142 | if raw_user:
143 | raw_user.pop("Password")
144 | return (True, "User profile retrieved from db, successfully", raw_user)
145 | return (False, "No such user exists", None)
146 |
147 |
148 | def edit_user_profile(
149 | ID,
150 | Username,
151 | LastName="DEFAULT",
152 | FirstName="DEFAULT",
153 | PhoneNumber="DEFAULT",
154 | Email="DEFAULT",
155 | Faculty="DEFAULT",
156 | Institution="DEFAULT",
157 | Address="DEFAULT",
158 | ):
159 | conn = get_conn()
160 | cur = conn.cursor(dictionary=True)
161 | sql_str = f"""
162 | UPDATE Users
163 | SET LastName = '{LastName}', FirstName = '{FirstName}',
164 | PhoneNumber = '{PhoneNumber}', Email = '{Email}',
165 | Faculty = '{Faculty}', Institution = '{Institution}',
166 | Address = '{Address}', Username = '{Username}'
167 | WHERE ID = {ID};
168 | """
169 | try:
170 | cur.execute(sql_str)
171 | conn.commit()
172 | except Error as e:
173 | cur.close()
174 | conn.close()
175 | return (False, str(e))
176 | cur.close()
177 | conn.close()
178 | return (True, "User profile updated successfully.")
179 |
180 |
181 | def create_cluster(Name):
182 | """
183 | CREATE TABLE Clusters (
184 | ID INT NOT NULL AUTO_INCREMENT,
185 | Name VARCHAR(64) NOT NULL UNIQUE,
186 | PRIMARY KEY (ID)
187 | );
188 | """
189 | conn = get_conn()
190 | cur = conn.cursor(dictionary=True)
191 | sql_str = f"""
192 | INSERT INTO Clusters (Name)
193 | VALUES ('{Name}')
194 | """
195 | try:
196 | cur.execute(sql_str)
197 | conn.commit()
198 | except Error as e:
199 | cur.close()
200 | conn.close()
201 | return (False, str(e))
202 | cur.close()
203 | conn.close()
204 | return (True, "Cluster created successfully.")
205 |
206 |
207 | def create_manager_cluster(ManagerID, ClusterID):
208 | """
209 | CREATE TABLE ManagerCluster (
210 | ManagerID INT NOT NULL,
211 | ClusterID INT NOT NULL,
212 | PRIMARY KEY (ManagerID, ClusterID),
213 | FOREIGN KEY (ManagerID) REFERENCES Users(ID)
214 | ON DELETE RESTRICT,
215 | FOREIGN KEY (ClusterID) REFERENCES Clusters(ID)
216 | ON DELETE RESTRICT
217 | )
218 | """
219 | conn = get_conn()
220 | cur = conn.cursor(dictionary=True)
221 | sql_str = f"""
222 | INSERT INTO ManagerCluster
223 | (ManagerID, ClusterID)
224 | VALUES
225 | ({ManagerID}, {ClusterID})
226 | """
227 | try:
228 | cur.execute(sql_str)
229 | conn.commit()
230 | except Error as e:
231 | cur.close()
232 | conn.close()
233 | return (False, str(e))
234 | cur.close()
235 | conn.close()
236 | return (True, "Cluster manager created successfully.")
237 |
238 |
239 | def get_cluster_manager_list(ManagerID):
240 | conn = get_conn()
241 | cur = conn.cursor(dictionary=True)
242 | sql_str = f"""
243 | SELECT Name AS 'Cluster Name', ID AS 'Cluster ID'
244 | FROM Clusters WHERE ID IN
245 | (SELECT ClusterID
246 | FROM ManagerCluster
247 | WHERE ManagerID={ManagerID})
248 | """
249 | try:
250 | cur.execute(sql_str)
251 | except Error as e:
252 | cur.close()
253 | conn.close()
254 | return (False, str(e), None)
255 | cluster_manager_list = cur.fetchall()
256 | cur.close()
257 | conn.close()
258 | return (
259 | True,
260 | "Manager's clusters retrieved from db, successfully",
261 | cluster_manager_list,
262 | )
263 |
264 |
265 | def is_manager(UserID):
266 | conn = get_conn()
267 | cur = conn.cursor(dictionary=True)
268 | sql_str = f"""
269 | SELECT COUNT(1) AS is_manager
270 | FROM ManagerCluster
271 | WHERE ManagerID={UserID}
272 | """
273 | try:
274 | cur.execute(sql_str)
275 | except Error as e:
276 | cur.close()
277 | conn.close()
278 | return (False, str(e), None)
279 | manager = cur.fetchone()
280 | cur.close()
281 | conn.close()
282 | return (
283 | True,
284 | "User checked for manager successfully",
285 | bool(int(manager["is_manager"])),
286 | )
287 |
288 |
289 | def get_cluster_list(ManagerID, is_admin=False):
290 | if is_admin:
291 | return get_all_cluster_list()
292 | return get_cluster_manager_list(ManagerID)
293 |
294 |
295 | def get_all_cluster_list():
296 | conn = get_conn()
297 | cur = conn.cursor(dictionary=True)
298 | sql_str = """
299 | SELECT Name AS 'Cluster Name', ID AS 'Cluster ID'
300 | FROM Clusters
301 | """
302 | try:
303 | cur.execute(sql_str)
304 | except Error as e:
305 | cur.close()
306 | conn.close()
307 | return (False, str(e), None)
308 | cluster_list = cur.fetchall()
309 | cur.close()
310 | conn.close()
311 | return (True, "Clusters retrieved from db, successfully.", cluster_list)
312 |
313 |
314 | def create_course(Name, TeacherID, ClusterID):
315 | """
316 | CREATE TABLE Courses (
317 | ID INT NOT NULL AUTO_INCREMENT,
318 | Name VARCHAR(64) NOT NULL,
319 | ClusterID INT NOT NULL,
320 | TeacherID INT NOT NULL,
321 | PRIMARY KEY (ID),
322 | FOREIGN KEY (ClusterID) REFERENCES Clusters(ID)
323 | ON DELETE RESTRICT,
324 | FOREIGN KEY (TeacherID) REFERENCES Users(ID)
325 | ON DELETE RESTRICT
326 | );
327 | """
328 | conn = get_conn()
329 | cur = conn.cursor(dictionary=True)
330 | sql_str = f"""
331 | INSERT INTO Courses
332 | (Name, ClusterID, TeacherID)
333 | VALUES
334 | ('{Name}', {ClusterID}, {TeacherID})
335 | """
336 | try:
337 | cur.execute(sql_str)
338 | conn.commit()
339 | except Error as e:
340 | cur.close()
341 | conn.close()
342 | return (False, str(e))
343 | cur.close()
344 | conn.close()
345 | return (True, "Course created successfully.")
346 |
347 |
348 | def get_all_course_list():
349 | conn = get_conn()
350 | cur = conn.cursor(dictionary=True)
351 | sql_str = """
352 | SELECT Name, ClusterID, TeacherID, ID
353 | FROM Courses
354 | """
355 | try:
356 | cur.execute(sql_str)
357 | except Error as e:
358 | cur.close()
359 | conn.close()
360 | return (False, str(e))
361 | course_list = cur.fetchall()
362 | cur.close()
363 | conn.close()
364 | return (True, "Course list retrieved from db, successfully", course_list)
365 |
366 |
367 | def create_student_course(StudentID, CourseID):
368 | """
369 | CREATE TABLE StudentCourse (
370 | StudentID INT NOT NULL,
371 | CourseID INT NOT NULL,
372 | PRIMARY KEY (StudentID, CourseID),
373 | FOREIGN KEY (StudentID) REFERENCES Users(ID)
374 | ON DELETE RESTRICT,
375 | FOREIGN KEY (CourseID) REFERENCES Courses(ID)
376 | ON DELETE RESTRICT
377 | );
378 | """
379 | conn = get_conn()
380 | cur = conn.cursor(dictionary=True)
381 | sql_str = f"""
382 | INSERT INTO StudentCourse
383 | (StudentID, CourseID)
384 | VALUES
385 | ({StudentID}, {CourseID})
386 | """
387 | try:
388 | cur.execute(sql_str)
389 | conn.commit()
390 | except Error as e:
391 | cur.close()
392 | conn.close()
393 | return (False, str(e))
394 | cur.close()
395 | conn.close()
396 | return (True, "Student participated in the course successfully.")
397 |
398 |
399 | def get_student_course_list(StudentID):
400 | conn = get_conn()
401 | cur = conn.cursor(dictionary=True)
402 | sql_str = f"""
403 | SELECT c.Name AS 'Course Name', u.FirstName AS 'Teacher First Name',
404 | u.LastName AS 'Teacher Last Name', c.ID AS 'Course ID'
405 | FROM ((StudentCourse AS sc
406 | INNER JOIN Courses AS c ON sc.CourseID = c.ID)
407 | INNER JOIN Users AS u ON c.TeacherID = u.ID)
408 | WHERE sc.StudentID = {StudentID}
409 | """
410 | try:
411 | cur.execute(sql_str)
412 | except Error as e:
413 | cur.close()
414 | conn.close()
415 | return (False, str(e), None)
416 | student_course_list = cur.fetchall()
417 | cur.close()
418 | conn.close()
419 | return (True, "Courses retrieved from db, successfully.", student_course_list)
420 |
421 |
422 | def get_teacher_course_list(TeacherID):
423 | conn = get_conn()
424 | cur = conn.cursor(dictionary=True)
425 | sql_str = f"""
426 | SELECT c.Name AS 'Course Name', u.FirstName AS 'Teacher First Name',
427 | u.LastName AS 'Teacher Last Name', c.ID AS 'Course ID'
428 | FROM Courses AS c
429 | INNER JOIN Users AS u ON c.TeacherID = u.ID
430 | WHERE c.TeacherID = {TeacherID}
431 | """
432 | try:
433 | cur.execute(sql_str)
434 | except Error as e:
435 | cur.close()
436 | conn.close()
437 | return (False, str(e), None)
438 | teacher_course_list = cur.fetchall()
439 | cur.close()
440 | conn.close()
441 | return (True, "Courses retrieved from db, successfully.", teacher_course_list)
442 |
443 |
444 | def is_teacher(UserID, CourseID):
445 | conn = get_conn()
446 | cur = conn.cursor(dictionary=True)
447 | sql_str = f"""
448 | SELECT COUNT(1) AS is_teacher
449 | FROM Courses
450 | WHERE ID={CourseID} AND TeacherID={UserID}
451 | """
452 | try:
453 | cur.execute(sql_str)
454 | except Error as e:
455 | cur.close()
456 | conn.close()
457 | return (False, str(e), None)
458 | teacher = cur.fetchone()
459 | cur.close()
460 | conn.close()
461 | return (
462 | True,
463 | "User checked for teacher successfully",
464 | bool(int(teacher["is_teacher"])),
465 | )
466 |
467 |
468 | def get_course(CourseID):
469 | conn = get_conn()
470 | cur = conn.cursor(dictionary=True)
471 | sql_str = f"""
472 | SELECT ID AS 'Course ID', Name AS 'Course Name', TeacherID AS 'Teacher ID'
473 | FROM Courses
474 | WHERE ID = {CourseID}
475 | """
476 | try:
477 | cur.execute(sql_str)
478 | except Error as e:
479 | cur.close()
480 | conn.close()
481 | return (False, str(e), None)
482 | course = cur.fetchone()
483 | cur.close()
484 | conn.close()
485 | return (True, "Course retrieved from db, successfully", course)
486 |
487 |
488 | def create_content(CourseID, Title, TextContent):
489 | """
490 | CREATE TABLE Contents (
491 | ID INT NOT NULL AUTO_INCREMENT,
492 | CourseID INT NOT NULL,
493 | Title VARCHAR(255) NOT NULL,
494 | TextContent LONGTEXT,
495 | CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
496 | PRIMARY KEY (ID),
497 | FOREIGN KEY (CourseID) REFERENCES Courses(ID)
498 | ON DELETE RESTRICT
499 | );
500 | """
501 | conn = get_conn()
502 | cur = conn.cursor(dictionary=True)
503 | sql_str = f"""
504 | INSERT INTO Contents
505 | (CourseID, Title, TextContent)
506 | VALUES
507 | ({CourseID}, '{Title}', '{TextContent}')
508 | """
509 | try:
510 | cur.execute(sql_str)
511 | conn.commit()
512 | except Error as e:
513 | cur.close()
514 | conn.close()
515 | return (False, str(e))
516 | cur.close()
517 | conn.close()
518 | return (True, "Content created successfully.")
519 |
520 |
521 | def get_content_list(CourseID):
522 | conn = get_conn()
523 | cur = conn.cursor(dictionary=True)
524 | sql_str = f"""
525 | SELECT Title, TextContent, CreatedAt
526 | FROM Contents
527 | WHERE CourseID = {CourseID}
528 | """
529 | try:
530 | cur.execute(sql_str)
531 | except Error as e:
532 | cur.close()
533 | conn.close()
534 | return (False, str(e), None)
535 | content_list = cur.fetchall()
536 | cur.close()
537 | conn.close()
538 | return (True, "Contents retrieved from db, successfully", content_list)
539 |
540 |
541 | def get_course_student_list(CourseID):
542 | conn = get_conn()
543 | cur = conn.cursor(dictionary=True)
544 | sql_str = f"""
545 | SELECT Username, FirstName AS 'First Name', LastName AS 'Last Name',
546 | PhoneNumber AS 'Phone Number', Email, Faculty
547 | FROM Users
548 | WHERE ID IN
549 | (SELECT StudentID FROM StudentCourse
550 | WHERE CourseID = {CourseID})
551 | """
552 | try:
553 | cur.execute(sql_str)
554 | except Error as e:
555 | cur.close()
556 | conn.close()
557 | return (False, str(e), None)
558 | student_list = cur.fetchall()
559 | cur.close()
560 | conn.close()
561 | return (True, "Student list retrieved from db, successfully", student_list)
562 |
--------------------------------------------------------------------------------
/app/static/css/jquery.dataTables.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Table styles
3 | */
4 | table.dataTable {
5 | width: 100%;
6 | margin: 0 auto;
7 | clear: both;
8 | border-collapse: separate;
9 | border-spacing: 0;
10 | /*
11 | * Header and footer styles
12 | */
13 | /*
14 | * Body styles
15 | */
16 | }
17 | table.dataTable thead th,
18 | table.dataTable tfoot th {
19 | font-weight: bold;
20 | }
21 | table.dataTable thead th,
22 | table.dataTable thead td {
23 | padding: 10px 18px;
24 | border-bottom: 1px solid #111;
25 | }
26 | table.dataTable thead th:active,
27 | table.dataTable thead td:active {
28 | outline: none;
29 | }
30 | table.dataTable tfoot th,
31 | table.dataTable tfoot td {
32 | padding: 10px 18px 6px 18px;
33 | border-top: 1px solid #111;
34 | }
35 | table.dataTable thead .sorting,
36 | table.dataTable thead .sorting_asc,
37 | table.dataTable thead .sorting_desc,
38 | table.dataTable thead .sorting_asc_disabled,
39 | table.dataTable thead .sorting_desc_disabled {
40 | cursor: pointer;
41 | *cursor: hand;
42 | background-repeat: no-repeat;
43 | background-position: center right;
44 | }
45 | table.dataTable thead .sorting {
46 | background-image: url("../images/sort_both.png");
47 | }
48 | table.dataTable thead .sorting_asc {
49 | background-image: url("../images/sort_asc.png");
50 | }
51 | table.dataTable thead .sorting_desc {
52 | background-image: url("../images/sort_desc.png");
53 | }
54 | table.dataTable thead .sorting_asc_disabled {
55 | background-image: url("../images/sort_asc_disabled.png");
56 | }
57 | table.dataTable thead .sorting_desc_disabled {
58 | background-image: url("../images/sort_desc_disabled.png");
59 | }
60 | table.dataTable tbody tr {
61 | background-color: #ffffff;
62 | }
63 | table.dataTable tbody tr.selected {
64 | background-color: #B0BED9;
65 | }
66 | table.dataTable tbody th,
67 | table.dataTable tbody td {
68 | padding: 8px 10px;
69 | }
70 | table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
71 | border-top: 1px solid #ddd;
72 | }
73 | table.dataTable.row-border tbody tr:first-child th,
74 | table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
75 | table.dataTable.display tbody tr:first-child td {
76 | border-top: none;
77 | }
78 | table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
79 | border-top: 1px solid #ddd;
80 | border-right: 1px solid #ddd;
81 | }
82 | table.dataTable.cell-border tbody tr th:first-child,
83 | table.dataTable.cell-border tbody tr td:first-child {
84 | border-left: 1px solid #ddd;
85 | }
86 | table.dataTable.cell-border tbody tr:first-child th,
87 | table.dataTable.cell-border tbody tr:first-child td {
88 | border-top: none;
89 | }
90 | table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
91 | background-color: #f9f9f9;
92 | }
93 | table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
94 | background-color: #acbad4;
95 | }
96 | table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
97 | background-color: #f6f6f6;
98 | }
99 | table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
100 | background-color: #aab7d1;
101 | }
102 | table.dataTable.order-column tbody tr > .sorting_1,
103 | table.dataTable.order-column tbody tr > .sorting_2,
104 | table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
105 | table.dataTable.display tbody tr > .sorting_2,
106 | table.dataTable.display tbody tr > .sorting_3 {
107 | background-color: #fafafa;
108 | }
109 | table.dataTable.order-column tbody tr.selected > .sorting_1,
110 | table.dataTable.order-column tbody tr.selected > .sorting_2,
111 | table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
112 | table.dataTable.display tbody tr.selected > .sorting_2,
113 | table.dataTable.display tbody tr.selected > .sorting_3 {
114 | background-color: #acbad5;
115 | }
116 | table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
117 | background-color: #f1f1f1;
118 | }
119 | table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
120 | background-color: #f3f3f3;
121 | }
122 | table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
123 | background-color: whitesmoke;
124 | }
125 | table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
126 | background-color: #a6b4cd;
127 | }
128 | table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
129 | background-color: #a8b5cf;
130 | }
131 | table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
132 | background-color: #a9b7d1;
133 | }
134 | table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
135 | background-color: #fafafa;
136 | }
137 | table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
138 | background-color: #fcfcfc;
139 | }
140 | table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
141 | background-color: #fefefe;
142 | }
143 | table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
144 | background-color: #acbad5;
145 | }
146 | table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
147 | background-color: #aebcd6;
148 | }
149 | table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
150 | background-color: #afbdd8;
151 | }
152 | table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
153 | background-color: #eaeaea;
154 | }
155 | table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
156 | background-color: #ececec;
157 | }
158 | table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
159 | background-color: #efefef;
160 | }
161 | table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
162 | background-color: #a2aec7;
163 | }
164 | table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
165 | background-color: #a3b0c9;
166 | }
167 | table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
168 | background-color: #a5b2cb;
169 | }
170 | table.dataTable.no-footer {
171 | border-bottom: 1px solid #111;
172 | }
173 | table.dataTable.nowrap th, table.dataTable.nowrap td {
174 | white-space: nowrap;
175 | }
176 | table.dataTable.compact thead th,
177 | table.dataTable.compact thead td {
178 | padding: 4px 17px;
179 | }
180 | table.dataTable.compact tfoot th,
181 | table.dataTable.compact tfoot td {
182 | padding: 4px;
183 | }
184 | table.dataTable.compact tbody th,
185 | table.dataTable.compact tbody td {
186 | padding: 4px;
187 | }
188 | table.dataTable th.dt-left,
189 | table.dataTable td.dt-left {
190 | text-align: left;
191 | }
192 | table.dataTable th.dt-center,
193 | table.dataTable td.dt-center,
194 | table.dataTable td.dataTables_empty {
195 | text-align: center;
196 | }
197 | table.dataTable th.dt-right,
198 | table.dataTable td.dt-right {
199 | text-align: right;
200 | }
201 | table.dataTable th.dt-justify,
202 | table.dataTable td.dt-justify {
203 | text-align: justify;
204 | }
205 | table.dataTable th.dt-nowrap,
206 | table.dataTable td.dt-nowrap {
207 | white-space: nowrap;
208 | }
209 | table.dataTable thead th.dt-head-left,
210 | table.dataTable thead td.dt-head-left,
211 | table.dataTable tfoot th.dt-head-left,
212 | table.dataTable tfoot td.dt-head-left {
213 | text-align: left;
214 | }
215 | table.dataTable thead th.dt-head-center,
216 | table.dataTable thead td.dt-head-center,
217 | table.dataTable tfoot th.dt-head-center,
218 | table.dataTable tfoot td.dt-head-center {
219 | text-align: center;
220 | }
221 | table.dataTable thead th.dt-head-right,
222 | table.dataTable thead td.dt-head-right,
223 | table.dataTable tfoot th.dt-head-right,
224 | table.dataTable tfoot td.dt-head-right {
225 | text-align: right;
226 | }
227 | table.dataTable thead th.dt-head-justify,
228 | table.dataTable thead td.dt-head-justify,
229 | table.dataTable tfoot th.dt-head-justify,
230 | table.dataTable tfoot td.dt-head-justify {
231 | text-align: justify;
232 | }
233 | table.dataTable thead th.dt-head-nowrap,
234 | table.dataTable thead td.dt-head-nowrap,
235 | table.dataTable tfoot th.dt-head-nowrap,
236 | table.dataTable tfoot td.dt-head-nowrap {
237 | white-space: nowrap;
238 | }
239 | table.dataTable tbody th.dt-body-left,
240 | table.dataTable tbody td.dt-body-left {
241 | text-align: left;
242 | }
243 | table.dataTable tbody th.dt-body-center,
244 | table.dataTable tbody td.dt-body-center {
245 | text-align: center;
246 | }
247 | table.dataTable tbody th.dt-body-right,
248 | table.dataTable tbody td.dt-body-right {
249 | text-align: right;
250 | }
251 | table.dataTable tbody th.dt-body-justify,
252 | table.dataTable tbody td.dt-body-justify {
253 | text-align: justify;
254 | }
255 | table.dataTable tbody th.dt-body-nowrap,
256 | table.dataTable tbody td.dt-body-nowrap {
257 | white-space: nowrap;
258 | }
259 |
260 | table.dataTable,
261 | table.dataTable th,
262 | table.dataTable td {
263 | box-sizing: content-box;
264 | }
265 |
266 | /*
267 | * Control feature layout
268 | */
269 | .dataTables_wrapper {
270 | position: relative;
271 | clear: both;
272 | *zoom: 1;
273 | zoom: 1;
274 | }
275 | .dataTables_wrapper .dataTables_length {
276 | float: left;
277 | }
278 | .dataTables_wrapper .dataTables_length select {
279 | border: 1px solid #aaa;
280 | border-radius: 3px;
281 | padding: 5px;
282 | background-color: transparent;
283 | padding: 4px;
284 | }
285 | .dataTables_wrapper .dataTables_filter {
286 | float: right;
287 | text-align: right;
288 | }
289 | .dataTables_wrapper .dataTables_filter input {
290 | border: 1px solid #aaa;
291 | border-radius: 3px;
292 | padding: 5px;
293 | background-color: transparent;
294 | margin-left: 3px;
295 | }
296 | .dataTables_wrapper .dataTables_info {
297 | clear: both;
298 | float: left;
299 | padding-top: 0.755em;
300 | }
301 | .dataTables_wrapper .dataTables_paginate {
302 | float: right;
303 | text-align: right;
304 | padding-top: 0.25em;
305 | }
306 | .dataTables_wrapper .dataTables_paginate .paginate_button {
307 | box-sizing: border-box;
308 | display: inline-block;
309 | min-width: 1.5em;
310 | padding: 0.5em 1em;
311 | margin-left: 2px;
312 | text-align: center;
313 | text-decoration: none !important;
314 | cursor: pointer;
315 | *cursor: hand;
316 | color: #333 !important;
317 | border: 1px solid transparent;
318 | border-radius: 2px;
319 | }
320 | .dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
321 | color: #333 !important;
322 | border: 1px solid #979797;
323 | background-color: white;
324 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
325 | /* Chrome,Safari4+ */
326 | background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
327 | /* Chrome10+,Safari5.1+ */
328 | background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
329 | /* FF3.6+ */
330 | background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
331 | /* IE10+ */
332 | background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
333 | /* Opera 11.10+ */
334 | background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
335 | /* W3C */
336 | }
337 | .dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
338 | cursor: default;
339 | color: #666 !important;
340 | border: 1px solid transparent;
341 | background: transparent;
342 | box-shadow: none;
343 | }
344 | .dataTables_wrapper .dataTables_paginate .paginate_button:hover {
345 | color: white !important;
346 | border: 1px solid #111;
347 | background-color: #585858;
348 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
349 | /* Chrome,Safari4+ */
350 | background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
351 | /* Chrome10+,Safari5.1+ */
352 | background: -moz-linear-gradient(top, #585858 0%, #111 100%);
353 | /* FF3.6+ */
354 | background: -ms-linear-gradient(top, #585858 0%, #111 100%);
355 | /* IE10+ */
356 | background: -o-linear-gradient(top, #585858 0%, #111 100%);
357 | /* Opera 11.10+ */
358 | background: linear-gradient(to bottom, #585858 0%, #111 100%);
359 | /* W3C */
360 | }
361 | .dataTables_wrapper .dataTables_paginate .paginate_button:active {
362 | outline: none;
363 | background-color: #2b2b2b;
364 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
365 | /* Chrome,Safari4+ */
366 | background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
367 | /* Chrome10+,Safari5.1+ */
368 | background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
369 | /* FF3.6+ */
370 | background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
371 | /* IE10+ */
372 | background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
373 | /* Opera 11.10+ */
374 | background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
375 | /* W3C */
376 | box-shadow: inset 0 0 3px #111;
377 | }
378 | .dataTables_wrapper .dataTables_paginate .ellipsis {
379 | padding: 0 1em;
380 | }
381 | .dataTables_wrapper .dataTables_processing {
382 | position: absolute;
383 | top: 50%;
384 | left: 50%;
385 | width: 100%;
386 | height: 40px;
387 | margin-left: -50%;
388 | margin-top: -25px;
389 | padding-top: 20px;
390 | text-align: center;
391 | font-size: 1.2em;
392 | background-color: white;
393 | background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
394 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
395 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
396 | background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
397 | background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
398 | background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
399 | }
400 | .dataTables_wrapper .dataTables_length,
401 | .dataTables_wrapper .dataTables_filter,
402 | .dataTables_wrapper .dataTables_info,
403 | .dataTables_wrapper .dataTables_processing,
404 | .dataTables_wrapper .dataTables_paginate {
405 | color: #333;
406 | }
407 | .dataTables_wrapper .dataTables_scroll {
408 | clear: both;
409 | }
410 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
411 | *margin-top: -1px;
412 | -webkit-overflow-scrolling: touch;
413 | }
414 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {
415 | vertical-align: middle;
416 | }
417 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,
418 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,
419 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {
420 | height: 0;
421 | overflow: hidden;
422 | margin: 0 !important;
423 | padding: 0 !important;
424 | }
425 | .dataTables_wrapper.no-footer .dataTables_scrollBody {
426 | border-bottom: 1px solid #111;
427 | }
428 | .dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,
429 | .dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
430 | border-bottom: none;
431 | }
432 | .dataTables_wrapper:after {
433 | visibility: hidden;
434 | display: block;
435 | content: "";
436 | clear: both;
437 | height: 0;
438 | }
439 |
440 | @media screen and (max-width: 767px) {
441 | .dataTables_wrapper .dataTables_info,
442 | .dataTables_wrapper .dataTables_paginate {
443 | float: none;
444 | text-align: center;
445 | }
446 | .dataTables_wrapper .dataTables_paginate {
447 | margin-top: 0.5em;
448 | }
449 | }
450 | @media screen and (max-width: 640px) {
451 | .dataTables_wrapper .dataTables_length,
452 | .dataTables_wrapper .dataTables_filter {
453 | float: none;
454 | text-align: center;
455 | }
456 | .dataTables_wrapper .dataTables_filter {
457 | margin-top: 0.5em;
458 | }
459 | }
460 |
--------------------------------------------------------------------------------
/app/static/js/popper.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) Federico Zivolo 2019
3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
5 | //# sourceMappingURL=popper.min.js.map
6 |
--------------------------------------------------------------------------------
/app/static/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.4.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t=t||self).bootstrap={},t.jQuery,t.Popper)}(this,function(t,g,u){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Y.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Se,popperConfig:null},Fe="show",Ue="out",We={HIDE:"hide"+Oe,HIDDEN:"hidden"+Oe,SHOW:"show"+Oe,SHOWN:"shown"+Oe,INSERTED:"inserted"+Oe,CLICK:"click"+Oe,FOCUSIN:"focusin"+Oe,FOCUSOUT:"focusout"+Oe,MOUSEENTER:"mouseenter"+Oe,MOUSELEAVE:"mouseleave"+Oe},qe="fade",Me="show",Ke=".tooltip-inner",Qe=".arrow",Be="hover",Ve="focus",Ye="click",ze="manual",Xe=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Me))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(qe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,this._getPopperConfig(a)),g(o).addClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===Ue&&e._leave(null,e)};if(g(this.tip).hasClass(qe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){function e(){n._hoverState!==Fe&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),g(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()}var n=this,i=this.getTipElement(),o=g.Event(this.constructor.Event.HIDE);if(g(this.element).trigger(o),!o.isDefaultPrevented()){if(g(i).removeClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ye]=!1,this._activeTrigger[Ve]=!1,this._activeTrigger[Be]=!1,g(this.tip).hasClass(qe)){var r=_.getTransitionDurationFromElement(i);g(i).one(_.TRANSITION_END,e).emulateTransitionEnd(r)}else e();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Pe+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ke)),this.getTitle()),g(t).removeClass(qe+" "+Me)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=we(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t=t||("function"==typeof this.config.title?this.config.title.call(this.element):this.config.title)},t._getPopperConfig=function(t){var e=this;return l({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Qe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},{},this.config.popperConfig)},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,{},e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Re[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==ze){var e=t===Be?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Be?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),this._hideModalHandler=function(){i.element&&i.hide()},g(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");!this.element.getAttribute("title")&&"string"==t||(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Ve:Be]=!0),g(e.getTipElement()).hasClass(Me)||e._hoverState===Fe?e._hoverState=Fe:(clearTimeout(e._timeout),e._hoverState=Fe,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Fe&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Ve:Be]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=Ue,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===Ue&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==je.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,{},e,{},"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(Ae,t,this.constructor.DefaultType),t.sanitize&&(t.template=we(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Le);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(qe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ne),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ne,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return xe}},{key:"NAME",get:function(){return Ae}},{key:"DATA_KEY",get:function(){return Ne}},{key:"Event",get:function(){return We}},{key:"EVENT_KEY",get:function(){return Oe}},{key:"DefaultType",get:function(){return He}}]),i}();g.fn[Ae]=Xe._jQueryInterface,g.fn[Ae].Constructor=Xe,g.fn[Ae].noConflict=function(){return g.fn[Ae]=ke,Xe._jQueryInterface};var $e="popover",Ge="bs.popover",Je="."+Ge,Ze=g.fn[$e],tn="bs-popover",en=new RegExp("(^|\\s)"+tn+"\\S+","g"),nn=l({},Xe.Default,{placement:"right",trigger:"click",content:"",template:''}),on=l({},Xe.DefaultType,{content:"(string|element|function)"}),rn="fade",sn="show",an=".popover-header",ln=".popover-body",cn={HIDE:"hide"+Je,HIDDEN:"hidden"+Je,SHOW:"show"+Je,SHOWN:"shown"+Je,INSERTED:"inserted"+Je,CLICK:"click"+Je,FOCUSIN:"focusin"+Je,FOCUSOUT:"focusout"+Je,MOUSEENTER:"mouseenter"+Je,MOUSELEAVE:"mouseleave"+Je},hn=function(t){function i(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}(i,t);var e=i.prototype;return e.isWithContent=function(){return this.getTitle()||this._getContent()},e.addAttachmentClass=function(t){g(this.getTipElement()).addClass(tn+"-"+t)},e.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},e.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(an),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ln),e),t.removeClass(rn+" "+sn)},e._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},e._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(en);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",Qn='[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',Bn=".dropdown-toggle",Vn="> .dropdown-menu .active",Yn=function(){function i(t){this._element=t}var t=i.prototype;return t.show=function(){var n=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&g(this._element).hasClass(Rn)||g(this._element).hasClass(xn))){var t,i,e=g(this._element).closest(qn)[0],o=_.getSelectorFromElement(this._element);if(e){var r="UL"===e.nodeName||"OL"===e.nodeName?Kn:Mn;i=(i=g.makeArray(g(e).find(r)))[i.length-1]}var s=g.Event(jn.HIDE,{relatedTarget:this._element}),a=g.Event(jn.SHOW,{relatedTarget:i});if(i&&g(i).trigger(s),g(this._element).trigger(a),!a.isDefaultPrevented()&&!s.isDefaultPrevented()){o&&(t=document.querySelector(o)),this._activate(this._element,e);var l=function(){var t=g.Event(jn.HIDDEN,{relatedTarget:n._element}),e=g.Event(jn.SHOWN,{relatedTarget:i});g(i).trigger(t),g(n._element).trigger(e)};t?this._activate(t,t.parentNode,l):l()}}},t.dispose=function(){g.removeData(this._element,kn),this._element=null},t._activate=function(t,e,n){function i(){return o._transitionComplete(t,r,n)}var o=this,r=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?g(e).children(Mn):g(e).find(Kn))[0],s=n&&r&&g(r).hasClass(Fn);if(r&&s){var a=_.getTransitionDurationFromElement(r);g(r).removeClass(Un).one(_.TRANSITION_END,i).emulateTransitionEnd(a)}else i()},t._transitionComplete=function(t,e,n){if(e){g(e).removeClass(Rn);var i=g(e.parentNode).find(Vn)[0];i&&g(i).removeClass(Rn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}if(g(t).addClass(Rn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),_.reflow(t),t.classList.contains(Fn)&&t.classList.add(Un),t.parentNode&&g(t.parentNode).hasClass(Hn)){var o=g(t).closest(Wn)[0];if(o){var r=[].slice.call(o.querySelectorAll(Bn));g(r).addClass(Rn)}t.setAttribute("aria-expanded",!0)}n&&n()},i._jQueryInterface=function(n){return this.each(function(){var t=g(this),e=t.data(kn);if(e||(e=new i(this),t.data(kn,e)),"string"==typeof n){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}}]),i}();g(document).on(jn.CLICK_DATA_API,Qn,function(t){t.preventDefault(),Yn._jQueryInterface.call(g(this),"show")}),g.fn.tab=Yn._jQueryInterface,g.fn.tab.Constructor=Yn,g.fn.tab.noConflict=function(){return g.fn.tab=Ln,Yn._jQueryInterface};var zn="toast",Xn="bs.toast",$n="."+Xn,Gn=g.fn[zn],Jn={CLICK_DISMISS:"click.dismiss"+$n,HIDE:"hide"+$n,HIDDEN:"hidden"+$n,SHOW:"show"+$n,SHOWN:"shown"+$n},Zn="fade",ti="hide",ei="show",ni="showing",ii={animation:"boolean",autohide:"boolean",delay:"number"},oi={animation:!0,autohide:!0,delay:500},ri='[data-dismiss="toast"]',si=function(){function i(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var t=i.prototype;return t.show=function(){var t=this,e=g.Event(Jn.SHOW);if(g(this._element).trigger(e),!e.isDefaultPrevented()){this._config.animation&&this._element.classList.add(Zn);var n=function(){t._element.classList.remove(ni),t._element.classList.add(ei),g(t._element).trigger(Jn.SHOWN),t._config.autohide&&(t._timeout=setTimeout(function(){t.hide()},t._config.delay))};if(this._element.classList.remove(ti),_.reflow(this._element),this._element.classList.add(ni),this._config.animation){var i=_.getTransitionDurationFromElement(this._element);g(this._element).one(_.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},t.hide=function(){if(this._element.classList.contains(ei)){var t=g.Event(Jn.HIDE);g(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},t.dispose=function(){clearTimeout(this._timeout),this._timeout=null,this._element.classList.contains(ei)&&this._element.classList.remove(ei),g(this._element).off(Jn.CLICK_DISMISS),g.removeData(this._element,Xn),this._element=null,this._config=null},t._getConfig=function(t){return t=l({},oi,{},g(this._element).data(),{},"object"==typeof t&&t?t:{}),_.typeCheckConfig(zn,t,this.constructor.DefaultType),t},t._setListeners=function(){var t=this;g(this._element).on(Jn.CLICK_DISMISS,ri,function(){return t.hide()})},t._close=function(){function t(){e._element.classList.add(ti),g(e._element).trigger(Jn.HIDDEN)}var e=this;if(this._element.classList.remove(ei),this._config.animation){var n=_.getTransitionDurationFromElement(this._element);g(this._element).one(_.TRANSITION_END,t).emulateTransitionEnd(n)}else t()},i._jQueryInterface=function(n){return this.each(function(){var t=g(this),e=t.data(Xn);if(e||(e=new i(this,"object"==typeof n&&n),t.data(Xn,e)),"string"==typeof n){if("undefined"==typeof e[n])throw new TypeError('No method named "'+n+'"');e[n](this)}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}},{key:"DefaultType",get:function(){return ii}},{key:"Default",get:function(){return oi}}]),i}();g.fn[zn]=si._jQueryInterface,g.fn[zn].Constructor=si,g.fn[zn].noConflict=function(){return g.fn[zn]=Gn,si._jQueryInterface},t.Alert=v,t.Button=H,t.Carousel=ut,t.Collapse=wt,t.Dropdown=ee,t.Modal=Te,t.Popover=hn,t.Scrollspy=On,t.Tab=Yn,t.Toast=si,t.Tooltip=Xe,t.Util=_,Object.defineProperty(t,"__esModule",{value:!0})});
7 | //# sourceMappingURL=bootstrap.min.js.map
--------------------------------------------------------------------------------
/app/static/js/jquery-3.4.1.slim.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.4.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],v=g.document,r=Object.getPrototypeOf,s=t.slice,y=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,m=n.hasOwnProperty,a=m.toString,l=a.call(Object),b={},x=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},w=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||v).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)},d=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function p(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!x(e)&&!w(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+R+"?|("+R+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&((e?e.ownerDocument||e:m)!==T&&C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!S[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=N),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+be(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){S(t,!0)}finally{s===N&&e.removeAttribute("id")}}}return g(t.replace(F,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[N]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),m!==T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=N,!T.getElementsByName||!T.getElementsByName(N).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+N+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+N+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML=" ";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument===m&&y(m,e)?-1:t===T||t.ownerDocument===m&&y(m,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===T?-1:t===T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==T&&C(e),p.matchesSelector&&E&&!S[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){S(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=d[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&d(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function L(e,n,r){return x(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:v,!0)),D.test(r[1])&&E.isPlainObject(t))for(r in t)x(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=v.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):x(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,j=E(v);var O=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};function H(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i,he={option:[1,""," "],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;nx",b.noCloneChecked=!!ye.cloneNode(!0).lastChild.defaultValue;var we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ne(){return!1}function Ae(e,t){return e===function(){try{return v.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ne;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return E().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),e.each(function(){E.event.add(this,t,i,r,n)})}function Se(e,i,o){o?(G.set(e,i,!1),E.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=G.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(E.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),G.set(this,i,r),t=o(this,i),this[i](),r!==(n=G.get(this,i))||t?G.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(G.set(this,i,{value:E.event.trigger(E.extend(r[0],E.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===G.get(e,i)&&E.event.add(e,i,Ee)}E.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=G.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&E.find.matchesSelector(ie,i),n.guid||(n.guid=E.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof E&&E.event.triggered!==e.type?E.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(I)||[""]).length;while(l--)p=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),p&&(f=E.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=E.event.special[p]||{},c=E.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=u[p])||((d=u[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(p,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),E.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=G.hasData(e)&&G.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(I)||[""]).length;while(l--)if(p=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p){f=E.event.special[p]||{},d=u[p=(r?f.delegateType:f.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||E.removeEvent(e,p,v.handle),delete u[p])}else for(p in u)E.event.remove(e,p+t[l],n,r,!0);E.isEmptyObject(u)&&G.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=E.event.fix(e),u=new Array(arguments.length),l=(G.get(this,"events")||{})[s.type]||[],c=E.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,Le=/