This commit is contained in:
Ross
2022-07-31 18:55:06 +01:00
parent 523d50cce4
commit be70054cf7
10 changed files with 142 additions and 72 deletions
+13 -4
View File
@@ -7,19 +7,28 @@ from generic.models import CidUser
class CidUserFilter(django_filters.FilterSet):
active = django_filters.BooleanFilter(field_name="active", label="CID active", initial=True)
active = django_filters.BooleanFilter(
field_name="active", label="CID active", initial=True
)
class Meta:
model = CidUser
fields = ("cid", "active", "internal_candidate", "group")
# fields = ("cid", "active", "internal_candidate", "group")
fields = {
"cid": ["contains"],
"active": ["exact"],
"internal_candidate": ["exact"],
"group": ["exact"],
"group__name": ["contains"],
"email": ["contains"],
}
@property
def qs(self):
parent = super().qs
#filter_active = getattr(self.request, 'active', True)
# filter_active = getattr(self.request, 'active', True)
if "active" not in self.request.GET:
return parent.filter(active=True)
return parent
+5
View File
@@ -595,6 +595,11 @@ class CidUser(models.Model):
def get_email_results_resend_url(self):
return reverse("generic:candidate_email_results_resend", kwargs={"cid": self.cid})
def get_next_cid():
"""Returns the next available CID"""
return CidUser.objects.order_by("-cid").first().cid + 1
class CidUserExam(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+55 -51
View File
@@ -35,6 +35,7 @@
<button id="deactivate-selected-cids">Deactivate selected</button>
<button id="delete-selected-cids">Delete selected</button>
<button id="toggle-internal">Toggle internal</button>
<button id="add-group">Add Group to users</button>
<p>Select exams below to add user(s) to (or edit existing user(s)).</p>
<div class="exam-selectors">
<span>
@@ -103,12 +104,12 @@
{% endfor %}
</select>
<button id="add-group">Add Group to users</button>
</div>
<button id="add-new-cids">Add New (Blank) Users</button>
<h3>Create New Users</h3>
<button id="add-new-cids" title="This will create N new users">Add New (Blank) Users</button>
<input type="number" id="add-number" value="number to add"><br />
<button id="add-new-emails">Add New Users From Email</button>
<textarea id="emails-input" name="emails" rows="4" cols="50">Enter emails here</textarea>
<button id="add-new-emails" title="This will create users from a list of email addresses">Add New Users From Email</button>
<textarea id="emails-input" name="emails" rows="4" cols="50" placeholder="Enter emails here (single email per line)"></textarea>
</details>
{% endblock %}
@@ -116,51 +117,51 @@
{% block js %}
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.11.3/r-2.2.9/datatables.min.css"/>
{% comment %} <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.11.3/r-2.2.9/datatables.min.css"/>
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.11.3/r-2.2.9/datatables.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.11.3/r-2.2.9/datatables.min.js"></script> {% endcomment %}
<script>
jQuery.fn.dataTable.render.ellipsis = function ( cutoff, wordbreak, escapeHtml ) {
var esc = function ( t ) {
return t
.replace( /&/g, '&amp;' )
.replace( /</g, '&lt;' )
.replace( />/g, '&gt;' )
.replace( /"/g, '&quot;' );
};
// jQuery.fn.dataTable.render.ellipsis = function ( cutoff, wordbreak, escapeHtml ) {
//var esc = function ( t ) {
//return t
//.replace( /&/g, '&amp;' )
//.replace( /</g, '&lt;' )
//.replace( />/g, '&gt;' )
//.replace( /"/g, '&quot;' );
//};
return function ( d, type, row ) {
// Order, search and type get the original data
if ( type !== 'display' ) {
return d;
}
//return function ( d, type, row ) {
//// Order, search and type get the original data
//if ( type !== 'display' ) {
//return d;
//}
if ( typeof d !== 'number' && typeof d !== 'string' ) {
return d;
}
//if ( typeof d !== 'number' && typeof d !== 'string' ) {
//return d;
//}
d = d.toString(); // cast numbers
//d = d.toString(); // cast numbers
if ( d.length <= cutoff ) {
return d;
}
//if ( d.length <= cutoff ) {
//return d;
//}
var shortened = d.substr(0, cutoff-1);
//var shortened = d.substr(0, cutoff-1);
// Find the last white space character in the string
if ( wordbreak ) {
shortened = shortened.replace(/\s([^\s]*)$/, '');
}
//// Find the last white space character in the string
//if ( wordbreak ) {
//shortened = shortened.replace(/\s([^\s]*)$/, '');
//}
// Protect against uncontrolled HTML input
if ( escapeHtml ) {
shortened = esc( shortened );
}
//// Protect against uncontrolled HTML input
//if ( escapeHtml ) {
//shortened = esc( shortened );
//}
return '<span class="ellipsis" title="'+esc(d)+'">'+shortened+'&#8230;</span>';
};
};
//return '<span class="ellipsis" title="'+esc(d)+'">'+shortened+'&#8230;</span>';
//};
//};
function postAjax(data) {
@@ -177,6 +178,8 @@
if (data.status == "success") {
toastr.info('Added/Updated');
location.reload();
} else {
toastr.error(data.details)
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
@@ -215,19 +218,19 @@
$(document).ready(function () {
{% if exams %}
$("table").DataTable({
columnDefs: [ {
targets: [6,7,8,9,10],
render: $.fn.dataTable.render.ellipsis( 17, true )
} ],
"paging": false,
});
{% else %}
$("table").DataTable({
"paging": false,
});
{% endif %}
// {% if exams %}
//$("table").DataTable({
//columnDefs: [ {
//targets: [6,7,8,9,10],
//render: $.fn.dataTable.render.ellipsis( 17, true )
//} ],
//"paging": false,
//});
//{% else %}
//$("table").DataTable({
//"paging": false,
//});
//{% endif %}
$("#toggle-internal").click((evt) => {
data = getGenericData();
@@ -237,6 +240,7 @@
$("#add-new-emails").click((evt) => {
data = getGenericData();
data["emails"] = JSON.stringify($("#emails-input").val());
data["add_new_emails"] = true;
postAjax(data);
});
$("#add-group").click((evt) => {
@@ -39,7 +39,7 @@
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
console.log("done", data);
if (data.status == "success") {
if (data.added) {
@@ -50,7 +50,14 @@
toastr.info(`User ${el.target.dataset.cid} removed from exam`)
}
} else {
toastr.error(data.status)
}
})
.fail(function (data) {
console.log("fail", data);
toastr.error(data.responseJSON.status)
})
.always(function () {
})
View File
+7
View File
@@ -0,0 +1,7 @@
from django import template
register = template.Library()
@register.filter(name='has_group')
def has_group(user, group_name):
return user.groups.filter(name=group_name).exists()
+1
View File
@@ -90,6 +90,7 @@ urlpatterns = [
name="user_group_create",
),
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
path("cids/create/email", views.create_cid_email, name="create_cid_email"),
]
+38 -6
View File
@@ -51,7 +51,7 @@ from .forms import (
UserUserGroupForm,
)
from .models import CidUser, CidUserGroup, Examination, QuestionNote, UserUserGroup
from .models import CidUser, CidUserGroup, Examination, QuestionNote, UserUserGroup, get_next_cid
from rapids.models import Rapid as RapidQuestion
from rapids.models import Exam as RapidExam
@@ -366,6 +366,9 @@ class ExamViews(View, LoginRequiredMixin):
if exam.authors_only:
return False
if user.groups.filter(name="cid_user_manager").exists():
return True
if (
self.app_name == "rapids"
and not user.groups.filter(name="rapid_checker").exists()
@@ -544,7 +547,7 @@ class ExamViews(View, LoginRequiredMixin):
exam_list = self.Exam.objects.all().order_by("name")
if not all:
exams = exam_list.filter(exam_mode=True, archive=False).order_by("name")
exam_list = exam_list.filter(exam_mode=True, archive=False).order_by("name")
marking = False
@@ -555,7 +558,7 @@ class ExamViews(View, LoginRequiredMixin):
request,
"exam_list.html".format(self.app_name),
{
"exams": exams,
"exams": exam_list,
"app_name": self.app_name,
"view_all": all,
"marking": marking,
@@ -947,7 +950,12 @@ class ExamViews(View, LoginRequiredMixin):
def exam_json_edit(self, request, pk):
if request.is_ajax() and request.method == "POST":
if not self.check_user_edit_access(request.user, exam_id=pk):
if request.user.groups.filter(name="cid_user_manager").exists():
# This may give more permissions than we actually want
pass
elif not self.check_user_edit_access(request.user, exam_id=pk):
data = {"status": "invalid permisions"}
return JsonResponse(data, status=400)
@@ -964,6 +972,8 @@ class ExamViews(View, LoginRequiredMixin):
app_exam_map["rapids"] = cid_user.rapid_exams
app_exam_map["anatomy"] = cid_user.anatomy_exams
app_exam_map["longs"] = cid_user.longs_exams
app_exam_map["physics"] = cid_user.physics_exams
app_exam_map["sbas"] = cid_user.sba_exams
app_exam_map["atlas"] = cid_user.casecollection_exams
if add:
@@ -985,6 +995,8 @@ class ExamViews(View, LoginRequiredMixin):
app_exam_map["rapids"] = user_user.user_rapid_exams
app_exam_map["anatomy"] = user_user.user_anatomy_exams
app_exam_map["longs"] = user_user.user_longs_exams
app_exam_map["physics"] = user_user.user_physics_exams
app_exam_map["sbas"] = user_user.user_sba_exams
# app_exam_map["atlas"] = user_user.casecollection_exams
if add:
@@ -2187,6 +2199,9 @@ def candidate_email_results(request, cid, resend=False):
def candidate_email_results_resend(request, cid, resend=True):
return candidate_email_results(request, cid, resend=True)
@user_is_cid_user_manager
def create_cid_email(request):
pass
@user_is_cid_user_manager
def manage_cid_users(request):
@@ -2203,6 +2218,7 @@ def manage_cid_users(request):
emails = json.loads(request.POST.get("emails"))
number_to_create = int(request.POST.get("number_to_create"))
add_group = request.POST.get("add_group")
@@ -2291,12 +2307,28 @@ def manage_cid_users(request):
return JsonResponse({"status": "success"})
try:
new_cid = CidUser.objects.all().order_by("-cid")[0].cid + 1
new_cid = get_next_cid()
except IndexError:
new_cid = 10000
if emails:
if "add_new_emails" in request.POST:
email_list = [i.strip() for i in emails.split()]
# Verify emails are all valid
invalid_emails = []
for email in email_list:
try:
validate_email(email)
except ValidationError as e:
invalid_emails.append(f"Invalid email: {email}")
print(f"Email list {email_list}")
if not email_list:
return JsonResponse({"status": "fail", "details": "No valid emails"})
if invalid_emails or not email_list:
e = "<br/>".join(invalid_emails)
return JsonResponse({"status": "fail", "details": f"Unable to create users<hr>{e}"})
for e in email_list:
passcode = "".join(random.choices(string.ascii_uppercase, k=4))
c = CidUser(cid=new_cid, passcode=passcode, email=e)
+13 -9
View File
@@ -1,4 +1,3 @@
from cProfile import label
import django_filters
@@ -7,17 +6,22 @@ from generic.models import UserGrades
from django.contrib.auth.models import User
class UserListFilter(django_filters.FilterSet):
userprofile__grade = django_filters.ModelChoiceFilter(label="Grade", queryset=UserGrades.objects.all())
#test2 = django_filters.CharFilter()
userprofile__grade = django_filters.ModelChoiceFilter(
label="Grade", queryset=UserGrades.objects.all()
)
# test2 = django_filters.CharFilter()
class Meta:
model= User
fields = {
"username": ["contains"], "email": ["contains"], "userprofile__grade": ["exact"]
}
#def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
model = User
fields = {
"username": ["contains"],
"email": ["contains"],
"userprofile__grade": ["exact"],
}
# def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
# super().__init__(data, queryset, request=request, prefix=prefix)
# self.filters["userprofile__grade"].label == "Grade"
+2 -1
View File
@@ -1,6 +1,7 @@
<!DOCTYPE HTML>
{% load static %}
{% load django_htmx %}
{% load auth_extras %}
<html>
<head>
@@ -91,7 +92,7 @@
<span id="atlas-link" class="top-bar-link">
<a href="{% url 'atlas:index' %}">Atlas</a>
</span>
{% if perms.generic.can_add_cid_user %}
{% if request.user|has_group:"cid_user_manager" %}
<span id="candidate-link" class="top-bar-link">
<a href="{% url 'generic:manage_cids' %}">Candidates</a>
</span>