start better rts inegration

This commit is contained in:
Ross
2026-06-29 10:44:39 +01:00
parent 2425e62caf
commit 1317d6a438
12 changed files with 255 additions and 7 deletions
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-06-29 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0029_exam_post_survey_mandatory_exam_pre_survey_mandatory'),
]
operations = [
migrations.AddField(
model_name='useranswer',
name='time_answered',
field=models.DateTimeField(blank=True, help_text='Time the question was answered (client-side)', null=True),
),
]
+9
View File
@@ -13,6 +13,15 @@ server {
client_max_body_size 100M; client_max_body_size 100M;
location /rts/ {
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_set_header X-Forwarded-Proto $scheme;
#proxy_set_header Host $host;
#proxy_redirect off;
#proxy_buffering off;
alias /usr/src/app/rts;
}
location /static/ { location /static/ {
alias /usr/src/app/static/; alias /usr/src/app/static/;
expires 30d; expires 30d;
+3
View File
@@ -19,6 +19,7 @@ services:
- ../logs:/var/log/rad - ../logs:/var/log/rad
- ../../media:/usr/src/app/media - ../../media:/usr/src/app/media
- ../../static:/usr/src/app/static - ../../static:/usr/src/app/static
- /home/ross/rts:/usr/src/app/rts
worker: worker:
env_file: env_file:
@@ -28,6 +29,7 @@ services:
- ../logs:/var/log/rad - ../logs:/var/log/rad
- ../../media:/usr/src/app/media - ../../media:/usr/src/app/media
- ../../static:/usr/src/app/static - ../../static:/usr/src/app/static
- /home/ross/rts:/usr/src/app/rts
entrypoint: entrypoint:
[ [
"sh", "sh",
@@ -45,6 +47,7 @@ services:
- ../logs:/var/log/rad:rw - ../logs:/var/log/rad:rw
- ../../media:/usr/src/app/media:ro - ../../media:/usr/src/app/media:ro
- ../../static:/usr/src/app/static:ro - ../../static:/usr/src/app/static:ro
- /home/ross/rts:/usr/src/app/rts:ro
loki: loki:
image: grafana/loki:2.8.2 image: grafana/loki:2.8.2
+1
View File
@@ -25,6 +25,7 @@ services:
- ../backups:/usr/src/app/backups - ../backups:/usr/src/app/backups
- ../../media:/usr/src/app/media - ../../media:/usr/src/app/media
- ../../static:/usr/src/app/static - ../../static:/usr/src/app/static
- /home/ross/rts:/usr/src/app/rts
ports: ports:
- 8000:8000 - 8000:8000
- 3459:3459 - 3459:3459
+5
View File
@@ -1326,6 +1326,11 @@ class UserAnswerBase(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)
time_answered = models.DateTimeField(
null=True,
blank=True,
help_text="Time the question was answered (client-side)",
)
class Meta: class Meta:
abstract = True abstract = True
+18 -7
View File
@@ -3440,6 +3440,15 @@ class ExamViews(View, LoginRequiredMixin):
# Mark as unmarked # Mark as unmarked
ans.score = ans.ScoreOptions.UNMARKED ans.score = ans.ScoreOptions.UNMARKED
time_answered_str = answer.get("time_answered")
if time_answered_str:
from django.utils.dateparse import parse_datetime
parsed_time = parse_datetime(time_answered_str)
if parsed_time:
if timezone.is_naive(parsed_time):
parsed_time = timezone.make_aware(parsed_time)
ans.time_answered = parsed_time
ans.full_clean() ans.full_clean()
ans.save() ans.save()
@@ -3467,14 +3476,16 @@ class ExamViews(View, LoginRequiredMixin):
cid_user_exam = exam.get_or_create_cid_user_exam( cid_user_exam = exam.get_or_create_cid_user_exam(
cid_user=c, start_time=t cid_user=c, start_time=t
) )
exam.exam_user_status.create( in_progress = request.POST.get("in_progress") == "true"
cid_user_exam=cid_user_exam, if not in_progress:
status="submitted", exam.exam_user_status.create(
extra="manual submission", cid_user_exam=cid_user_exam,
) status="submitted",
extra="manual submission",
)
cid_user_exam.end_time = timezone.now() cid_user_exam.end_time = timezone.now()
cid_user_exam.save() cid_user_exam.save()
return JsonResponse({"success": True, "question_count": n}) return JsonResponse({"success": True, "question_count": n})
return JsonResponse({"success": False, "error": "Invalid data"}) return JsonResponse({"success": False, "error": "Invalid data"})
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-06-29 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('longs', '0037_exam_post_survey_mandatory_exam_pre_survey_mandatory'),
]
operations = [
migrations.AddField(
model_name='useranswer',
name='time_answered',
field=models.DateTimeField(blank=True, help_text='Time the question was answered (client-side)', null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-06-29 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('physics', '0020_exam_post_survey_mandatory_exam_pre_survey_mandatory'),
]
operations = [
migrations.AddField(
model_name='useranswer',
name='time_answered',
field=models.DateTimeField(blank=True, help_text='Time the question was answered (client-side)', null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-06-29 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0019_exam_post_survey_mandatory_exam_pre_survey_mandatory'),
]
operations = [
migrations.AddField(
model_name='useranswer',
name='time_answered',
field=models.DateTimeField(blank=True, help_text='Time the question was answered (client-side)', null=True),
),
]
+111
View File
@@ -0,0 +1,111 @@
import json
import pytest
from django.urls import reverse
from django.utils import timezone
from rapids.models import Exam, Rapid as Question, UserAnswer, Answer
from generic.models import CidUser, CidUserExam, ExamUserStatus
from django.contrib.auth.models import User
@pytest.fixture
def test_setup(db):
# Create a test candidate
cid_user = CidUser.objects.create(cid=9999, passcode="pass123", name="RTS Candidate")
# Create an exam
exam = Exam.objects.create(name="RTS Integration Exam", exam_mode=True, active=True)
exam.valid_cid_users.add(cid_user)
# Create questions
q1 = Question.objects.create(history="Q1 History", normal=True)
q1.exams.add(exam)
# Create correct answers
Answer.objects.create(question=q1, answer="Normal", answer_compare="Normal", status=Answer.MarkOptions.CORRECT)
return {
"cid_user": cid_user,
"exam": exam,
"q1": q1,
}
def test_autosubmit_in_progress(client, test_setup):
exam = test_setup["exam"]
cid_user = test_setup["cid_user"]
q1 = test_setup["q1"]
answers = [
{
"aid": "session-1",
"cid": str(cid_user.cid),
"eid": f"rapid/{exam.pk}",
"qid": q1.pk,
"qidn": "1",
"passcode": cid_user.passcode,
"ans": "Normal",
"time_answered": "2026-06-29T12:00:00.000Z",
}
]
payload = {
"eid": f"rapid/{exam.pk}",
"cid": str(cid_user.cid),
"start_time": 1782730000.0,
"answers": json.dumps(answers),
"in_progress": "true",
}
response = client.post(reverse("rapids:exam_answers_submit"), payload)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["question_count"] == 1
# Verify answers stored with correct timestamp
user_answer = UserAnswer.objects.get(exam=exam, question=q1, cid=cid_user.cid)
assert user_answer.answer == "Normal"
assert user_answer.time_answered is not None
assert user_answer.time_answered.year == 2026
assert user_answer.time_answered.month == 6
assert user_answer.time_answered.day == 29
# Verify exam session is NOT complete / finalized
cid_user_exam = CidUserExam.objects.get(object_id=exam.pk, cid_user=cid_user)
assert cid_user_exam.end_time is None
assert cid_user_exam.completed is False
assert not ExamUserStatus.objects.filter(cid_user_exam=cid_user_exam, status="submitted").exists()
def test_final_submit(client, test_setup):
exam = test_setup["exam"]
cid_user = test_setup["cid_user"]
q1 = test_setup["q1"]
answers = [
{
"aid": "session-1",
"cid": str(cid_user.cid),
"eid": f"rapid/{exam.pk}",
"qid": q1.pk,
"qidn": "1",
"passcode": cid_user.passcode,
"ans": "Normal",
"time_answered": "2026-06-29T12:00:00.000Z",
}
]
payload = {
"eid": f"rapid/{exam.pk}",
"cid": str(cid_user.cid),
"start_time": 1782730000.0,
"answers": json.dumps(answers),
# in_progress absent (defaults to false)
}
response = client.post(reverse("rapids:exam_answers_submit"), payload)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
# Verify exam session IS complete / finalized
cid_user_exam = CidUserExam.objects.get(object_id=exam.pk, cid_user=cid_user)
assert cid_user_exam.end_time is not None
assert ExamUserStatus.objects.filter(cid_user_exam=cid_user_exam, status="submitted").exists()
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-06-29 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sbas', '0025_exam_post_survey_mandatory_exam_pre_survey_mandatory'),
]
operations = [
migrations.AddField(
model_name='useranswer',
name='time_answered',
field=models.DateTimeField(blank=True, help_text='Time the question was answered (client-side)', null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-06-29 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shorts', '0013_exam_post_survey_mandatory_exam_pre_survey_mandatory'),
]
operations = [
migrations.AddField(
model_name='useranswer',
name='time_answered',
field=models.DateTimeField(blank=True, help_text='Time the question was answered (client-side)', null=True),
),
]