This commit is contained in:
Ross
2021-12-20 10:22:39 +00:00
parent e3ea5e9a6f
commit 5f32cce58d
20 changed files with 192 additions and 43 deletions
-21
View File
@@ -1,21 +0,0 @@
{% extends 'anatomy/exams.html' %}
{% block content %}
{% load thumbnail %}
<div class="anatomy">
<h1>Exam: {{ exam.name }}</h1>
<p>{{cid_user_count}} users.</p>
<ol>
{% for cid in cid_users %}
<li>{{cid.cid}} [{{cid.passcode}}]</li>
{% endfor %}
</ol>
</div>
{% endblock %}
+1 -1
View File
@@ -8,7 +8,7 @@
{% if exam.exam_mode %}
<a href="{% url 'anatomy:mark_overview' pk=exam.pk %}">Mark</a> /
<a href="{% url 'anatomy:exam_scores_cid' pk=exam.pk %}">Scores</a> /
<a href="{% url 'anatomy:exam_cids' exam_id=exam.pk %}">CIDs</a> /
<a href="{% url 'anatomy:exam_cids' exam_id=exam.pk %}">Candidates</a> /
{% endif %}
<a href="{% url 'anatomy:anatomy_create_exam' pk=exam.pk %}">Add New Question</a>
{% endblock %}
+1 -1
View File
@@ -226,7 +226,7 @@ def loadJsonAnswer(answer):
# in the frontend this is appended with the type
# e.g. anatomy/1
# TODO Update to only allow valid CIDs
# TODO Update to only allow valid Candidates
eid = int(answer["eid"].split("/")[1])
# As access is not restricted make sure the data appears valid
if (not isinstance(answer["cid"], int)) or (
+6 -1
View File
@@ -10,7 +10,7 @@ from django.forms import (
from django.forms import inlineformset_factory
from generic.models import (
Examination, QuestionNote
CidUser, Examination, QuestionNote
)
from django.contrib.admin.widgets import FilteredSelectMultiple
@@ -66,3 +66,8 @@ class QuestionNoteForm(ModelForm):
##initial["content_id"] = pk
#self.fields["content_type"].initial = content_type
#self.fields["object_id"].initial = pk
class CidUserForm(ModelForm):
class Meta:
model = CidUser
fields = ["cid", "passcode", "active", "internal_candidate", "name", "email", "supervisor_email"]
+3
View File
@@ -1,8 +1,11 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models
from django.http import HttpRequest
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
+4 -1
View File
@@ -23,7 +23,10 @@ class CidUserTable(tables.Table):
# edit = tables.LinkColumn(
# "anatomy:anatomy_question_update", text="Edit", args=[A("pk")], orderable=False
# )
cid = tables.LinkColumn("cid_scores", args=[A("cid"), A("passcode")], orderable=True)
cid = tables.LinkColumn(
"cid_scores", args=[A("cid"), A("passcode")], orderable=True
)
edit = tables.LinkColumn("generic:update_cid", text="Edit", args=[A("pk")], orderable=True)
# image = ImageColumn("image", orderable=False)
# clone = tables.LinkColumn('anatomy:anatomy_question_clone',
# text='Clone',
+2 -2
View File
@@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% block title %}
CIDs
Candidates
{% endblock %}
{% block css %}
@@ -15,7 +15,7 @@ CIDs
{% block content %}
{% endblock %}
{% block navigation %}
CIDs:
Candidates:
{% if request.user.is_authenticated %}
<a href="{% url 'generic:manage_cids' %}">Cids</a> /
<a href="{% url 'generic:group_view' %}">Groups</a> /
+26
View File
@@ -0,0 +1,26 @@
{% extends "atlas/base.html" %}
<!-- {% load static from static %} -->
{% block css %}
{% endblock %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
{{form.media}}
<script type="text/javascript">
</script>
<!-- {{ form.media }} -->
{% endblock %}
{% block content %}
<h2>Edit CID User</h2>
Use this form to create or edit a CID user.
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" class="submit-button" value="Submit" name="submit">
</form>
{% endblock %}
+1
View File
@@ -26,6 +26,7 @@ urlpatterns = [
),
name="examination-autocomplete",
),
path("cids/manage/<int:pk>/update", views.CidUserUpdate.as_view(), name="update_cid"),
path("cids/manage/", views.CidUserView.as_view(), name="manage_cids"),
path("cids/group/", views.group_view, name="group_view"),
path("cids/group/<int:pk>/email", views.group_email, name="group_email"),
+32 -4
View File
@@ -25,14 +25,17 @@ import json
import urllib
from django.views import View
from django.views.generic.edit import CreateView, UpdateView
from django_filters.views import FilterView
from django_tables2.views import SingleTableMixin
from reversion.views import RevisionMixin
from generic.decorators import user_is_cid_user_manager
from generic.filters import CidUserFilter
from generic.tables import CidUserTable
from .forms import (
CidUserForm,
ExaminationForm,
)
@@ -414,10 +417,20 @@ class ExamViews(View, LoginRequiredMixin):
if not self.check_user_access(request.user):
raise PermissionDenied
marking = False
if self.app_name in ("rapids", "anatomy", "longs"):
marking = True
return render(
request,
"exam_list.html".format(self.app_name),
{"exams": exams, "app_name": self.app_name, "view_all": all},
{
"exams": exams,
"app_name": self.app_name,
"view_all": all,
"marking": marking,
},
)
@method_decorator(login_required)
@@ -526,8 +539,13 @@ class ExamViews(View, LoginRequiredMixin):
return render(
request,
"{}/exam_cids.html".format(self.app_name),
{"exam": exam, "cid_users": cid_users, "cid_user_count": cid_user_count},
"exam_cids.html",
{
"exam": exam,
"cid_users": cid_users,
"cid_user_count": cid_user_count,
"app_name": self.app_name,
},
)
# @method_decorator(login_required)
@@ -1287,7 +1305,7 @@ def group_email(request, pk, resend=False):
if resend:
users = group.ciduser_set.filter()
else:
users = group.ciduser_set.filter(login_email_sent=False)
users = gGroup.ciduser_set.filter(login_email_sent=False)
sent = []
not_sent = []
@@ -1449,3 +1467,13 @@ def manage_cid_users(request):
return JsonResponse({"status": "success"})
return JsonResponse({"status": "fail"})
class CidUserCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = CidUser
form_class = CidUserForm
class CidUserUpdate(RevisionMixin, LoginRequiredMixin, UpdateView):
model = CidUser
form_class = CidUserForm
+8 -4
View File
@@ -1,7 +1,11 @@
{% extends 'longs/base.html' %}
{% block navigation %}
{{block.super}}
<br/>
Exams: {{exam.name}}-> <a href="{% url 'longs:exam_overview' pk=exam.pk %}">Overview</a> / <a href="{% url 'longs:mark_overview' pk=exam.pk %}">Mark</a> / <a href="{% url 'longs:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
{{block.super}}
<br/>
Exams: {{exam.name}}->
<a href="{% url 'longs:exam_overview' pk=exam.pk %}">Overview</a> /
<a href="{% url 'longs:mark_overview' pk=exam.pk %}">Mark</a> /
<a href="{% url 'longs:exam_scores_cid' pk=exam.pk %}">Scores</a>
<a href="{% url 'longs:exam_cids' exam_id=exam.pk %}">Candidates</a> /
{% endblock %}
+1
View File
@@ -125,6 +125,7 @@ urlpatterns = [
path("exam/", views.GenericExamViews.exam_list, name="exam_list"),
path("exam/all", views.GenericExamViews.exam_list_all, name="exam_list_all"),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
+1
View File
@@ -43,6 +43,7 @@ urlpatterns = [
name="exam_toggle_results_published",
),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
path(
+1
View File
@@ -8,6 +8,7 @@
{% if exam.exam_mode %}
<a href="{% url 'rapids:mark_overview' pk=exam.pk %}">Mark</a> /
<a href="{% url 'rapids:exam_scores_cid' pk=exam.pk %}">Scores</a> /
<a href="{% url 'rapids:exam_cids' exam_id=exam.pk %}">Candidates</a> /
{% endif %}
<a href="{% url 'rapids:rapid_create_exam' pk=exam.pk %}">Add New Question</a>
{% endblock %}
+1
View File
@@ -105,6 +105,7 @@ urlpatterns = [
path("exam/all", views.GenericExamViews.exam_list_all, name="exam_list_all"),
path("exam/create", views.ExamCreate.as_view(), name="exam_create"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
path("exam/<int:pk>/update", views.ExamUpdate.as_view(), name="exam_update"),
path("exam/<int:pk>/delete", views.ExamDelete.as_view(), name="exam_delete"),
path("exam/json/", views.GenericExamViews.active_exams, name="active_exams"),
+7 -4
View File
@@ -1,7 +1,10 @@
{% extends 'sbas/base.html' %}
{% block navigation %}
{{block.super}}
<br/>
Exams: {{exam.name}}-> <a href="{% url 'sbas:exam_overview' pk=exam.pk %}">Overview</a> / <a href="{% url 'sbas:exam_scores_cid' pk=exam.pk %}">Scores</a>
{% endblock %}
{{block.super}}
<br/>
Exams: {{exam.name}}->
<a href="{% url 'sbas:exam_overview' pk=exam.pk %}">Overview</a> /
<a href="{% url 'sbas:exam_scores_cid' pk=exam.pk %}">Scores</a>
<a href="{% url 'sbas:exam_cids' exam_id=exam.pk %}">Candidates</a> /
{% endblock %}
+2
View File
@@ -48,6 +48,8 @@ urlpatterns = [
),
# path("exam/submit", views.GenericExamViews.postExamAnswers, name="exam_answers_submit"),
path("exam/", views.GenericExamViews.exam_list, name="exam_list"),
path("exam/<int:exam_id>/clone", views.ExamClone.as_view(), name="exam_clone"),
path("exam/<int:exam_id>/cids", views.GenericExamViews.exam_cids, name="exam_cids"),
path("exam/available", views.active_exams, name="active_exams"),
# path("exam/json/<int:pk>", views.exam_json, name="exam_json"),
# path("exam/json/<int:pk>/recreate", views.exam_json_recreate, name="exam_json_recreate"),
+58 -2
View File
@@ -1,6 +1,7 @@
from reversion.views import RevisionMixin
from generic.mixins import SuperuserRequiredMixin
from django.views.generic.detail import DetailView
from sbas.forms import CidUserAnswerForm
from sbas.forms import CidUserAnswerForm, ExamForm
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt
from django import forms
@@ -37,7 +38,7 @@ import json
import statistics
import plotly.express as px
from generic.views import ExamViews, GenericViewBase
from generic.views import ExamCloneMixin, ExamViews, GenericViewBase
from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination
@@ -319,3 +320,58 @@ class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
GenericViews = GenericViewBase("sbas", Question, CidUserAnswer, Exam)
class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
model = Question
success_url = reverse_lazy("sbas:question_list")
class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
model = Exam
form_class = ExamForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
return super().form_valid(form)
class ExamUpdate(
RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
):
model = Exam
form_class = ExamForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
return super().form_valid(form)
#class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# # queryset = Exam.objects.all().order_by('name')
# serializer_class = ExamSerializer
# permission_classes = [permissions.IsAuthenticated]
#
# def get_queryset(self):
# """
# This view should return a list exams available to a user.
# """
# user = self.request.user
# if user.groups.filter(name="sbas_checker").exists():
# return Exam.objects.all()
#
# return Exam.objects.filter(author__id=user.id)
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
model = Exam
template_name = "exam_confirm_delete.html"
success_url = reverse_lazy("sbas:index")
class ExamClone(ExamCloneMixin, ExamCreate):
"""Clone exam view"""
+27
View File
@@ -0,0 +1,27 @@
{% extends app_name|add:'/exams.html' %}
{% block content %}
{% load thumbnail %}
<div class="{{app_name}}">
<h1>Exam: {{ exam.name }}</h1>
<p>{{cid_user_count}} candidates.</p>
<ol>
{% for cid in cid_users %}
<li><a href="{% url 'generic:update_cid' cid.pk %}">{{cid.cid}}</a> [{{cid.passcode}}] {{cid.name}} /
{{cid.email}} /
{{cid.supervisor_email}} /
{% if cid.login_email_sent %}Login email sent{% endif %} /
{% if cid.results_email_sent %}Results email sent{% endif %} /
</li>
{% endfor %}
</ol>
</div>
{% endblock %}
+10 -2
View File
@@ -8,7 +8,11 @@
{% for exam in exams %}
{% if exam.active %}
<li>
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url app_name|add:':exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a>
{% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col">Mark</a>{% endif %}
<a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}" class="flex-col">Candidates</a>
<a href="{% url app_name|add:':exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a>
<span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
</li>
{% endif %}
{% endfor %}
@@ -19,7 +23,11 @@
{% for exam in exams %}
{% if not exam.active %}
<li>
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a> <a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col">Mark</a><a href="{% url app_name|add:':exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a> <span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="flex-col">{{exam.name}}</a>
{% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col">Mark</a>{% endif %}
<a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}" class="flex-col">Candidates</a>
<a href="{% url app_name|add:':exam_scores_cid' pk=exam.pk %}" class="flex-col">Scores</a>
<span class="flex-col icon-container"><span class="published-icon {% if exam.publish_results %}published{% endif %}">Results Published</span></span>
</li>
{% endif %}
{% endfor %}