diff --git a/anatomy/api.py b/anatomy/api.py index 1d2cf0e6..e7840cdf 100644 --- a/anatomy/api.py +++ b/anatomy/api.py @@ -3,7 +3,7 @@ from typing import List from django.shortcuts import get_object_or_404 from ninja import ModelSchema, Router -from generic.models import CidUser, ExamUserStatus +from generic.models import CidUser, CidUserExam, ExamUserStatus from .models import AnatomyQuestion as Question, Exam from django.core.exceptions import PermissionDenied @@ -29,6 +29,13 @@ class UserUserSchema(ModelSchema): model = get_user_model() model_fields = ["id", "email"] +class CidUserExamSchema(ModelSchema): + cid_user : CidUserSchema | None + user_user : UserUserSchema | None + class Config: + model = CidUserExam + model_fields = ["id"] + class ExamStatusSchema(ModelSchema): class Config: model = ExamUserStatus @@ -36,8 +43,9 @@ class ExamStatusSchema(ModelSchema): class ExamUserStatusSchema(ModelSchema): - cid_user : CidUserSchema | None - user_user : UserUserSchema | None + #cid_user : CidUserSchema | None + #user_user : UserUserSchema | None + cid_user_exam : CidUserExamSchema | None class Config: model = ExamUserStatus model_fields = ["id", "datetime", "status", "extra"] @@ -90,8 +98,8 @@ def get_exam_user_status_all(request, exam_id: int, latest:bool=True): results = exam.exam_user_status.all() if latest: - cid_users = exam.exam_user_status.filter(cid_user__isnull=False).order_by("cid_user", "-datetime").distinct("cid_user") - user_users = exam.exam_user_status.filter(user_user__isnull=False).order_by("user_user", "-datetime").distinct("user_user") + cid_users = exam.exam_user_status.filter(cid_user_exam__cid_user__isnull=False).order_by("cid_user_exam", "-datetime")#.distinct("cid_user") + user_users = exam.exam_user_status.filter(cid_user_exam__user_user__isnull=False).order_by("cid_user_exam", "-datetime")#.distinct("user_user") results = list(cid_users) results.extend(list(user_users)) @@ -107,7 +115,7 @@ def get_exam_user_status_cid_user(request, exam_id: int, cid: int): print(exam_id, cid) - return exam.exam_user_status.filter(cid_user__cid=cid) + return exam.exam_user_status.filter(cid_user_exam__cid_user__cid=cid) @router.get('/exam/{exam_id}/user_status/{user_id}/user', response=List[ExamStatusSchema]) def get_exam_user_status_user_user(request, exam_id: int, user_id: int): @@ -115,4 +123,4 @@ def get_exam_user_status_user_user(request, exam_id: int, user_id: int): if not exam.check_user_can_edit(request.user): raise HttpError(HTTPStatus.UNAUTHORIZED,"Exam author required") - return exam.exam_user_status.filter(user_user=user_id) \ No newline at end of file + return exam.exam_user_status.filter(cid_user_exam__user_user=user_id) \ No newline at end of file diff --git a/atlas/models.py b/atlas/models.py index 14f24e39..19338d70 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -811,7 +811,6 @@ class CaseCollection(ExamOrCollectionGenericBase): ) exam_user_status = GenericRelation(ExamUserStatus) - cid_user_exam = GenericRelation(CidUserExam) feedback_once_collection_complete = models.BooleanField(default=True, help_text="If true feedback is only given once the collection is complete. If false feedback is given after each case.") diff --git a/atlas/views.py b/atlas/views.py index 06132ce0..557e1128 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -1988,15 +1988,9 @@ def collection_take_overview( cid_user_exam.completed = True cid_user_exam.save() - if cid is not None: - c = CidUser.objects.get(cid=cid) - collection.exam_user_status.create( - cid_user=c, user_user=None, status="submitted", extra="manual submission" - ) - else: - collection.exam_user_status.create( - cid_user=None, user_user=request.user, status="submitted", extra="manual submission" - ) + collection.exam_user_status.create( + cid_user_exam=cid_user_exam, status="submitted", extra="manual submission" + ) return HttpResponse("True") else: @@ -2841,7 +2835,7 @@ def collection_reset_answers(request, exam_id: int): collection.exam_user_status.all().delete() # CidUserExams - CidUserExam.objects.filter(exam=collection).delete() + collection.cid_users.delete() else: diff --git a/generic/migrations/0014_examuserstatus_cid_user_exam.py b/generic/migrations/0014_examuserstatus_cid_user_exam.py new file mode 100644 index 00000000..3bd8b552 --- /dev/null +++ b/generic/migrations/0014_examuserstatus_cid_user_exam.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.2 on 2024-07-22 10:50 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('generic', '0013_examcollection_archive'), + ] + + operations = [ + migrations.AddField( + model_name='examuserstatus', + name='cid_user_exam', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.ciduserexam'), + ), + ] diff --git a/generic/migrations/0015_migrate_exam_user_status.py b/generic/migrations/0015_migrate_exam_user_status.py new file mode 100644 index 00000000..306ab0b8 --- /dev/null +++ b/generic/migrations/0015_migrate_exam_user_status.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.2 on 2024-07-22 10:53 + +from django.db import migrations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from generic.models import ExamUserStatus, CidUserExam + +def change_exam_user_status(apps, schema_editor): + ExamUserStatus: "ExamUserStatus" = apps.get_model("generic", "ExamUserStatus") + CidUserExam: "CidUserExam" = apps.get_model("generic", "CidUserExam") + + for exam_user_status in ExamUserStatus.objects.all(): + + cid_user_exam, created = CidUserExam.objects.get_or_create(content_type=exam_user_status.content_type, object_id=exam_user_status.object_id, user_user=exam_user_status.user_user, cid_user=exam_user_status.cid_user) + + exam_user_status.cid_user_exam = cid_user_exam + + exam_user_status.save() + +def change_exam_user_status_reverse(apps, schema_editor): + ExamUserStatus: "ExamUserStatus" = apps.get_model("generic", "ExamUserStatus") + for exam_user_status in ExamUserStatus.objects.all(): + exam_user_status.cid_user_exam = None + exam_user_status.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('generic', '0014_examuserstatus_cid_user_exam'), + ] + + operations = [ + migrations.RunPython(change_exam_user_status, change_exam_user_status_reverse) + ] diff --git a/generic/models.py b/generic/models.py index 211d4d0f..66915e36 100644 --- a/generic/models.py +++ b/generic/models.py @@ -1074,14 +1074,16 @@ class ExamUserStatus(models.Model): object_id = models.PositiveIntegerField() exam = GenericForeignKey("content_type", "object_id") + cid_user_exam = models.ForeignKey("CidUserExam", blank=True, null=True, on_delete=models.SET_NULL) + def __str__(self): - self.cid_user: "CidUser" - if self.cid_user: - user = self.cid_user.cid - email = self.cid_user.email + self.cid_user_exam: "CidUserExam" + if self.cid_user_exam.cid_user: + user = self.cid_user_exam.cid_user.cid + email = self.cid_user_exam.cid_user.email return f"{self.datetime:%Y-%m-%d %H:%M:%S}: [CID] {user} ({email}) - {self.status} ({self.extra})" else: - user = self.user_user.username + user = self.cid_user_exam.user_user.username return f"{self.datetime:%Y-%m-%d %H:%M:%S}: [USER] {user} - {self.status} ({self.extra})" @@ -1440,7 +1442,7 @@ class CidUserExam(models.Model): else: end_time = f"{ self.end_time:%Y-%m-%d %H:%M }" - return f"{user}: {start_time} {end_time}" + return f"{self.exam} / {user}: {start_time} {end_time}" CID_GROUP_EXAMS = ( diff --git a/generic/views.py b/generic/views.py index 5b21f0fd..44d4d849 100644 --- a/generic/views.py +++ b/generic/views.py @@ -1105,9 +1105,9 @@ class ExamViews(View, LoginRequiredMixin): raise PermissionDenied if cid is not None: - statuses = exam.exam_user_status.filter(cid_user__cid=cid) + statuses = exam.exam_user_status.filter(cid_user_exam__cid_user__cid=cid) elif user_id is not None: - statuses = exam.exam_user_status.filter(user_user__id=user_id) + statuses = exam.exam_user_status.filter(cid_user_exam__user_user__id=user_id) else: statuses = exam.exam_user_status.all() @@ -1692,17 +1692,14 @@ class ExamViews(View, LoginRequiredMixin): cid_user_exam = exam.get_or_create_cid_user_exam( user_user=request.user, start_time=t ) - exam.exam_user_status.create( - cid_user=None, user_user=request.user, status="submitted", extra="manual submission" - ) else: c = CidUser.objects.filter(cid=request.POST.get("cid")).first() cid_user_exam = exam.get_or_create_cid_user_exam( cid_user=c, start_time=t ) - exam.exam_user_status.create( - cid_user=c, user_user=None, status="submitted", extra="manual submission" - ) + exam.exam_user_status.create( + cid_user_exam=cid_user_exam, status="submitted", extra="manual submission" + ) cid_user_exam.end_time = timezone.now() cid_user_exam.save() @@ -1738,14 +1735,11 @@ class ExamViews(View, LoginRequiredMixin): # Log exam access if exam.exam_mode: - if cid is not None: - user = None - cid_user = CidUser.objects.get(cid=cid) - else: - user = request.user - cid_user = None + cid_user_exam = exam.get_or_create_cid_user_exam( + cid=cid, user_user=request.user + ) exam.exam_user_status.create( - cid_user=cid_user, user_user=user, status="downloaded" + cid_user_exam=cid_user_exam, status="downloaded" ) path = "{0}{1}/exam/{2}.json".format(settings.MEDIA_ROOT, self.app_name, pk) @@ -1818,14 +1812,11 @@ class ExamViews(View, LoginRequiredMixin): # Log exam access if exam.exam_mode: - user = request.user - if cid is not None: - user = None - cid_user = CidUser.objects.get(cid=cid) - else: - cid_user = None + cid_user_exam = exam.get_or_create_cid_user_exam( + cid=cid, user_user=request.user + ) exam.exam_user_status.create( - cid_user=cid_user, user_user=user, status="downloaded", extra="unbased" + cid_user_exam=cid_user_exam, status="downloaded", extra="unbased" ) return JsonResponse(exam_json)