Compare commits
4
Commits
cf9b514597
...
80412dcbe0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80412dcbe0 | ||
|
|
a024d7fca7 | ||
|
|
33b49c9430 | ||
|
|
4f60440ea1 |
@@ -1,4 +1,4 @@
|
|||||||
{% extends 'anatomy/base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="question-display-block">
|
<div class="question-display-block">
|
||||||
|
|||||||
+45
-29
@@ -68,6 +68,9 @@ from django.forms.utils import from_current_timezone, to_current_timezone
|
|||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit
|
from crispy_forms.layout import Submit
|
||||||
|
|
||||||
|
from dal import autocomplete
|
||||||
|
|
||||||
|
|
||||||
class SplitDateTimeFieldDefaultTime(SplitDateTimeField):
|
class SplitDateTimeFieldDefaultTime(SplitDateTimeField):
|
||||||
def compress(self, data_list):
|
def compress(self, data_list):
|
||||||
if data_list:
|
if data_list:
|
||||||
@@ -79,9 +82,9 @@ class SplitDateTimeFieldDefaultTime(SplitDateTimeField):
|
|||||||
)
|
)
|
||||||
if data_list[1] in self.empty_values:
|
if data_list[1] in self.empty_values:
|
||||||
data_list[1] = datetime.time(0, 1)
|
data_list[1] = datetime.time(0, 1)
|
||||||
#raise ValidationError(
|
# raise ValidationError(
|
||||||
# self.error_messages["invalid_time"], code="invalid_time"
|
# self.error_messages["invalid_time"], code="invalid_time"
|
||||||
#)
|
# )
|
||||||
result = datetime.datetime.combine(*data_list)
|
result = datetime.datetime.combine(*data_list)
|
||||||
return from_current_timezone(result)
|
return from_current_timezone(result)
|
||||||
return None
|
return None
|
||||||
@@ -98,9 +101,9 @@ class SplitDateTimeFieldDefaultTimeEnd(SplitDateTimeField):
|
|||||||
)
|
)
|
||||||
if data_list[1] in self.empty_values:
|
if data_list[1] in self.empty_values:
|
||||||
data_list[1] = datetime.time(23, 59)
|
data_list[1] = datetime.time(23, 59)
|
||||||
#raise ValidationError(
|
# raise ValidationError(
|
||||||
# self.error_messages["invalid_time"], code="invalid_time"
|
# self.error_messages["invalid_time"], code="invalid_time"
|
||||||
#)
|
# )
|
||||||
result = datetime.datetime.combine(*data_list)
|
result = datetime.datetime.combine(*data_list)
|
||||||
return from_current_timezone(result)
|
return from_current_timezone(result)
|
||||||
return None
|
return None
|
||||||
@@ -111,29 +114,28 @@ class ExamFormMixin:
|
|||||||
super(ModelForm, self).__init__(*args, **kwargs)
|
super(ModelForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
self.helper.form_id = 'id-exam-form'
|
self.helper.form_id = "id-exam-form"
|
||||||
self.helper.form_class = 'exam-form'
|
self.helper.form_class = "exam-form"
|
||||||
self.helper.form_method = 'post'
|
self.helper.form_method = "post"
|
||||||
self.helper.form_action = 'submit'
|
self.helper.form_action = "submit"
|
||||||
|
|
||||||
|
self.helper.add_input(Submit("submit", "Submit"))
|
||||||
|
|
||||||
self.helper.add_input(Submit('submit', 'Submit'))
|
# if user.is_superuser or user.groups.filter(name="cid_user_manager").exists():
|
||||||
|
|
||||||
#if user.is_superuser or user.groups.filter(name="cid_user_manager").exists():
|
|
||||||
# cid_user_group_queryset = CidUserGroup.objects.filter(archive=False)
|
# cid_user_group_queryset = CidUserGroup.objects.filter(archive=False)
|
||||||
# user_user_group_queryset = UserUserGroup.objects.filter(archive=False)
|
# user_user_group_queryset = UserUserGroup.objects.filter(archive=False)
|
||||||
#else:
|
# else:
|
||||||
# cid_user_group_queryset = CidUserGroup.objects.none()
|
# cid_user_group_queryset = CidUserGroup.objects.none()
|
||||||
# user_user_group_queryset = UserUserGroup.objects.none()
|
# user_user_group_queryset = UserUserGroup.objects.none()
|
||||||
|
|
||||||
#self.fields["cid_user_groups"] = ModelMultipleChoiceField(
|
# self.fields["cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
# required=False,
|
# required=False,
|
||||||
# queryset=cid_user_group_queryset,
|
# queryset=cid_user_group_queryset,
|
||||||
#)
|
# )
|
||||||
#self.fields["user_user_groups"] = ModelMultipleChoiceField(
|
# self.fields["user_user_groups"] = ModelMultipleChoiceField(
|
||||||
# required=False,
|
# required=False,
|
||||||
# queryset=user_user_group_queryset,
|
# queryset=user_user_group_queryset,
|
||||||
#)
|
# )
|
||||||
self.fields["start_date"] = SplitDateTimeFieldDefaultTime(
|
self.fields["start_date"] = SplitDateTimeFieldDefaultTime(
|
||||||
widget=SplitDateTimeWidget(
|
widget=SplitDateTimeWidget(
|
||||||
date_attrs={"type": "date", "class": "datepicker"},
|
date_attrs={"type": "date", "class": "datepicker"},
|
||||||
@@ -169,8 +171,8 @@ class ExamFormMixin:
|
|||||||
"active",
|
"active",
|
||||||
"publish_results",
|
"publish_results",
|
||||||
"archive",
|
"archive",
|
||||||
#"cid_user_groups",
|
# "cid_user_groups",
|
||||||
#"user_user_groups",
|
# "user_user_groups",
|
||||||
# "author",
|
# "author",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -199,6 +201,7 @@ class ExamAuthorFormMixin(ModelForm):
|
|||||||
widget=FilteredSelectMultiple(verbose_name="Authors", is_stacked=False),
|
widget=FilteredSelectMultiple(verbose_name="Authors", is_stacked=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExamMarkerFormMixin(ModelForm):
|
class ExamMarkerFormMixin(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ["markers"]
|
fields = ["markers"]
|
||||||
@@ -214,7 +217,7 @@ class ExamMarkerFormMixin(ModelForm):
|
|||||||
|
|
||||||
class ExamGroupsFormMixin(ModelForm):
|
class ExamGroupsFormMixin(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
#model = ExamCollection
|
# model = ExamCollection
|
||||||
fields = ("cid_user_groups", "user_user_groups")
|
fields = ("cid_user_groups", "user_user_groups")
|
||||||
|
|
||||||
def __init__(self, *args, user, **kwargs) -> None:
|
def __init__(self, *args, user, **kwargs) -> None:
|
||||||
@@ -227,10 +230,14 @@ class ExamGroupsFormMixin(ModelForm):
|
|||||||
cid_user_group_queryset = CidUserGroup.objects.filter(archive=False)
|
cid_user_group_queryset = CidUserGroup.objects.filter(archive=False)
|
||||||
user_user_group_queryset = UserUserGroup.objects.filter(archive=False)
|
user_user_group_queryset = UserUserGroup.objects.filter(archive=False)
|
||||||
else:
|
else:
|
||||||
cid_user_group_queryset = CidUserGroup.objects.filter(archive=False, open_access=True)
|
cid_user_group_queryset = CidUserGroup.objects.filter(
|
||||||
user_user_group_queryset = UserUserGroup.objects.none(archive=False, open_access=True)
|
archive=False, open_access=True
|
||||||
#cid_user_group_queryset = CidUserGroup.objects.none()
|
)
|
||||||
#user_user_group_queryset = UserUserGroup.objects.none()
|
user_user_group_queryset = UserUserGroup.objects.none(
|
||||||
|
archive=False, open_access=True
|
||||||
|
)
|
||||||
|
# cid_user_group_queryset = CidUserGroup.objects.none()
|
||||||
|
# user_user_group_queryset = UserUserGroup.objects.none()
|
||||||
|
|
||||||
self.fields["cid_user_groups"] = ModelMultipleChoiceField(
|
self.fields["cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
@@ -667,13 +674,23 @@ class UserUserForm(Form):
|
|||||||
first_name = CharField(max_length=255, required=True)
|
first_name = CharField(max_length=255, required=True)
|
||||||
last_name = CharField(max_length=255, required=True)
|
last_name = CharField(max_length=255, required=True)
|
||||||
grade = ModelChoiceField(UserGrades.objects.all(), required=False)
|
grade = ModelChoiceField(UserGrades.objects.all(), required=False)
|
||||||
|
|
||||||
|
class TraineeForm(Form):
|
||||||
|
username = EmailField(
|
||||||
|
required=True,
|
||||||
|
help_text="Username / email should be the same. Ideally this should be an nhs email.",
|
||||||
|
)
|
||||||
|
first_name = CharField(max_length=255, required=True)
|
||||||
|
last_name = CharField(max_length=255, required=True)
|
||||||
|
grade = ModelChoiceField(UserGrades.objects.all(), required=False)
|
||||||
supervisor = ModelChoiceField(
|
supervisor = ModelChoiceField(
|
||||||
Supervisor.objects.all(), required=False
|
Supervisor.objects.all(), required=False, widget=autocomplete.ModelSelect2(url='generic:supervisor-autocomplete')
|
||||||
) # Needs to be a user/object ref
|
) # Needs to be a user/object ref
|
||||||
#
|
|
||||||
# class Meta:
|
#class Meta:
|
||||||
#
|
# widgets = {
|
||||||
# fields = ("username", "first_name", "last_name")
|
# "supervisor" : autocomplete.ModelSelect2(url='generic:supervisor-autocomplete')
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
class SupervisorForm(ModelForm):
|
class SupervisorForm(ModelForm):
|
||||||
@@ -751,4 +768,3 @@ class ExamCollectionCloneForm(ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ExamCollection
|
model = ExamCollection
|
||||||
fields = ("name", "date")
|
fields = ("name", "date")
|
||||||
|
|
||||||
|
|||||||
@@ -39,44 +39,67 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h3>Exams</h3>
|
<h3>Exams</h3>
|
||||||
The group is currently associated with the following exams.
|
The group is currently associated with the following exams. This allows the users within the group to be easily added to the exam. It will not automatically give members of the group access to the exam.
|
||||||
{% if group_type == "cid" %}
|
{% if group_type == "cid" %}
|
||||||
Add more <a href='{% url "generic:cid_group_exams" group.pk %}'>here</a>.
|
Add more <a href='{% url "generic:cid_group_exams" group.pk %}'>here</a>.
|
||||||
{% else %}
|
{% else %}
|
||||||
Add more <a href='{% url "generic:user_group_exams" group.pk %}'>here</a>.
|
Add more <a href='{% url "generic:user_group_exams" group.pk %}'>here</a>.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<p>It is possible to quickly add or remove all group candidates from an exam by clicking the buttons below. This will only affect the <b>{{users|length}}</b> candidates in this group. You will need to refresh the page to see updated candidate counts.</p>
|
||||||
{% with group.GetGroupExams as exam_map %}
|
{% with group.GetGroupExams as exam_map %}
|
||||||
{% for key, value in exam_map.items %}
|
{% for key, value in exam_map.items %}
|
||||||
<h4>{{key}}</h4>
|
<h4>{{key}}</h4>
|
||||||
<ul>
|
<div>
|
||||||
{% for exam in value %}
|
{% for exam in value %}
|
||||||
<li>
|
<div class="d-flex">
|
||||||
<a href="{{exam.get_absolute_url}}">{{exam}}</a>
|
<a href="{{exam.get_absolute_url}}" class="px-2">{{exam}}</a>
|
||||||
{% if group_type == "cid" %}
|
{% if group_type == "cid" %}
|
||||||
<a href="{{exam.get_cid_edit_url}}" class="edit-link">Edit candidates</a>
|
<a href="{{exam.get_cid_edit_url}}" class="edit-link" px-2>Edit candidates</a>
|
||||||
|
{% comment %} <button>Add group candidates</button>
|
||||||
|
<button>Remove group candidates</button> {% endcomment %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{exam.get_user_edit_url}}" class="edit-link">Edit candidates</a>
|
<a href="{{exam.get_user_edit_url}}" class="edit-link px-2">Edit candidates</a>
|
||||||
|
<form>
|
||||||
|
<input type="hidden" name="exam_id" value="{{exam.pk}}">
|
||||||
|
<input type="hidden" name="exam_type" value="{{exam.app_name}}">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm px-2"
|
||||||
|
hx-post="{% url 'generic:user_group_add_candidates_to_exams' group.pk %}"
|
||||||
|
title="Add all group candidates to this exam"
|
||||||
|
name="action"
|
||||||
|
value="add"
|
||||||
|
>Add group candidates</button>
|
||||||
|
<button
|
||||||
|
hx-post="{% url 'generic:user_group_add_candidates_to_exams' group.pk %}"
|
||||||
|
title="Remove all group candidates to this exam"
|
||||||
|
class="btn btn-sm px-2"
|
||||||
|
name="action"
|
||||||
|
value="remove"
|
||||||
|
>Remove group candidates</button>
|
||||||
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="cid-number">
|
|
||||||
|
|
||||||
|
<div class="px-2 ms-auto ">
|
||||||
{% if group_type == "cid" %}
|
{% if group_type == "cid" %}
|
||||||
(Cid candidate count: {{exam.valid_cid_users.count}})
|
(Cid candidate count: {{exam.valid_cid_users.count}})
|
||||||
{% else %}
|
{% else %}
|
||||||
(User candidate count: {{exam.valid_user_users.count}})
|
(User candidate count: {{exam.valid_user_users.count}})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<style>
|
<style>
|
||||||
td, th { padding-left: 10px }
|
td, th { padding-left: 10px }
|
||||||
|
|
||||||
.cid-number {float: right}
|
|
||||||
|
|
||||||
.edit-link {
|
.edit-link {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
opacity: 60%;
|
opacity: 60%;
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends "generic/base.html" %}
|
||||||
|
<!-- {% load static from static %} -->
|
||||||
|
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% 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>Create Trainee</h2>
|
||||||
|
Use this form to create a user. Only existing supervisors can be added (<a href="{% url 'generic:supervisor_create' %}">create them first</a> and refresh this page or add them later if they do not exist).
|
||||||
|
{% if errors %}
|
||||||
|
<div class="alert alert-info" role="alert">{{errors}}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{{ form|crispy }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" class="submit-button" value="Submit" name="submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
{% for i in "123456"|make_list %}
|
{% for i in "123456"|make_list %}
|
||||||
<a href='{% url "trainees_grade" "ST"|add:i %}'>ST{{i}}</a>{% if not forloop.last %}, {% endif %}
|
<a href='{% url "trainees_grade" "ST"|add:i %}'>ST{{i}}</a>{% if not forloop.last %}, {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
(<a href="{% url 'create_trainee' %}" title="Click to add a trainee to the platform, creating an account for them.">Add trainee</a>)
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
{% if grade %}
|
{% if grade %}
|
||||||
@@ -38,7 +39,12 @@
|
|||||||
<a href="{% url 'account_profile_update' trainee.user.username %}" class="add-supervisor">add</a>
|
<a href="{% url 'account_profile_update' trainee.user.username %}" class="add-supervisor">add</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><a href="{% url 'account_update' trainee.user.username %}">User</a>/<a href="{% url 'account_profile_update' trainee.user.username %}">Profile</a></td>
|
<td><a href="{% url 'account_update' trainee.user.username %}">User</a>/<a href="{% url 'account_profile_update' trainee.user.username %}">Profile</a>
|
||||||
|
<span class="hover-highlight"><i class="bi bi-x-circle" title="Click to remove trainee status"
|
||||||
|
hx-get = "{% url 'generic:user_not_trainee' trainee.user.pk %}"
|
||||||
|
></i></span>
|
||||||
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{% extends "generic/base.html" %}
|
{% extends "generic/base.html" %}
|
||||||
<!-- {% load static from static %} -->
|
<!-- {% load static from static %} -->
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -13,8 +14,8 @@
|
|||||||
<!-- {{ form.media }} -->
|
<!-- {{ form.media }} -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Edit User / {{ciduser.cid}}</h2>
|
<h2>Create User</h2>
|
||||||
Use this form to create a user. Only existing supervisors can be added (<a href="{% url 'generic:supervisor_create' %}">create them first</a> and refresh this page or add them later if they do not exist).
|
Use this form to create a user. Create a trainee <a href="{% url 'create_trainee' %}">here</a>.
|
||||||
{% if errors %}
|
{% if errors %}
|
||||||
<div class="alert alert-info" role="alert">{{errors}}</a></div>
|
<div class="alert alert-info" role="alert">{{errors}}</a></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -22,7 +23,7 @@ Use this form to create a user. Only existing supervisors can be added (<a href=
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
{{ form.as_table }}
|
{{ form|crispy }}
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" class="submit-button" value="Submit" name="submit">
|
<input type="submit" class="submit-button" value="Submit" name="submit">
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+23
-1
@@ -1,6 +1,6 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
from generic.models import Examination
|
from generic.models import Examination, Supervisor
|
||||||
from . import views
|
from . import views
|
||||||
from generic.views import ExamViews as GenericExamViews, GenericViewBase
|
from generic.views import ExamViews as GenericExamViews, GenericViewBase
|
||||||
|
|
||||||
@@ -42,6 +42,13 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="examination-autocomplete",
|
name="examination-autocomplete",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"supervisor-autocomplete",
|
||||||
|
views.SupervisorAutocomplete.as_view(
|
||||||
|
model=Supervisor
|
||||||
|
),
|
||||||
|
name="supervisor-autocomplete",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"cids/manage/<int:pk>/update", views.CidUserUpdate.as_view(), name="update_cid"
|
"cids/manage/<int:pk>/update", views.CidUserUpdate.as_view(), name="update_cid"
|
||||||
),
|
),
|
||||||
@@ -113,6 +120,11 @@ urlpatterns = [
|
|||||||
views.users_bulk_edit,
|
views.users_bulk_edit,
|
||||||
name="users_bulk_edit",
|
name="users_bulk_edit",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"user/<int:user_id>/not_trainee",
|
||||||
|
views.user_not_trainee,
|
||||||
|
name="user_not_trainee",
|
||||||
|
),
|
||||||
path("user/group/", views.user_group_view, name="user_group_view"),
|
path("user/group/", views.user_group_view, name="user_group_view"),
|
||||||
path("user/group/all", views.user_group_view_all, name="user_group_view_all"),
|
path("user/group/all", views.user_group_view_all, name="user_group_view_all"),
|
||||||
path(
|
path(
|
||||||
@@ -120,6 +132,16 @@ urlpatterns = [
|
|||||||
views.user_group_view_detail,
|
views.user_group_view_detail,
|
||||||
name="user_group_detail",
|
name="user_group_detail",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"user/group/<int:group_id>/add",
|
||||||
|
views.user_group_add_candidates_to_exams,
|
||||||
|
name="user_group_add_candidates_to_exams",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"user/group/<int:group_id>/",
|
||||||
|
views.user_group_view_detail,
|
||||||
|
name="user_group_detail",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"user/group/<int:pk>/exams",
|
"user/group/<int:pk>/exams",
|
||||||
views.UserGroupExamUpdate.as_view(),
|
views.UserGroupExamUpdate.as_view(),
|
||||||
|
|||||||
+83
-7
@@ -53,7 +53,7 @@ from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
|
|||||||
|
|
||||||
import zipfile
|
import zipfile
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
CidGroupExamForm,
|
CidGroupExamForm,
|
||||||
@@ -65,6 +65,7 @@ from .forms import (
|
|||||||
CidUserGroupForm,
|
CidUserGroupForm,
|
||||||
ExaminationMergeForm,
|
ExaminationMergeForm,
|
||||||
SupervisorForm,
|
SupervisorForm,
|
||||||
|
TraineeForm,
|
||||||
UserGroupExamForm,
|
UserGroupExamForm,
|
||||||
CidGroupExamForm,
|
CidGroupExamForm,
|
||||||
UserUserForm,
|
UserUserForm,
|
||||||
@@ -133,6 +134,17 @@ class CidManagerRequiredMixin(UserPassesTestMixin):
|
|||||||
# return obj
|
# return obj
|
||||||
# raise PermissionDenied() # or Http404
|
# raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
|
def get_exam_model_from_app_name(app_name: str) -> ExamBase:
|
||||||
|
EXAM_MAP = {
|
||||||
|
"physics": PhysicsExam,
|
||||||
|
"anatomy": AnatomyExam,
|
||||||
|
"rapids": RapidsExam,
|
||||||
|
"longs": LongsExam,
|
||||||
|
"sbas": SbasExam,
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXAM_MAP[app_name]
|
||||||
|
|
||||||
|
|
||||||
def normaliseRapidsScore(score):
|
def normaliseRapidsScore(score):
|
||||||
if score == 49:
|
if score == 49:
|
||||||
@@ -2597,6 +2609,23 @@ class ExaminationAutocomplete(autocomplete.Select2QuerySetView):
|
|||||||
return Examination.objects.none()
|
return Examination.objects.none()
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
class SupervisorAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
|
def get_queryset(self):
|
||||||
|
# TODO: we should probably filter this to only
|
||||||
|
# allow access by trainees / other suprevisors
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return Supervisor.objects.none()
|
||||||
|
|
||||||
|
qs = Supervisor.objects.all()
|
||||||
|
|
||||||
|
if self.q:
|
||||||
|
# This raises a fielderror which breaks creating a new item if not caught
|
||||||
|
try:
|
||||||
|
qs = qs.filter(Q(email__icontains=self.q) | Q(name__icontains=self.q))
|
||||||
|
except FieldError:
|
||||||
|
return Supervisor.objects.none()
|
||||||
|
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class CidUserExamView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
|
class CidUserExamView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
|
||||||
@@ -2709,6 +2738,30 @@ def cid_group_view_all(request):
|
|||||||
{"groups": groups, "view_all": True},
|
{"groups": groups, "view_all": True},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@user_is_cid_user_manager
|
||||||
|
def user_group_add_candidates_to_exams(request, group_id):
|
||||||
|
if request.htmx:
|
||||||
|
group = get_object_or_404(UserUserGroup, pk=group_id)
|
||||||
|
|
||||||
|
exam_id = request.POST.get("exam_id")
|
||||||
|
exam_type = request.POST.get("exam_type")
|
||||||
|
|
||||||
|
ExamModel = get_exam_model_from_app_name(exam_type)
|
||||||
|
|
||||||
|
exam = get_object_or_404(ExamModel, pk=exam_id)
|
||||||
|
|
||||||
|
if request.POST.get("action") == "remove":
|
||||||
|
exam.valid_user_users.remove(*group.users.all())
|
||||||
|
return HttpResponse(f"Candidates removed")
|
||||||
|
else:
|
||||||
|
exam.valid_user_users.add(*group.users.all())
|
||||||
|
return HttpResponse(f"Candidates added")
|
||||||
|
#exam.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
raise PermissionDenied() # or Http404
|
||||||
|
pass
|
||||||
|
|
||||||
@user_is_cid_user_manager
|
@user_is_cid_user_manager
|
||||||
def user_group_view_detail(request, group_id):
|
def user_group_view_detail(request, group_id):
|
||||||
@@ -2756,6 +2809,14 @@ def get_user_selection_from_request(request):
|
|||||||
user_models = User.objects.filter(id__in=selected_users)
|
user_models = User.objects.filter(id__in=selected_users)
|
||||||
return user_models
|
return user_models
|
||||||
|
|
||||||
|
@user_is_cid_user_manager
|
||||||
|
def user_not_trainee(request, user_id):
|
||||||
|
user = get_object_or_404(User, pk=user_id)
|
||||||
|
|
||||||
|
user.userprofile.peninsula_trainee = False
|
||||||
|
user.userprofile.save()
|
||||||
|
|
||||||
|
return HttpResponse(f"{user.username} is no longer a trainee", content_type="text/plain")
|
||||||
|
|
||||||
@user_is_cid_user_manager
|
@user_is_cid_user_manager
|
||||||
def users_bulk_edit(request):
|
def users_bulk_edit(request):
|
||||||
@@ -3321,12 +3382,24 @@ def trainees(request, grade: None|str=None):
|
|||||||
|
|
||||||
return render(request, "generic/trainees.html", context)
|
return render(request, "generic/trainees.html", context)
|
||||||
|
|
||||||
def create_user(request, context=None):
|
def create_trainee(request, context=None):
|
||||||
|
return create_user(request, context, trainee=True)
|
||||||
|
|
||||||
|
@user_is_cid_user_manager
|
||||||
|
def create_user(request, context=None, trainee: bool=False):
|
||||||
|
if trainee:
|
||||||
|
form_template = "generic/trainee_creation_form.html"
|
||||||
|
else:
|
||||||
|
form_template = "generic/user_creation_form.html"
|
||||||
# if this is a POST request we need to process the form data
|
# if this is a POST request we need to process the form data
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
# create a form instance and populate it with data from the request:
|
# create a form instance and populate it with data from the request:
|
||||||
|
if trainee:
|
||||||
|
form = TraineeForm(request.POST)
|
||||||
|
else:
|
||||||
form = UserUserForm(request.POST)
|
form = UserUserForm(request.POST)
|
||||||
# check whether it's valid:
|
# check whether it's valid:
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
try:
|
try:
|
||||||
user_dict = {
|
user_dict = {
|
||||||
@@ -3342,19 +3415,19 @@ def create_user(request, context=None):
|
|||||||
errors = f"Unable to create account: {error}"
|
errors = f"Unable to create account: {error}"
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"generic/user_creation_form.html",
|
form_template,
|
||||||
{"form": form, "errors": errors},
|
{"form": form, "errors": errors},
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user_profile = UserProfile.objects.get(user=new_user)
|
user_profile = UserProfile.objects.get(user=new_user)
|
||||||
user_profile.peninsula_trainee = True
|
user_profile.peninsula_trainee = trainee
|
||||||
|
|
||||||
if request.POST["grade"]:
|
if request.POST["grade"]:
|
||||||
grade = UserGrades.objects.get(pk=request.POST["grade"])
|
grade = UserGrades.objects.get(pk=request.POST["grade"])
|
||||||
user_profile.grade = grade
|
user_profile.grade = grade
|
||||||
|
|
||||||
if request.POST["supervisor"]:
|
if "supervisor" in request.POST and request.POST["supervisor"]:
|
||||||
supervisor = Supervisor.objects.get(pk=request.POST["supervisor"])
|
supervisor = Supervisor.objects.get(pk=request.POST["supervisor"])
|
||||||
user_profile.supervisor = supervisor
|
user_profile.supervisor = supervisor
|
||||||
|
|
||||||
@@ -3364,7 +3437,7 @@ def create_user(request, context=None):
|
|||||||
errors = f"Unable to create account profile {error}"
|
errors = f"Unable to create account profile {error}"
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"generic/user_creation_form.html",
|
form_template,
|
||||||
{"form": form, "errors": errors},
|
{"form": form, "errors": errors},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3372,10 +3445,13 @@ def create_user(request, context=None):
|
|||||||
return reverse_lazy("accounts_list")
|
return reverse_lazy("accounts_list")
|
||||||
|
|
||||||
# if a GET (or any other method) we'll create a blank form
|
# if a GET (or any other method) we'll create a blank form
|
||||||
|
else:
|
||||||
|
if trainee:
|
||||||
|
form = TraineeForm()
|
||||||
else:
|
else:
|
||||||
form = UserUserForm()
|
form = UserUserForm()
|
||||||
|
|
||||||
return render(request, "generic/user_creation_form.html", {"form": form})
|
return render(request, form_template, {"form": form})
|
||||||
|
|
||||||
|
|
||||||
class SupervisorDetail(CidManagerRequiredMixin, DetailView):
|
class SupervisorDetail(CidManagerRequiredMixin, DetailView):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends 'longs/base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="question-display-block">
|
<div class="question-display-block">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends 'physics/base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="physics">
|
<div class="physics">
|
||||||
|
|||||||
@@ -1275,3 +1275,11 @@ tr:has(> td > a) {
|
|||||||
color: lightgray;
|
color: lightgray;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover-highlight {
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
.hover-highlight:hover {
|
||||||
|
opacity: 100%;
|
||||||
|
color:purple;
|
||||||
|
}
|
||||||
@@ -75,6 +75,9 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"accounts/create/", generic_views.create_user, name="create_user"
|
"accounts/create/", generic_views.create_user, name="create_user"
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"accounts/trainees/create/", generic_views.create_trainee, name="create_trainee"
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"accounts/trainees/", generic_views.trainees, name="trainees"
|
"accounts/trainees/", generic_views.trainees, name="trainees"
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends 'rapids/base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="question-display-block">
|
<div class="question-display-block">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends 'sbas/base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="sbas">
|
<div class="sbas">
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
|
<br/>
|
||||||
{% if cid %}
|
{% if cid %}
|
||||||
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
|
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'user_scores' %}">Other exams</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user