Skip to content

Commit 40a4e2a

Browse files
committed
links to task attempt pages and pages themselves
1 parent 8e09b66 commit 40a4e2a

File tree

11 files changed

+223
-5
lines changed

11 files changed

+223
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.venv
12
venv
23
.idea
34
ssl

app/api/task_attempts.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from app.lti_session_passback.auth_checkers import check_auth, is_admin
88
from app.mongo_odm import TaskAttemptsDBManager, TasksDBManager
99
from app.utils import check_arguments_are_convertible_to_object_id, check_argument_is_convertible_to_object_id
10+
from app.localisation import t
11+
from app.status import PassBackStatus
12+
13+
from app.mongo_models import TaskAttempts
1014

1115
api_task_attempts = Blueprint('api_task_attempts', __name__)
1216
logger = get_root_logger()
@@ -15,7 +19,7 @@
1519

1620
@check_arguments_are_convertible_to_object_id
1721
@api_task_attempts.route('/api/task-attempts/', methods=['GET'])
18-
def get_current_task_attempt() -> (dict, int):
22+
def get_current_task_attempt() -> tuple[dict, int]:
1923
"""
2024
Endpoint to get current task attempt information.
2125
@@ -56,10 +60,49 @@ def get_current_task_attempt() -> (dict, int):
5660
'attempt_count': task_db.attempt_count,
5761
}, 200
5862

63+
def get_task_attempt_information(task_attempt_db: TaskAttempts) -> dict:
64+
try:
65+
is_passed_back = dict(task_attempt_db.is_passed_back)
66+
67+
for training_id, training_status in is_passed_back.items():
68+
is_passed_back[training_id] = t(PassBackStatus.russian.get(training_status))
69+
70+
return {
71+
'message': 'OK',
72+
'task_id': str(task_attempt_db.task_id),
73+
'username': str(task_attempt_db.username),
74+
'training_count': task_attempt_db.training_count,
75+
'training_scores': dict(task_attempt_db.training_scores),
76+
'is_passed_back': is_passed_back,
77+
'params_for_passback': dict(task_attempt_db.params_for_passback)
78+
}
79+
except Exception as e:
80+
return {'message': '{}: {}'.format(e.__class__, e)}
81+
82+
83+
@check_arguments_are_convertible_to_object_id
84+
@api_task_attempts.route('/api/task-attempts/<task_attempt_id>/', methods=['GET'])
85+
def get_task_attempt(task_attempt_id) -> tuple[dict, int]:
86+
"""
87+
Endpoint to get information about a task attempt by its identifier.
88+
89+
:param task_attempt_id: Task attempt identifier
90+
:return: Dictionary with task attempt information and 'OK' message, or
91+
a dictionary with an explanation and 404 HTTP return code if a task attempt was not found, or
92+
an empty dictionary with 404 HTTP return code if access was denied.
93+
"""
94+
if not check_access({'_id': ObjectId(task_attempt_id)}):
95+
return {}, 404
96+
97+
task_attempt_db = TaskAttemptsDBManager().get_task_attempt(task_attempt_id)
98+
99+
if task_attempt_db is None:
100+
return {'message': 'No task attempt with task_attempt_id = {}.'.format(task_attempt_id)}, 404
101+
return get_task_attempt_information(task_attempt_db), 200
59102

60103
@check_arguments_are_convertible_to_object_id
61104
@api_task_attempts.route('/api/task_attempts/<task_attempt_id>/', methods=['DELETE'])
62-
def delete_task_attempt_by_task_attempt_id(task_attempt_id: str) -> (dict, int):
105+
def delete_task_attempt_by_task_attempt_id(task_attempt_id: str) -> tuple[dict, int]:
63106
"""
64107
Endpoint to delete a task attempt by its identifier.
65108

app/routes/task_attempts.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from app.root_logger import get_root_logger
2+
3+
from bson import ObjectId
4+
from flask import Blueprint, render_template, jsonify, request, session
5+
from app.localisation import *
6+
7+
from app.api.task_attempts import get_task_attempt
8+
from app.check_access import check_access
9+
from app.lti_session_passback.auth_checkers import check_admin, check_auth
10+
from app.utils import check_arguments_are_convertible_to_object_id
11+
12+
routes_task_attempts = Blueprint('routes_task_attempts', __name__)
13+
logger = get_root_logger()
14+
15+
@check_arguments_are_convertible_to_object_id
16+
@routes_task_attempts.route('/task_attempts/<task_attempt_id>/', methods=['GET'])
17+
def view_task_attempt(task_attempt_id: str):
18+
"""
19+
Route to view page with task attempt.
20+
21+
:param task_attempt_id: Task attempt identifier
22+
:return: Page or an empty dictionary with 404 HTTP code if access was denied.
23+
"""
24+
25+
if not check_access({'_id': ObjectId(task_attempt_id)}):
26+
return {}, 404
27+
28+
# Нужна ли проверка авторизации?
29+
30+
task_attempt, task_attempt_status_code = get_task_attempt(task_attempt_id)
31+
32+
if task_attempt.get('message') != 'OK':
33+
return task_attempt, task_attempt_status_code
34+
35+
return render_template(
36+
'task_attempt.html',
37+
task_attempt_id=task_attempt_id,
38+
task_id=task_attempt['task_id'],
39+
username=task_attempt['username'],
40+
training_scores=task_attempt['training_scores'],
41+
is_passed_back=task_attempt['is_passed_back'],
42+
), 200

app/static/css/task_attempt.css

Whitespace-only changes.

app/static/js/show_all_trainings.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ function buildCurrentTrainingRow(trainingId, trainingJson, is_Admin=false) {
2121

2222
const trainingAttemptIdElement = document.createElement("td");
2323
if(trainingJson["task_attempt_id"] !== "undefined" && trainingJson["message"] === "OK"){
24-
trainingAttemptIdElement.textContent = "..." + String(trainingJson["task_attempt_id"]).slice(-5);
24+
const trainingAttemptIdLink = document.createElement("a");
25+
trainingAttemptIdLink.href=`/task_attempts/${trainingJson["task_attempt_id"]}`;
26+
trainingAttemptIdLink.textContent = `...${(trainingJson["task_attempt_id"]).slice(-5)}`;
27+
trainingAttemptIdElement.appendChild(trainingAttemptIdLink);
2528
}
2629
currentTrainingRowElement.appendChild(trainingAttemptIdElement);
2730

app/static/js/task_attempts.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
function getTrainingsTable(trainingScores, isPassedBack) {
2+
const trainingsTable = {};
3+
4+
Object.keys(trainingScores).forEach(trainingId => {
5+
if (trainingsTable[trainingId] === undefined) {
6+
trainingsTable[trainingId] = {};
7+
}
8+
trainingsTable[trainingId].score = trainingScores[trainingId];
9+
});
10+
11+
Object.keys(isPassedBack).forEach(trainingId => {
12+
if (trainingsTable[trainingId] === undefined) {
13+
trainingsTable[trainingId] = {};
14+
}
15+
trainingsTable[trainingId].passedBackStatus = isPassedBack[trainingId];
16+
});
17+
18+
return trainingsTable;
19+
}
20+
21+
function createTableHeaderElement() {
22+
const trainingIdHeaderElement = document.createElement("th");
23+
trainingIdHeaderElement.innerHTML = "training_id";
24+
25+
const trainingScoreHeaderElement = document.createElement("th");
26+
trainingScoreHeaderElement.innerHTML = "score";
27+
28+
const trainingStatusHeaderElement = document.createElement("th");
29+
trainingStatusHeaderElement.innerHTML = "passed_back_status";
30+
31+
const tableHeaderElement = document.createElement("tr");
32+
33+
tableHeaderElement.append(trainingIdHeaderElement, trainingScoreHeaderElement, trainingStatusHeaderElement);
34+
35+
return tableHeaderElement;
36+
}
37+
38+
function createTableRowElement(trainingInfo, trainingId) {
39+
const tableRowElement = document.createElement("tr");
40+
41+
const tableRowIdElement = document.createElement("td");
42+
tableRowIdElement.innerHTML = `<a href="/trainings/statistics/${trainingId}/">${trainingId}</a>`;
43+
44+
const tableRowScoreElement = document.createElement("td");
45+
tableRowScoreElement.innerHTML = trainingInfo.score.toFixed(2) || "none";
46+
47+
const tableRowStatusElement = document.createElement("td");
48+
tableRowStatusElement.innerHTML = trainingInfo.passedBackStatus || "none";
49+
50+
tableRowElement.append(tableRowIdElement, tableRowScoreElement, tableRowStatusElement);
51+
52+
return tableRowElement;
53+
}
54+
55+
function showRelatedTrainingsTable(trainingScores, isPassedBack) {
56+
const trainingsTable = getTrainingsTable(trainingScores, isPassedBack);
57+
58+
const tableElement = document.getElementById("related-trainings-table");
59+
60+
tableElement.appendChild(createTableHeaderElement());
61+
62+
Object.keys(trainingsTable).forEach(trainingId => {
63+
tableElement.appendChild(createTableRowElement(trainingsTable[trainingId], trainingId));
64+
});
65+
}

app/static/js/training_statistics.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ function buildCurrentAttemptStatistics() {
55
.then(response => {
66
let trainingNumber = response["training_number"];
77
let attemptCount = response["attempt_count"];
8-
if (response === {} || trainingNumber === attemptCount) {
8+
if (jQuery.isEmptyObject(response) || trainingNumber === attemptCount) {
99
return;
1010
}
1111
document.getElementById("training-number").textContent

app/templates/task_attempt.html

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<link rel="stylesheet" href="{{ url_for('static', filename='css/task_attempt.css') }}">
5+
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
6+
<link rel="stylesheet" href="{{ url_for('static', filename='css/libraries/jquery-ui/jquery-ui.min.css') }}">
7+
8+
{% endblock %}
9+
10+
{% block content %}
11+
<div class="base-container">
12+
<h3>{{ title }}</h3>
13+
<h3>Информация по попытке с ID {{ task_attempt_id }}</h3>
14+
<h3>task_id: {{ task_id }}</h3>
15+
<h3>Имя пользователя: {{ username }}</h3>
16+
<div class="related-trainings-container">
17+
<h3>Связанные тренировки:</h3>
18+
<table id="related-trainings-table" class="table center-align-table"></table>
19+
</div>
20+
</div>
21+
<script src="{{ url_for('static', filename='js/libraries/jquery.min.js') }}"></script>
22+
<script src="{{ url_for('static', filename='js/task_attempts.js') }}"></script>
23+
<script type="text/javascript">
24+
showRelatedTrainingsTable({{ training_scores|tojson|safe }}, {{ is_passed_back|tojson|safe }});
25+
</script>
26+
{% endblock %}

app/web_speech_trainer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from app.routes.lti import routes_lti
3030
from app.routes.presentations import routes_presentations
3131
from app.routes.trainings import routes_trainings
32+
from app.routes.task_attempts import routes_task_attempts
3233
from app.routes.version import routes_version
3334
from app.status import PassBackStatus, TrainingStatus
3435
from app.training_manager import TrainingManager
@@ -52,6 +53,7 @@
5253
app.register_blueprint(routes_lti)
5354
app.register_blueprint(routes_presentations)
5455
app.register_blueprint(routes_trainings)
56+
app.register_blueprint(routes_task_attempts)
5557
app.register_blueprint(routes_version)
5658

5759
logger = get_root_logger(service_name='web')

app_conf/no_docker.ini

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[constants]
2+
presentation_file_max_size_in_megabytes=16
3+
app_secret_key=secret_key_placeholder
4+
lti_consumer_key=secretconsumerkey
5+
lti_consumer_secret=supersecretconsumersecret
6+
version_file=VERSION.json
7+
backup_path=../dump/database-dump/
8+
9+
[mongodb]
10+
url=mongodb://localhost:27017/
11+
database_name=database
12+
13+
[vosk]
14+
url=ws://localhost:2700
15+
16+
[whisper]
17+
url=http://whisper:9000/asr
18+
19+
[user_agent_platform]
20+
windows=True
21+
linux=True
22+
23+
[user_agent_browser]
24+
chrome=89
25+
firefox=87
26+
27+
[locale]
28+
language=ru
29+
30+
[bugreport]
31+
form_link=https://docs.google.com/forms/d/e/1FAIpQLScUudcDPUwtTvmN_sbeljicHYhubK7pPQIM1o8Wh54HstT2BQ/viewform?usp=sf_link
32+

0 commit comments

Comments
 (0)