.
This commit is contained in:
@@ -805,14 +805,26 @@ input {
|
||||
.view-finding-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.view-finding-details {
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.delete-finding-link, .edit-finding-link {
|
||||
.delete-finding-link,
|
||||
.edit-finding-link {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
summary h2, summary h3, summary h4, summary h5 {
|
||||
summary h2,
|
||||
summary h3,
|
||||
summary h4,
|
||||
summary h5 {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mark-buttons {
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
background-color: black;
|
||||
opacity: 80%;
|
||||
}
|
||||
@@ -36,17 +36,20 @@
|
||||
<li><pre><span class="answer incorrect" title="{{answer}}">{{ answer }}</span></pre></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<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>
|
||||
{% if question_details.current > 1 %}
|
||||
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
|
||||
{% endif %}
|
||||
<button type="submit" name="save" class="save btn btn-default">Save</button>
|
||||
{% if question_details.current >= question_details.total %}
|
||||
{% else %}
|
||||
<button type="submit" name="next" class="save btn btn-default">Next</button>
|
||||
<button type="submit" name="skip" class="save btn btn-default">Skip</button>
|
||||
{% endif %}
|
||||
<div class="mark-buttons">
|
||||
<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>
|
||||
{% if question_details.current > 1 %}
|
||||
<button type="submit" name="previous" class="save btn btn-default">Previous</button>
|
||||
{% endif %}
|
||||
<button type="submit" name="save" class="save btn btn-default">Save</button>
|
||||
{% if question_details.current >= question_details.total %}
|
||||
{% else %}
|
||||
<button type="submit" name="next" class="save btn btn-default">Next</button>
|
||||
<button type="submit" name="skip" class="save btn btn-default">Skip</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<span class=hide>
|
||||
{{ form.as_p }}
|
||||
|
||||
@@ -143,4 +143,6 @@ class CidUserForm(ModelForm):
|
||||
"name",
|
||||
"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:
|
||||
exam_rel = getattr(self, t)
|
||||
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))
|
||||
|
||||
return available_exams
|
||||
@@ -324,28 +324,33 @@ class CidUser(models.Model):
|
||||
if 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:
|
||||
exam_text.append(f"{'='*len(exam_type)}\n{exam_type}\n{'='*len(exam_type)}")
|
||||
|
||||
for exam in exams:
|
||||
print(exam)
|
||||
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)}
|
||||
# Score: {user_score} / {int(exam.stats_max_possible)}{percentage}
|
||||
"""
|
||||
)
|
||||
|
||||
msg = "\n\n".join(exam_text)
|
||||
|
||||
print(msg)
|
||||
return msg
|
||||
|
||||
html_msg = f""""""
|
||||
@@ -430,6 +435,9 @@ class CidUser(models.Model):
|
||||
self.save()
|
||||
return True, ""
|
||||
|
||||
def get_email_results_url(self):
|
||||
return reverse("generic:candidate_email_results", kwargs={"cid": self.cid})
|
||||
|
||||
|
||||
class CidUserExam(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
|
||||
@@ -1,15 +1,124 @@
|
||||
{% extends 'generic/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
Attempted to send {{users_count}} emails.
|
||||
{{users_count}} emails to send.
|
||||
|
||||
<h2>Emails to send:</h2>
|
||||
<div class="sending"><div class="lds-ripple"><div></div><div></div></div> Emailing <span id="active-email"></span></div>
|
||||
<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 %}
|
||||
</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 sent:</h2>
|
||||
{% for u in sent %}
|
||||
<p>{{u.cid}}: {{u.email}}</p>
|
||||
{% endfor %}
|
||||
|
||||
<h2>Emails not sent:</h2>
|
||||
{% for u, msg in not_sent %}
|
||||
<p>{{u.cid}}: {{u.email}} [{{msg}}]</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
+18
-3
@@ -26,12 +26,27 @@ urlpatterns = [
|
||||
),
|
||||
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/exams", views.CidUserExamView.as_view(), name="manage_cid_exams"),
|
||||
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_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/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"),
|
||||
]
|
||||
|
||||
+21
-12
@@ -1415,31 +1415,40 @@ def group_email(request, pk, resend=False):
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_is_cid_user_manager
|
||||
def group_email_results(request, pk, resend=False):
|
||||
group = get_object_or_404(CidUserGroup, pk=pk)
|
||||
|
||||
if resend:
|
||||
users = group.ciduser_set.filter()
|
||||
users = group.ciduser_set.filter(internal_candidate=True)
|
||||
else:
|
||||
users = group.ciduser_set.filter(results_email_sent=False)
|
||||
users = group.ciduser_set.filter(internal_candidate=True, results_email_sent=False)
|
||||
|
||||
sent = []
|
||||
not_sent = []
|
||||
for u in users:
|
||||
success, msg = u.email_results(resend=resend)
|
||||
if success:
|
||||
sent.append(u)
|
||||
else:
|
||||
not_sent.append((u, msg))
|
||||
#sent = []
|
||||
#not_sent = []
|
||||
#for u in users:
|
||||
# success, msg = u.email_results(resend=resend)
|
||||
# if success:
|
||||
# sent.append(u)
|
||||
# else:
|
||||
# not_sent.append((u, msg))
|
||||
|
||||
return render(
|
||||
request,
|
||||
"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
|
||||
@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 django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
@@ -85,7 +86,8 @@ def cid_scores_admin(request, cid):
|
||||
|
||||
|
||||
@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):
|
||||
cid_user = CidUser.objects.filter(cid=cid).first()
|
||||
|
||||
@@ -122,7 +124,7 @@ def cid_scores(request, pk, passcode):
|
||||
"longs": (LongsCidUserAnswer, LongsExam),
|
||||
"sbas": (SbasCidUserAnswer, SbasExam),
|
||||
}
|
||||
kwargs = {"exam_mode": True}
|
||||
kwargs = {"exam_mode": True, "archive": False}
|
||||
|
||||
exams = []
|
||||
for exam_type in EXAM_ANSWER_MAP:
|
||||
|
||||
Reference in New Issue
Block a user