.
This commit is contained in:
@@ -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 %}
|
||||
@@ -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
@@ -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
@@ -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"]
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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> /
|
||||
|
||||
Executable
+26
@@ -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 %}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
@@ -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"""
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user