.
This commit is contained in:
+188
-188
@@ -1,189 +1,189 @@
|
||||
from django.forms import (
|
||||
Form,
|
||||
ModelForm,
|
||||
ModelMultipleChoiceField,
|
||||
ModelChoiceField,
|
||||
ChoiceField,
|
||||
CharField,
|
||||
HiddenInput,
|
||||
)
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote
|
||||
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||
|
||||
from rapids.models import Rapid
|
||||
from anatomy.models import AnatomyQuestion
|
||||
from longs.models import Long
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
from physics.models import CidUserAnswer as PhysicsCidUserAnswer, Question
|
||||
from physics.models import Exam as PhysicsExam
|
||||
from physics.models import Question as SbasQuestion
|
||||
|
||||
from anatomy.models import CidUserAnswer as AnatomyCidUserAnswer
|
||||
from anatomy.models import Exam as AnatomyExam
|
||||
from anatomy.models import AnatomyQuestion
|
||||
from anatomy.models import Answer as AnatomyAnswer
|
||||
|
||||
from rapids.models import CidUserAnswer as RapidsCidUserAnswer
|
||||
from rapids.models import Exam as RapidsExam
|
||||
from rapids.models import Rapid
|
||||
from rapids.models import Answer as RapidAnswer
|
||||
|
||||
from longs.models import CidUserAnswer as LongsCidUserAnswer
|
||||
from longs.models import Exam as LongsExam
|
||||
from longs.models import Long
|
||||
|
||||
from sbas.models import CidUserAnswer as SbasCidUserAnswer
|
||||
from sbas.models import Exam as SbasExam
|
||||
from sbas.models import Question as SbasQuestion
|
||||
|
||||
class ExaminationForm(ModelForm):
|
||||
class Meta:
|
||||
model = Examination
|
||||
fields = ["examination"]
|
||||
|
||||
|
||||
class QuestionNoteForm(ModelForm):
|
||||
class Meta:
|
||||
model = QuestionNote
|
||||
fields = ["content_type", "object_id", "author", "note_type", "note"]
|
||||
widgets = {"content_type": HiddenInput(), "object_id": HiddenInput()}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
question_type = kwargs.pop("question_type")
|
||||
pk = kwargs.pop("pk")
|
||||
user = kwargs.pop("user")
|
||||
|
||||
# if question_type == "rapid":
|
||||
# question = Rapid
|
||||
# content_type = ContentType.objects.get(model="rapid")
|
||||
# elif question_type == "anatomy":
|
||||
# question = AnatomyQuestion
|
||||
# content_type = ContentType.objects.get(model="anatomyquestion")
|
||||
# elif question_type == "long":
|
||||
# question = Long
|
||||
# content_type = ContentType.objects.get(model="long")
|
||||
# else:
|
||||
# raise PermissionError()
|
||||
|
||||
# get_object_or_404(question, pk=pk)
|
||||
# We get the 'initial' keyword argument or initialize it
|
||||
# as a dict if it didn't exist.
|
||||
# if kwargs.get("instance"):
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
super(QuestionNoteForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# initial = kwargs.setdefault("initial", {})
|
||||
## The widget for a ModelMultipleChoiceField expects
|
||||
## a list of primary key for the selected data.
|
||||
##initial["content_type"] = question
|
||||
##initial["content_id"] = pk
|
||||
# self.fields["content_type"].initial = content_type
|
||||
# self.fields["object_id"].initial = pk
|
||||
|
||||
|
||||
class CidUserForm(ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get("instance"):
|
||||
# We get the 'initial' keyword argument or initialize it
|
||||
# as a dict if it didn't exist.
|
||||
initial = kwargs.setdefault("initial", {})
|
||||
# The widget for a ModelMultipleChoiceField expects
|
||||
# a list of primary key for the selected data.
|
||||
initial["physics_exams"] = [t.pk for t in kwargs["instance"].physics_exams.all()]
|
||||
initial["anatomy_exams"] = [t.pk for t in kwargs["instance"].anatomy_exams.all()]
|
||||
initial["sba_exams"] = [t.pk for t in kwargs["instance"].sba_exams.all()]
|
||||
initial["rapid_exams"] = [t.pk for t in kwargs["instance"].rapid_exams.all()]
|
||||
initial["longs_exams"] = [t.pk for t in kwargs["instance"].longs_exams.all()]
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
|
||||
super(CidUserForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["physics_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=PhysicsExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Physics Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["anatomy_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=AnatomyExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Anatomy Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["sba_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=SbasExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="SBA Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["rapid_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=RapidsExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["longs_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=LongsExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Long Exams", is_stacked=False),
|
||||
)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Get the unsaved Long instance
|
||||
instance = ModelForm.save(self, False)
|
||||
|
||||
# Prepare a 'save_m2m' method for the form,
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def save_m2m():
|
||||
old_save_m2m()
|
||||
instance.physics_exams.clear()
|
||||
for physics_exams in self.cleaned_data["physics_exams"]:
|
||||
instance.physics_exams.add(physics_exams.pk)
|
||||
instance.anatomy_exams.clear()
|
||||
for anatomy_exams in self.cleaned_data["anatomy_exams"]:
|
||||
instance.anatomy_exams.add(anatomy_exams.pk)
|
||||
instance.sba_exams.clear()
|
||||
for sba_exams in self.cleaned_data["sba_exams"]:
|
||||
instance.sba_exams.add(sba_exams.pk)
|
||||
instance.rapid_exams.clear()
|
||||
for rapid_exams in self.cleaned_data["rapid_exams"]:
|
||||
instance.rapid_exams.add(rapid_exams.pk)
|
||||
instance.longs_exams.clear()
|
||||
for longs_exams in self.cleaned_data["longs_exams"]:
|
||||
instance.longs_exams.add(longs_exams.pk)
|
||||
|
||||
self.save_m2m = save_m2m
|
||||
|
||||
# Do we need to save all changes now?
|
||||
# if commit:
|
||||
instance.save()
|
||||
self.save_m2m()
|
||||
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = CidUser
|
||||
fields = [
|
||||
"passcode",
|
||||
"active",
|
||||
"internal_candidate",
|
||||
"name",
|
||||
"email",
|
||||
"supervisor_email",
|
||||
"login_email_sent",
|
||||
"results_email_sent",
|
||||
"group",
|
||||
]
|
||||
|
||||
class CidUserGroupForm(ModelForm):
|
||||
class Meta:
|
||||
model = CidUserGroup
|
||||
from django.forms import (
|
||||
Form,
|
||||
ModelForm,
|
||||
ModelMultipleChoiceField,
|
||||
ModelChoiceField,
|
||||
ChoiceField,
|
||||
CharField,
|
||||
HiddenInput,
|
||||
)
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote
|
||||
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||
|
||||
from rapids.models import Rapid
|
||||
from anatomy.models import AnatomyQuestion
|
||||
from longs.models import Long
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
from physics.models import CidUserAnswer as PhysicsCidUserAnswer, Question
|
||||
from physics.models import Exam as PhysicsExam
|
||||
from physics.models import Question as SbasQuestion
|
||||
|
||||
from anatomy.models import CidUserAnswer as AnatomyCidUserAnswer
|
||||
from anatomy.models import Exam as AnatomyExam
|
||||
from anatomy.models import AnatomyQuestion
|
||||
from anatomy.models import Answer as AnatomyAnswer
|
||||
|
||||
from rapids.models import CidUserAnswer as RapidsCidUserAnswer
|
||||
from rapids.models import Exam as RapidsExam
|
||||
from rapids.models import Rapid
|
||||
from rapids.models import Answer as RapidAnswer
|
||||
|
||||
from longs.models import CidUserAnswer as LongsCidUserAnswer
|
||||
from longs.models import Exam as LongsExam
|
||||
from longs.models import Long
|
||||
|
||||
from sbas.models import CidUserAnswer as SbasCidUserAnswer
|
||||
from sbas.models import Exam as SbasExam
|
||||
from sbas.models import Question as SbasQuestion
|
||||
|
||||
class ExaminationForm(ModelForm):
|
||||
class Meta:
|
||||
model = Examination
|
||||
fields = ["examination"]
|
||||
|
||||
|
||||
class QuestionNoteForm(ModelForm):
|
||||
class Meta:
|
||||
model = QuestionNote
|
||||
fields = ["content_type", "object_id", "author", "note_type", "note"]
|
||||
widgets = {"content_type": HiddenInput(), "object_id": HiddenInput()}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
question_type = kwargs.pop("question_type")
|
||||
pk = kwargs.pop("pk")
|
||||
user = kwargs.pop("user")
|
||||
|
||||
# if question_type == "rapid":
|
||||
# question = Rapid
|
||||
# content_type = ContentType.objects.get(model="rapid")
|
||||
# elif question_type == "anatomy":
|
||||
# question = AnatomyQuestion
|
||||
# content_type = ContentType.objects.get(model="anatomyquestion")
|
||||
# elif question_type == "long":
|
||||
# question = Long
|
||||
# content_type = ContentType.objects.get(model="long")
|
||||
# else:
|
||||
# raise PermissionError()
|
||||
|
||||
# get_object_or_404(question, pk=pk)
|
||||
# We get the 'initial' keyword argument or initialize it
|
||||
# as a dict if it didn't exist.
|
||||
# if kwargs.get("instance"):
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
super(QuestionNoteForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# initial = kwargs.setdefault("initial", {})
|
||||
## The widget for a ModelMultipleChoiceField expects
|
||||
## a list of primary key for the selected data.
|
||||
##initial["content_type"] = question
|
||||
##initial["content_id"] = pk
|
||||
# self.fields["content_type"].initial = content_type
|
||||
# self.fields["object_id"].initial = pk
|
||||
|
||||
|
||||
class CidUserForm(ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get("instance"):
|
||||
# We get the 'initial' keyword argument or initialize it
|
||||
# as a dict if it didn't exist.
|
||||
initial = kwargs.setdefault("initial", {})
|
||||
# The widget for a ModelMultipleChoiceField expects
|
||||
# a list of primary key for the selected data.
|
||||
initial["physics_exams"] = [t.pk for t in kwargs["instance"].physics_exams.all()]
|
||||
initial["anatomy_exams"] = [t.pk for t in kwargs["instance"].anatomy_exams.all()]
|
||||
initial["sba_exams"] = [t.pk for t in kwargs["instance"].sba_exams.all()]
|
||||
initial["rapid_exams"] = [t.pk for t in kwargs["instance"].rapid_exams.all()]
|
||||
initial["longs_exams"] = [t.pk for t in kwargs["instance"].longs_exams.all()]
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
|
||||
super(CidUserForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["physics_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=PhysicsExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Physics Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["anatomy_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=AnatomyExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Anatomy Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["sba_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=SbasExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="SBA Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["rapid_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=RapidsExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False),
|
||||
)
|
||||
self.fields["longs_exams"] = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=LongsExam.objects.filter(archive=False),
|
||||
widget=FilteredSelectMultiple(verbose_name="Long Exams", is_stacked=False),
|
||||
)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Get the unsaved Long instance
|
||||
instance = ModelForm.save(self, False)
|
||||
|
||||
# Prepare a 'save_m2m' method for the form,
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def save_m2m():
|
||||
old_save_m2m()
|
||||
instance.physics_exams.clear()
|
||||
for physics_exams in self.cleaned_data["physics_exams"]:
|
||||
instance.physics_exams.add(physics_exams.pk)
|
||||
instance.anatomy_exams.clear()
|
||||
for anatomy_exams in self.cleaned_data["anatomy_exams"]:
|
||||
instance.anatomy_exams.add(anatomy_exams.pk)
|
||||
instance.sba_exams.clear()
|
||||
for sba_exams in self.cleaned_data["sba_exams"]:
|
||||
instance.sba_exams.add(sba_exams.pk)
|
||||
instance.rapid_exams.clear()
|
||||
for rapid_exams in self.cleaned_data["rapid_exams"]:
|
||||
instance.rapid_exams.add(rapid_exams.pk)
|
||||
instance.longs_exams.clear()
|
||||
for longs_exams in self.cleaned_data["longs_exams"]:
|
||||
instance.longs_exams.add(longs_exams.pk)
|
||||
|
||||
self.save_m2m = save_m2m
|
||||
|
||||
# Do we need to save all changes now?
|
||||
# if commit:
|
||||
instance.save()
|
||||
self.save_m2m()
|
||||
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = CidUser
|
||||
fields = [
|
||||
"passcode",
|
||||
"active",
|
||||
"internal_candidate",
|
||||
"name",
|
||||
"email",
|
||||
"supervisor_email",
|
||||
"login_email_sent",
|
||||
"results_email_sent",
|
||||
"group",
|
||||
]
|
||||
|
||||
class CidUserGroupForm(ModelForm):
|
||||
class Meta:
|
||||
model = CidUserGroup
|
||||
fields = ["name", "archive"]
|
||||
@@ -1,19 +1,19 @@
|
||||
# Generated by Django 3.2.10 on 2022-03-01 18:32
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0024_auto_20211231_1044'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ciduser',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.cidusergroup'),
|
||||
),
|
||||
]
|
||||
# Generated by Django 3.2.10 on 2022-03-01 18:32
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0024_auto_20211231_1044'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ciduser',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.cidusergroup'),
|
||||
),
|
||||
]
|
||||
|
||||
+469
-469
@@ -1,469 +1,469 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils import timezone
|
||||
from smtplib import SMTPException
|
||||
|
||||
from django.urls import reverse
|
||||
from django.views.generic.edit import CreateView
|
||||
from reversion.views import RevisionMixin
|
||||
|
||||
import tagulous
|
||||
import tagulous.models
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
|
||||
class Plane(models.Model):
|
||||
plane = models.CharField(max_length=200, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.plane
|
||||
|
||||
class Meta:
|
||||
ordering = ("plane",)
|
||||
|
||||
|
||||
class Contrast(models.Model):
|
||||
contrast = models.CharField(max_length=200, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.contrast
|
||||
|
||||
class Meta:
|
||||
ordering = ("contrast",)
|
||||
|
||||
|
||||
class Examination(models.Model):
|
||||
examination = models.CharField(max_length=200, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.examination
|
||||
|
||||
class Meta:
|
||||
ordering = ("examination",)
|
||||
|
||||
|
||||
class Site(models.Model):
|
||||
site = models.CharField(max_length=200)
|
||||
initials = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.site
|
||||
|
||||
|
||||
class Condition(tagulous.models.TagModel):
|
||||
pass
|
||||
|
||||
|
||||
class Sign(tagulous.models.TagModel):
|
||||
pass
|
||||
|
||||
|
||||
class ExamBase(models.Model):
|
||||
name = models.CharField(max_length=200, help_text="Name of the exam")
|
||||
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
||||
|
||||
active = models.BooleanField(
|
||||
help_text="If an exam should be available to take", default=False
|
||||
)
|
||||
|
||||
exam_mode = models.BooleanField(
|
||||
help_text="If an exam should be taken in exam mode (users results will be submited to the server for marking)",
|
||||
default=False,
|
||||
)
|
||||
|
||||
publish_results = models.BooleanField(
|
||||
help_text="If an exams results should be available", default=False
|
||||
)
|
||||
|
||||
recreate_json = models.BooleanField(
|
||||
help_text="If the json cache needs updating", default=False
|
||||
)
|
||||
|
||||
json_creation_time = models.DateTimeField(blank=True, default=None, null=True)
|
||||
exam_json_id = models.IntegerField(
|
||||
default=1, help_text="auto incrementing field when json recreated"
|
||||
)
|
||||
|
||||
archive = models.BooleanField(
|
||||
help_text="Archived exams will remain on the test system but will not be displayed by default",
|
||||
default=False,
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If the exam is freely accessible (to view and edit on the test system)",
|
||||
default=False,
|
||||
)
|
||||
|
||||
stats_mean = models.FloatField(default=0)
|
||||
stats_mode = models.CharField(default=0, max_length=25)
|
||||
stats_median = models.FloatField(default=0)
|
||||
|
||||
stats_candidates = models.FloatField(default=0)
|
||||
|
||||
stats_min = models.FloatField(default=0)
|
||||
stats_max = models.FloatField(default=0)
|
||||
|
||||
stats_max_possible = models.FloatField(default=0)
|
||||
|
||||
stats_graph = models.TextField(default=0)
|
||||
|
||||
user_scores = models.JSONField(default=dict)
|
||||
|
||||
notes = GenericRelation("generic.CidUserExam")
|
||||
|
||||
# time_limit = models.IntegerField(
|
||||
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
||||
# default=2100,
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_exam_stats(self):
|
||||
text = f"""{self.name}
|
||||
{"-"*len(self.name)}
|
||||
- Candidates: {int(self.stats_candidates)}
|
||||
- Mean: {self.stats_mean:.2f} Median: {self.stats_median} Mode: {self.stats_mode} [Min: {self.stats_min} / Max: {self.stats_max}]
|
||||
"""
|
||||
return text
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("{}:exam_overview".format(self.app_name), kwargs={"pk": self.pk})
|
||||
|
||||
def get_take_url(self):
|
||||
return f"{settings.REMOTE_URL}/rts"
|
||||
|
||||
def get_exam_name(self):
|
||||
return self.name
|
||||
|
||||
def get_json_url(self):
|
||||
return reverse("{}:exam_json".format(self.app_name), args=(self.pk,))
|
||||
|
||||
def get_question_index(self, question):
|
||||
return list(self.exam_questions.all()).index(question)
|
||||
|
||||
def get_authors(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
authors = ", ".join([i.username for i in self.author.all()])
|
||||
return authors
|
||||
|
||||
def get_author_objects(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
authors = [i for i in self.author.all()]
|
||||
return authors
|
||||
|
||||
def check_cid_user(self, cid, passcode, request=None):
|
||||
if request is not None and request.user.is_superuser:
|
||||
return True
|
||||
|
||||
if self.valid_users.exists():
|
||||
|
||||
user = self.valid_users.filter(cid=cid).first()
|
||||
|
||||
if not user or user.passcode != passcode:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_or_create_cid_user_exam(self, cid_user, start_time=None):
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
c = CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
).first()
|
||||
print(c)
|
||||
if c:
|
||||
return c
|
||||
|
||||
if start_time is None:
|
||||
start_time = timezone.now()
|
||||
new = CidUserExam(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
cid_user=cid_user,
|
||||
start_time=start_time,
|
||||
)
|
||||
new.save()
|
||||
return new
|
||||
|
||||
def get_cid_user_exams(self, cid_user=None):
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
if cid_user is None:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
)
|
||||
else:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
)
|
||||
|
||||
def get_cid_user_score(self, cid_user):
|
||||
c = str(cid_user)
|
||||
if c in self.user_scores:
|
||||
return self.user_scores[c]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class NoteType(models.Model):
|
||||
note_type = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.note_type
|
||||
|
||||
|
||||
class QuestionNote(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
question = GenericForeignKey("content_type", "object_id")
|
||||
# author = models.ForeignKey(User,
|
||||
|
||||
author = models.CharField(max_length=200, blank=True)
|
||||
|
||||
user_author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
|
||||
note = models.TextField(blank=True)
|
||||
created_on = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
complete = models.BooleanField(default=False)
|
||||
|
||||
note_type = models.ForeignKey(
|
||||
NoteType, on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
|
||||
# def get_absolute_url(self):
|
||||
# self.pk = self.rapid_id
|
||||
# return reverse('rapids:question_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "{}: {} [{}] {} / {}".format(
|
||||
self.content_type,
|
||||
self.get_author_str(),
|
||||
self.created_on,
|
||||
self.note_type,
|
||||
self.note,
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
# TODO: return to question if logged in
|
||||
return "/"
|
||||
|
||||
def get_object_url(self):
|
||||
return self.question.get_absolute_url()
|
||||
|
||||
def get_author_str(self):
|
||||
if self.user_author is not None:
|
||||
return self.user_author.username
|
||||
else:
|
||||
return self.author
|
||||
|
||||
def get_short_str(self):
|
||||
return f"{self.note_type} - {self.note}"
|
||||
|
||||
|
||||
class CidUser(models.Model):
|
||||
EXAM_TYPES = (
|
||||
("Physics", "physics_exams"),
|
||||
("Rapids", "rapid_exams"),
|
||||
("SBAs", "sba_exams"),
|
||||
("Anatomy", "anatomy_exams"),
|
||||
("Longs", "longs_exams"),
|
||||
)
|
||||
|
||||
cid = models.IntegerField(blank=True, null=True, unique=True)
|
||||
passcode = models.CharField(blank=True, max_length=50)
|
||||
active = models.BooleanField(default=True)
|
||||
internal_candidate = models.BooleanField(default=False)
|
||||
name = models.CharField(blank=True, max_length=255)
|
||||
email = models.EmailField(blank=True)
|
||||
supervisor_email = models.EmailField(blank=True)
|
||||
|
||||
login_email_sent = models.BooleanField(default=False)
|
||||
results_email_sent = models.BooleanField(default=False)
|
||||
|
||||
group = models.ForeignKey("CidUserGroup", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.cid)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("generic:manage_cids")
|
||||
|
||||
def check_passcode(self, passcode) -> bool:
|
||||
return self.passcode == passcode
|
||||
|
||||
def get_cid_exams(self):
|
||||
available_exams = []
|
||||
|
||||
for n, t in self.EXAM_TYPES:
|
||||
exam_rel = getattr(self, t)
|
||||
if exam_rel.exists():
|
||||
exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
|
||||
available_exams.append((n, exams))
|
||||
|
||||
return available_exams
|
||||
|
||||
def generate_exam_report(self):
|
||||
exam_list = self.get_cid_exams()
|
||||
|
||||
name = ""
|
||||
if self.name:
|
||||
name = f"{self.name} "
|
||||
|
||||
exam_text = [f"Candidate {self.cid} {name}[{self.email}]"]
|
||||
|
||||
for exam_type, exams in exam_list:
|
||||
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
||||
|
||||
for exam in exams:
|
||||
try:
|
||||
user_score = exam.user_scores[str(self.cid)]
|
||||
except KeyError:
|
||||
user_score = "Score not generated"
|
||||
t = exam.get_exam_stats()
|
||||
|
||||
try:
|
||||
percentage = (
|
||||
f" ({user_score / int(exam.stats_max_possible) * 100:.2f}%)"
|
||||
)
|
||||
except TypeError:
|
||||
percentage = ""
|
||||
|
||||
exam_text.append(
|
||||
f"""{t}
|
||||
# Score: {user_score} / {int(exam.stats_max_possible)}{percentage}
|
||||
"""
|
||||
)
|
||||
|
||||
msg = "\n\n".join(exam_text)
|
||||
|
||||
return msg
|
||||
|
||||
html_msg = f""""""
|
||||
|
||||
def email_results(self, resend=False, additional_emails=[]):
|
||||
if not self.internal_candidate:
|
||||
return False, "Not internal candidate."
|
||||
|
||||
if self.results_email_sent and not resend:
|
||||
return False, "Already sent."
|
||||
|
||||
# Get a list of taken exams
|
||||
msg = self.generate_exam_report()
|
||||
|
||||
emails = [self.email, self.supervisor_email]
|
||||
|
||||
if additional_emails:
|
||||
emails.extend(additional_emails)
|
||||
|
||||
try:
|
||||
send_mail(
|
||||
f"{self.group} results",
|
||||
msg,
|
||||
"test@xkjq.uk",
|
||||
emails,
|
||||
fail_silently=False,
|
||||
# html_message=html_msg,
|
||||
)
|
||||
except SMTPException as e:
|
||||
return False, e
|
||||
|
||||
self.results_email_sent = True
|
||||
self.save()
|
||||
return True, ""
|
||||
|
||||
def email_details(self, resend=False):
|
||||
if self.login_email_sent and not resend:
|
||||
return False, "Already sent."
|
||||
|
||||
login_url = reverse("cid_selector")
|
||||
|
||||
msg = f"""
|
||||
See below for your details for: {self.group}
|
||||
|
||||
---------
|
||||
CID: {self.cid}
|
||||
Passcode: {self.passcode}
|
||||
---------
|
||||
|
||||
You will need these to access to the test system.
|
||||
|
||||
Direct link to login:
|
||||
{settings.REMOTE_URL}{login_url}?cid={self.cid}&passcode={self.passcode}
|
||||
|
||||
The above details are unique to you. Please do not share as otherwise others will be able to access and submit your results.
|
||||
|
||||
|
||||
|
||||
Please note this inbox is not monitored. Direct all enquiries to plh-tr.radiology-academy@nhs.net
|
||||
"""
|
||||
|
||||
try:
|
||||
# send_mail(
|
||||
# "Candidate Details",
|
||||
# msg,
|
||||
# "test@xkjq.uk",
|
||||
# [self.email],
|
||||
# fail_silently=False,
|
||||
# )
|
||||
email = EmailMessage(
|
||||
"Candidate Details",
|
||||
msg,
|
||||
"test@xkjq.uk",
|
||||
[self.email],
|
||||
["ross.kruger@nhs.net"],
|
||||
)
|
||||
email.send(fail_silently=False)
|
||||
except SMTPException as e:
|
||||
return False, e
|
||||
|
||||
self.login_email_sent = True
|
||||
self.save()
|
||||
return True, ""
|
||||
|
||||
def get_email_results_url(self):
|
||||
return reverse("generic:candidate_email_results", kwargs={"cid": self.cid})
|
||||
|
||||
def get_email_results_resend_url(self):
|
||||
return reverse("generic:candidate_email_results_resend", kwargs={"cid": self.cid})
|
||||
|
||||
|
||||
class CidUserExam(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
exam = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
start_time = models.DateTimeField(blank=True, null=True)
|
||||
end_time = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
cid_user = models.ForeignKey(CidUser, on_delete=models.CASCADE)
|
||||
|
||||
manual_submission = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class CidUserGroup(models.Model):
|
||||
name = models.CharField(blank=True, max_length=50)
|
||||
archive = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.name)
|
||||
|
||||
def GetGroupUsers(self):
|
||||
cid_users = self.ciduser_set.all()
|
||||
|
||||
s = ", ".join([f"{user.id} - {user.name}" for user in cid_users])
|
||||
return s
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils import timezone
|
||||
from smtplib import SMTPException
|
||||
|
||||
from django.urls import reverse
|
||||
from django.views.generic.edit import CreateView
|
||||
from reversion.views import RevisionMixin
|
||||
|
||||
import tagulous
|
||||
import tagulous.models
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
|
||||
class Plane(models.Model):
|
||||
plane = models.CharField(max_length=200, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.plane
|
||||
|
||||
class Meta:
|
||||
ordering = ("plane",)
|
||||
|
||||
|
||||
class Contrast(models.Model):
|
||||
contrast = models.CharField(max_length=200, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.contrast
|
||||
|
||||
class Meta:
|
||||
ordering = ("contrast",)
|
||||
|
||||
|
||||
class Examination(models.Model):
|
||||
examination = models.CharField(max_length=200, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.examination
|
||||
|
||||
class Meta:
|
||||
ordering = ("examination",)
|
||||
|
||||
|
||||
class Site(models.Model):
|
||||
site = models.CharField(max_length=200)
|
||||
initials = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.site
|
||||
|
||||
|
||||
class Condition(tagulous.models.TagModel):
|
||||
pass
|
||||
|
||||
|
||||
class Sign(tagulous.models.TagModel):
|
||||
pass
|
||||
|
||||
|
||||
class ExamBase(models.Model):
|
||||
name = models.CharField(max_length=200, help_text="Name of the exam")
|
||||
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
||||
|
||||
active = models.BooleanField(
|
||||
help_text="If an exam should be available to take", default=False
|
||||
)
|
||||
|
||||
exam_mode = models.BooleanField(
|
||||
help_text="If an exam should be taken in exam mode (users results will be submited to the server for marking)",
|
||||
default=False,
|
||||
)
|
||||
|
||||
publish_results = models.BooleanField(
|
||||
help_text="If an exams results should be available", default=False
|
||||
)
|
||||
|
||||
recreate_json = models.BooleanField(
|
||||
help_text="If the json cache needs updating", default=False
|
||||
)
|
||||
|
||||
json_creation_time = models.DateTimeField(blank=True, default=None, null=True)
|
||||
exam_json_id = models.IntegerField(
|
||||
default=1, help_text="auto incrementing field when json recreated"
|
||||
)
|
||||
|
||||
archive = models.BooleanField(
|
||||
help_text="Archived exams will remain on the test system but will not be displayed by default",
|
||||
default=False,
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If the exam is freely accessible (to view and edit on the test system)",
|
||||
default=False,
|
||||
)
|
||||
|
||||
stats_mean = models.FloatField(default=0)
|
||||
stats_mode = models.CharField(default=0, max_length=25)
|
||||
stats_median = models.FloatField(default=0)
|
||||
|
||||
stats_candidates = models.FloatField(default=0)
|
||||
|
||||
stats_min = models.FloatField(default=0)
|
||||
stats_max = models.FloatField(default=0)
|
||||
|
||||
stats_max_possible = models.FloatField(default=0)
|
||||
|
||||
stats_graph = models.TextField(default=0)
|
||||
|
||||
user_scores = models.JSONField(default=dict)
|
||||
|
||||
notes = GenericRelation("generic.CidUserExam")
|
||||
|
||||
# time_limit = models.IntegerField(
|
||||
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
||||
# default=2100,
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_exam_stats(self):
|
||||
text = f"""{self.name}
|
||||
{"-"*len(self.name)}
|
||||
- Candidates: {int(self.stats_candidates)}
|
||||
- Mean: {self.stats_mean:.2f} Median: {self.stats_median} Mode: {self.stats_mode} [Min: {self.stats_min} / Max: {self.stats_max}]
|
||||
"""
|
||||
return text
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("{}:exam_overview".format(self.app_name), kwargs={"pk": self.pk})
|
||||
|
||||
def get_take_url(self):
|
||||
return f"{settings.REMOTE_URL}/rts"
|
||||
|
||||
def get_exam_name(self):
|
||||
return self.name
|
||||
|
||||
def get_json_url(self):
|
||||
return reverse("{}:exam_json".format(self.app_name), args=(self.pk,))
|
||||
|
||||
def get_question_index(self, question):
|
||||
return list(self.exam_questions.all()).index(question)
|
||||
|
||||
def get_authors(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
authors = ", ".join([i.username for i in self.author.all()])
|
||||
return authors
|
||||
|
||||
def get_author_objects(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
authors = [i for i in self.author.all()]
|
||||
return authors
|
||||
|
||||
def check_cid_user(self, cid, passcode, request=None):
|
||||
if request is not None and request.user.is_superuser:
|
||||
return True
|
||||
|
||||
if self.valid_users.exists():
|
||||
|
||||
user = self.valid_users.filter(cid=cid).first()
|
||||
|
||||
if not user or user.passcode != passcode:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_or_create_cid_user_exam(self, cid_user, start_time=None):
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
c = CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
).first()
|
||||
print(c)
|
||||
if c:
|
||||
return c
|
||||
|
||||
if start_time is None:
|
||||
start_time = timezone.now()
|
||||
new = CidUserExam(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
cid_user=cid_user,
|
||||
start_time=start_time,
|
||||
)
|
||||
new.save()
|
||||
return new
|
||||
|
||||
def get_cid_user_exams(self, cid_user=None):
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
if cid_user is None:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
)
|
||||
else:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
)
|
||||
|
||||
def get_cid_user_score(self, cid_user):
|
||||
c = str(cid_user)
|
||||
if c in self.user_scores:
|
||||
return self.user_scores[c]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class NoteType(models.Model):
|
||||
note_type = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.note_type
|
||||
|
||||
|
||||
class QuestionNote(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
question = GenericForeignKey("content_type", "object_id")
|
||||
# author = models.ForeignKey(User,
|
||||
|
||||
author = models.CharField(max_length=200, blank=True)
|
||||
|
||||
user_author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
|
||||
note = models.TextField(blank=True)
|
||||
created_on = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
complete = models.BooleanField(default=False)
|
||||
|
||||
note_type = models.ForeignKey(
|
||||
NoteType, on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
|
||||
# def get_absolute_url(self):
|
||||
# self.pk = self.rapid_id
|
||||
# return reverse('rapids:question_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "{}: {} [{}] {} / {}".format(
|
||||
self.content_type,
|
||||
self.get_author_str(),
|
||||
self.created_on,
|
||||
self.note_type,
|
||||
self.note,
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
# TODO: return to question if logged in
|
||||
return "/"
|
||||
|
||||
def get_object_url(self):
|
||||
return self.question.get_absolute_url()
|
||||
|
||||
def get_author_str(self):
|
||||
if self.user_author is not None:
|
||||
return self.user_author.username
|
||||
else:
|
||||
return self.author
|
||||
|
||||
def get_short_str(self):
|
||||
return f"{self.note_type} - {self.note}"
|
||||
|
||||
|
||||
class CidUser(models.Model):
|
||||
EXAM_TYPES = (
|
||||
("Physics", "physics_exams"),
|
||||
("Rapids", "rapid_exams"),
|
||||
("SBAs", "sba_exams"),
|
||||
("Anatomy", "anatomy_exams"),
|
||||
("Longs", "longs_exams"),
|
||||
)
|
||||
|
||||
cid = models.IntegerField(blank=True, null=True, unique=True)
|
||||
passcode = models.CharField(blank=True, max_length=50)
|
||||
active = models.BooleanField(default=True)
|
||||
internal_candidate = models.BooleanField(default=False)
|
||||
name = models.CharField(blank=True, max_length=255)
|
||||
email = models.EmailField(blank=True)
|
||||
supervisor_email = models.EmailField(blank=True)
|
||||
|
||||
login_email_sent = models.BooleanField(default=False)
|
||||
results_email_sent = models.BooleanField(default=False)
|
||||
|
||||
group = models.ForeignKey("CidUserGroup", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.cid)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("generic:manage_cids")
|
||||
|
||||
def check_passcode(self, passcode) -> bool:
|
||||
return self.passcode == passcode
|
||||
|
||||
def get_cid_exams(self):
|
||||
available_exams = []
|
||||
|
||||
for n, t in self.EXAM_TYPES:
|
||||
exam_rel = getattr(self, t)
|
||||
if exam_rel.exists():
|
||||
exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
|
||||
available_exams.append((n, exams))
|
||||
|
||||
return available_exams
|
||||
|
||||
def generate_exam_report(self):
|
||||
exam_list = self.get_cid_exams()
|
||||
|
||||
name = ""
|
||||
if self.name:
|
||||
name = f"{self.name} "
|
||||
|
||||
exam_text = [f"Candidate {self.cid} {name}[{self.email}]"]
|
||||
|
||||
for exam_type, exams in exam_list:
|
||||
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
||||
|
||||
for exam in exams:
|
||||
try:
|
||||
user_score = exam.user_scores[str(self.cid)]
|
||||
except KeyError:
|
||||
user_score = "Score not generated"
|
||||
t = exam.get_exam_stats()
|
||||
|
||||
try:
|
||||
percentage = (
|
||||
f" ({user_score / int(exam.stats_max_possible) * 100:.2f}%)"
|
||||
)
|
||||
except TypeError:
|
||||
percentage = ""
|
||||
|
||||
exam_text.append(
|
||||
f"""{t}
|
||||
# Score: {user_score} / {int(exam.stats_max_possible)}{percentage}
|
||||
"""
|
||||
)
|
||||
|
||||
msg = "\n\n".join(exam_text)
|
||||
|
||||
return msg
|
||||
|
||||
html_msg = f""""""
|
||||
|
||||
def email_results(self, resend=False, additional_emails=[]):
|
||||
if not self.internal_candidate:
|
||||
return False, "Not internal candidate."
|
||||
|
||||
if self.results_email_sent and not resend:
|
||||
return False, "Already sent."
|
||||
|
||||
# Get a list of taken exams
|
||||
msg = self.generate_exam_report()
|
||||
|
||||
emails = [self.email, self.supervisor_email]
|
||||
|
||||
if additional_emails:
|
||||
emails.extend(additional_emails)
|
||||
|
||||
try:
|
||||
send_mail(
|
||||
f"{self.group} results",
|
||||
msg,
|
||||
"test@xkjq.uk",
|
||||
emails,
|
||||
fail_silently=False,
|
||||
# html_message=html_msg,
|
||||
)
|
||||
except SMTPException as e:
|
||||
return False, e
|
||||
|
||||
self.results_email_sent = True
|
||||
self.save()
|
||||
return True, ""
|
||||
|
||||
def email_details(self, resend=False):
|
||||
if self.login_email_sent and not resend:
|
||||
return False, "Already sent."
|
||||
|
||||
login_url = reverse("cid_selector")
|
||||
|
||||
msg = f"""
|
||||
See below for your details for: {self.group}
|
||||
|
||||
---------
|
||||
CID: {self.cid}
|
||||
Passcode: {self.passcode}
|
||||
---------
|
||||
|
||||
You will need these to access to the test system.
|
||||
|
||||
Direct link to login:
|
||||
{settings.REMOTE_URL}{login_url}?cid={self.cid}&passcode={self.passcode}
|
||||
|
||||
The above details are unique to you. Please do not share as otherwise others will be able to access and submit your results.
|
||||
|
||||
|
||||
|
||||
Please note this inbox is not monitored. Direct all enquiries to plh-tr.radiology-academy@nhs.net
|
||||
"""
|
||||
|
||||
try:
|
||||
# send_mail(
|
||||
# "Candidate Details",
|
||||
# msg,
|
||||
# "test@xkjq.uk",
|
||||
# [self.email],
|
||||
# fail_silently=False,
|
||||
# )
|
||||
email = EmailMessage(
|
||||
"Candidate Details",
|
||||
msg,
|
||||
"test@xkjq.uk",
|
||||
[self.email],
|
||||
["ross.kruger@nhs.net"],
|
||||
)
|
||||
email.send(fail_silently=False)
|
||||
except SMTPException as e:
|
||||
return False, e
|
||||
|
||||
self.login_email_sent = True
|
||||
self.save()
|
||||
return True, ""
|
||||
|
||||
def get_email_results_url(self):
|
||||
return reverse("generic:candidate_email_results", kwargs={"cid": self.cid})
|
||||
|
||||
def get_email_results_resend_url(self):
|
||||
return reverse("generic:candidate_email_results_resend", kwargs={"cid": self.cid})
|
||||
|
||||
|
||||
class CidUserExam(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
exam = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
start_time = models.DateTimeField(blank=True, null=True)
|
||||
end_time = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
cid_user = models.ForeignKey(CidUser, on_delete=models.CASCADE)
|
||||
|
||||
manual_submission = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class CidUserGroup(models.Model):
|
||||
name = models.CharField(blank=True, max_length=50)
|
||||
archive = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.name)
|
||||
|
||||
def GetGroupUsers(self):
|
||||
cid_users = self.ciduser_set.all()
|
||||
|
||||
s = ", ".join([f"{user.id} - {user.name}" for user in cid_users])
|
||||
return s
|
||||
|
||||
+1650
-1650
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user