start better rts inegration
This commit is contained in:
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user