feat: Implement sharing functionality for CidUserExam attempts, including user management and sharing settings

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Ross
2026-04-28 22:09:06 +01:00
parent cc100dba89
commit 7062723433
8 changed files with 597 additions and 3 deletions
+86
View File
@@ -1681,6 +1681,92 @@ class CidUserExam(models.Model):
share_with_supervisor = models.BooleanField(default=False, help_text="If true the exam status and results will be available to a users supervisor")
shared_with_users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name="shared_cid_user_exams",
help_text="Users with whom this attempt has been manually shared.",
)
def get_sharing_info(self):
"""Return a structured dict describing who can view this attempt and why.
Returns a dict with keys:
always_visible_to list of User objects (authors + markers of the exam)
supervisor User or None (the supervisor's Django account, if applicable)
supervisor_enabled bool (CidUserExam.share_with_supervisor value)
exam_supervisor_visible bool (exam-level results_supervisor_visible flag)
manual_shares QuerySet of User objects (shared_with_users)
"""
exam = self.exam
always_visible = []
seen_pks = set()
for attr in ("author", "markers"):
if hasattr(exam, attr):
for u in getattr(exam, attr).all():
if u.pk not in seen_pks:
seen_pks.add(u.pk)
always_visible.append(u)
exam_supervisor_visible = getattr(exam, "results_supervisor_visible", False)
supervisor_user = None
if self.share_with_supervisor or exam_supervisor_visible:
if self.user_user_id:
try:
sup = self.user_user.userprofile.supervisor
if sup and sup.user_id:
supervisor_user = sup.user
except Exception:
pass
elif self.cid_user_id:
sup = self.cid_user.supervisor
if sup and sup.user_id:
supervisor_user = sup.user
return {
"always_visible_to": always_visible,
"supervisor": supervisor_user,
"supervisor_enabled": self.share_with_supervisor,
"exam_supervisor_visible": exam_supervisor_visible,
"manual_shares": self.shared_with_users.all(),
}
def can_user_view(self, user):
"""Check whether a given authenticated User may view this attempt.
Permitted if the user is:
- The attempt owner (user_user)
- An author or marker of the related exam/collection
- The supervisor of the attempt owner (when share_with_supervisor=True or
the exam-level results_supervisor_visible flag is set)
- Manually listed in shared_with_users
"""
if not user or not user.is_authenticated:
return False
if self.user_user_id and self.user_user_id == user.pk:
return True
exam = self.exam
for attr in ("author", "markers"):
if hasattr(exam, attr) and getattr(exam, attr).filter(pk=user.pk).exists():
return True
exam_supervisor_visible = getattr(exam, "results_supervisor_visible", False)
if self.share_with_supervisor or exam_supervisor_visible:
if self.user_user_id:
try:
sup = self.user_user.userprofile.supervisor
if sup and sup.user_id == user.pk:
return True
except Exception:
pass
elif self.cid_user_id:
sup = self.cid_user.supervisor
if sup and sup.user_id == user.pk:
return True
if self.shared_with_users.filter(pk=user.pk).exists():
return True
return False
def __str__(self) -> str:
if self.cid_user is None:
try: