.
This commit is contained in:
@@ -805,14 +805,26 @@ input {
|
|||||||
.view-finding-button {
|
.view-finding-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-finding-details {
|
.view-finding-details {
|
||||||
flex: 4;
|
flex: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-finding-link, .edit-finding-link {
|
.delete-finding-link,
|
||||||
|
.edit-finding-link {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
summary h2, summary h3, summary h4, summary h5 {
|
summary h2,
|
||||||
|
summary h3,
|
||||||
|
summary h4,
|
||||||
|
summary h5 {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mark-buttons {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0px;
|
||||||
|
background-color: black;
|
||||||
|
opacity: 80%;
|
||||||
|
}
|
||||||
@@ -36,8 +36,10 @@
|
|||||||
<li><pre><span class="answer incorrect" title="{{answer}}">{{ answer }}</span></pre></li>
|
<li><pre><span class="answer incorrect" title="{{answer}}">{{ answer }}</span></pre></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="mark-buttons">
|
||||||
<div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1
|
<div class="answer-list key">Key: <span class="correct">2 Marks</span>, <span class="half-correct">1
|
||||||
Mark</span>, <span class="incorrect">0 Marks</span></div>
|
Mark</span>, <span class="incorrect">0 Marks</span>
|
||||||
|
</div>
|
||||||
{% if question_details.current > 1 %}
|
{% if question_details.current > 1 %}
|
||||||
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
|
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
<button type="submit" name="skip" class="save btn btn-default">Skip</button>
|
<button type="submit" name="skip" class="save btn btn-default">Skip</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<span class=hide>
|
<span class=hide>
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -143,4 +143,6 @@ class CidUserForm(ModelForm):
|
|||||||
"name",
|
"name",
|
||||||
"email",
|
"email",
|
||||||
"supervisor_email",
|
"supervisor_email",
|
||||||
|
"login_email_sent",
|
||||||
|
"results_email_sent",
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-5
@@ -312,7 +312,7 @@ class CidUser(models.Model):
|
|||||||
for n, t in self.EXAM_TYPES:
|
for n, t in self.EXAM_TYPES:
|
||||||
exam_rel = getattr(self, t)
|
exam_rel = getattr(self, t)
|
||||||
if exam_rel.exists():
|
if exam_rel.exists():
|
||||||
exams = exam_rel.filter(exam_mode=True).order_by("name")
|
exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
|
||||||
available_exams.append((n, exams))
|
available_exams.append((n, exams))
|
||||||
|
|
||||||
return available_exams
|
return available_exams
|
||||||
@@ -324,28 +324,33 @@ class CidUser(models.Model):
|
|||||||
if self.name:
|
if self.name:
|
||||||
name = f"{self.name} "
|
name = f"{self.name} "
|
||||||
|
|
||||||
exam_text = [f"Candidate {name}{self.cid} [{self.email}]"]
|
exam_text = [f"Candidate {self.cid} {name}[{self.email}]"]
|
||||||
|
|
||||||
for exam_type, exams in exam_list:
|
for exam_type, exams in exam_list:
|
||||||
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
||||||
|
|
||||||
for exam in exams:
|
for exam in exams:
|
||||||
print(exam)
|
|
||||||
try:
|
try:
|
||||||
user_score = exam.user_scores[str(self.cid)]
|
user_score = exam.user_scores[str(self.cid)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
user_score = "Score not generated"
|
user_score = "Score not generated"
|
||||||
t = exam.get_exam_stats()
|
t = exam.get_exam_stats()
|
||||||
|
|
||||||
|
try:
|
||||||
|
percentage = (
|
||||||
|
f" ({user_score / int(exam.stats_max_possible) * 100:.2f}%)"
|
||||||
|
)
|
||||||
|
except TypeError:
|
||||||
|
percentage = ""
|
||||||
|
|
||||||
exam_text.append(
|
exam_text.append(
|
||||||
f"""{t}
|
f"""{t}
|
||||||
# Score: {user_score} / {int(exam.stats_max_possible)}
|
# Score: {user_score} / {int(exam.stats_max_possible)}{percentage}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = "\n\n".join(exam_text)
|
msg = "\n\n".join(exam_text)
|
||||||
|
|
||||||
print(msg)
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
html_msg = f""""""
|
html_msg = f""""""
|
||||||
@@ -430,6 +435,9 @@ class CidUser(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
def get_email_results_url(self):
|
||||||
|
return reverse("generic:candidate_email_results", kwargs={"cid": self.cid})
|
||||||
|
|
||||||
|
|
||||||
class CidUserExam(models.Model):
|
class CidUserExam(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -1,15 +1,124 @@
|
|||||||
{% extends 'generic/base.html' %}
|
{% extends 'generic/base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
Attempted to send {{users_count}} emails.
|
{{users_count}} emails to send.
|
||||||
|
|
||||||
<h2>Emails sent:</h2>
|
<h2>Emails to send:</h2>
|
||||||
{% for u in sent %}
|
<div class="sending"><div class="lds-ripple"><div></div><div></div></div> Emailing <span id="active-email"></span></div>
|
||||||
<p>{{u.cid}}: {{u.email}}</p>
|
<ul id="user-list">
|
||||||
|
{% for user in users %}
|
||||||
|
<li data-email={{user.email}} data-posturl='{{user.get_email_results_url}}'>{{user.cid}}/{{user.name}}: {{user.email}} [{{user.supervisor_email}}] </li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button id="send-emails">Send emails</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$('document').ready(() => {
|
||||||
|
|
||||||
|
$("#send-emails").click(() => {
|
||||||
|
//$("#user-list li").each((n, el) => {
|
||||||
|
// console.log(n, el);
|
||||||
|
//})
|
||||||
|
li_list = $("#user-list li").get();
|
||||||
|
sendEmail(li_list);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
function sendEmail(li_list) {
|
||||||
|
//console.log(li_list)
|
||||||
|
$(".sending").show()
|
||||||
|
li = li_list.shift();
|
||||||
|
//console.log(li)
|
||||||
|
url = li.dataset.posturl;
|
||||||
|
|
||||||
|
$("#active-email").text(li.dataset.email)
|
||||||
|
|
||||||
|
console.log(url)
|
||||||
|
|
||||||
|
$.get(url, function(data, status) {
|
||||||
|
if (status == "success") {
|
||||||
|
if (data.sent) {
|
||||||
|
$(li).append("Email Sent");
|
||||||
|
$(li).addClass("email-sent");
|
||||||
|
} else {
|
||||||
|
$(li).append(`Error ${data.comment}`);
|
||||||
|
$(li).addClass("email-error");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$(li).append(`Error ${status}`);
|
||||||
|
$(li).addClass("email-error");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||||
|
$(li).append(`Error ${textStatus} / ${errorThrown}`);
|
||||||
|
$(li).addClass("email-error");
|
||||||
|
}).always(() => {
|
||||||
|
if (li_list.length > 0) {
|
||||||
|
sendEmail(li_list)
|
||||||
|
} else {
|
||||||
|
$(".sending").hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.email-sent {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
.email-error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.sending {
|
||||||
|
display: none;
|
||||||
|
color: darkblue;
|
||||||
|
font-size: larger;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.lds-ripple {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.lds-ripple div {
|
||||||
|
position: absolute;
|
||||||
|
border: 4px solid blue;
|
||||||
|
opacity: 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||||
|
}
|
||||||
|
.lds-ripple div:nth-child(2) {
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
}
|
||||||
|
@keyframes lds-ripple {
|
||||||
|
0% {
|
||||||
|
top: 36px;
|
||||||
|
left: 36px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<h2>Emails not sent:</h2>
|
|
||||||
{% for u, msg in not_sent %}
|
|
||||||
<p>{{u.cid}}: {{u.email}} [{{msg}}]</p>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+18
-3
@@ -26,12 +26,27 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="examination-autocomplete",
|
name="examination-autocomplete",
|
||||||
),
|
),
|
||||||
path("cids/manage/<int:pk>/update", views.CidUserUpdate.as_view(), name="update_cid"),
|
path(
|
||||||
|
"cids/manage/<int:pk>/update", views.CidUserUpdate.as_view(), name="update_cid"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"cids/manage/<int:cid>/email_results",
|
||||||
|
views.candidate_email_results,
|
||||||
|
name="candidate_email_results",
|
||||||
|
),
|
||||||
path("cids/manage/", views.CidUserView.as_view(), name="manage_cids"),
|
path("cids/manage/", views.CidUserView.as_view(), name="manage_cids"),
|
||||||
path("cids/manage/exams", views.CidUserExamView.as_view(), name="manage_cid_exams"),
|
path("cids/manage/exams", views.CidUserExamView.as_view(), name="manage_cid_exams"),
|
||||||
path("cids/group/", views.group_view, name="group_view"),
|
path("cids/group/", views.group_view, name="group_view"),
|
||||||
path("cids/group/<int:pk>/email", views.group_email, name="group_email"),
|
path("cids/group/<int:pk>/email", views.group_email, name="group_email"),
|
||||||
path("cids/group/<int:pk>/email_results", views.group_email_results, name="group_email_results"),
|
path(
|
||||||
path("cids/group/<int:pk>/email/resend", views.group_email_resend, name="group_email_resend"),
|
"cids/group/<int:pk>/email_results",
|
||||||
|
views.group_email_results,
|
||||||
|
name="group_email_results",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"cids/group/<int:pk>/email/resend",
|
||||||
|
views.group_email_resend,
|
||||||
|
name="group_email_resend",
|
||||||
|
),
|
||||||
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
|
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
|
||||||
]
|
]
|
||||||
|
|||||||
+21
-12
@@ -1415,31 +1415,40 @@ def group_email(request, pk, resend=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@user_is_cid_user_manager
|
@user_is_cid_user_manager
|
||||||
def group_email_results(request, pk, resend=False):
|
def group_email_results(request, pk, resend=False):
|
||||||
group = get_object_or_404(CidUserGroup, pk=pk)
|
group = get_object_or_404(CidUserGroup, pk=pk)
|
||||||
|
|
||||||
if resend:
|
if resend:
|
||||||
users = group.ciduser_set.filter()
|
users = group.ciduser_set.filter(internal_candidate=True)
|
||||||
else:
|
else:
|
||||||
users = group.ciduser_set.filter(results_email_sent=False)
|
users = group.ciduser_set.filter(internal_candidate=True, results_email_sent=False)
|
||||||
|
|
||||||
sent = []
|
#sent = []
|
||||||
not_sent = []
|
#not_sent = []
|
||||||
for u in users:
|
#for u in users:
|
||||||
success, msg = u.email_results(resend=resend)
|
# success, msg = u.email_results(resend=resend)
|
||||||
if success:
|
# if success:
|
||||||
sent.append(u)
|
# sent.append(u)
|
||||||
else:
|
# else:
|
||||||
not_sent.append((u, msg))
|
# not_sent.append((u, msg))
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"generic/group_emailed.html",
|
"generic/group_emailed.html",
|
||||||
{"sent": sent, "not_sent": not_sent, "users_count": users.count()},
|
#{"sent": sent, "not_sent": not_sent, "users_count": users.count()},
|
||||||
|
{"users": users, "users_count": users.count()},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@user_is_cid_user_manager
|
||||||
|
def candidate_email_results(request, cid, resend=False):
|
||||||
|
user = CidUser.objects.get(cid=cid)
|
||||||
|
|
||||||
|
email_sent, comment = user.email_results()
|
||||||
|
|
||||||
|
return JsonResponse({"sent": email_sent, "comment": comment})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_is_cid_user_manager
|
@user_is_cid_user_manager
|
||||||
|
|||||||
+4
-2
@@ -1,3 +1,4 @@
|
|||||||
|
from generic.decorators import user_is_cid_user_manager
|
||||||
from generic.views import get_question_and_content_type
|
from generic.views import get_question_and_content_type
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
@@ -85,7 +86,8 @@ def cid_scores_admin(request, cid):
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(lambda u: u.is_superuser)
|
#@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
@user_is_cid_user_manager
|
||||||
def cid_results(request, cid):
|
def cid_results(request, cid):
|
||||||
cid_user = CidUser.objects.filter(cid=cid).first()
|
cid_user = CidUser.objects.filter(cid=cid).first()
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ def cid_scores(request, pk, passcode):
|
|||||||
"longs": (LongsCidUserAnswer, LongsExam),
|
"longs": (LongsCidUserAnswer, LongsExam),
|
||||||
"sbas": (SbasCidUserAnswer, SbasExam),
|
"sbas": (SbasCidUserAnswer, SbasExam),
|
||||||
}
|
}
|
||||||
kwargs = {"exam_mode": True}
|
kwargs = {"exam_mode": True, "archive": False}
|
||||||
|
|
||||||
exams = []
|
exams = []
|
||||||
for exam_type in EXAM_ANSWER_MAP:
|
for exam_type in EXAM_ANSWER_MAP:
|
||||||
|
|||||||
Reference in New Issue
Block a user