.
This commit is contained in:
+13
-4
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, '&' )
|
||||
.replace( /</g, '<' )
|
||||
.replace( />/g, '>' )
|
||||
.replace( /"/g, '"' );
|
||||
};
|
||||
// jQuery.fn.dataTable.render.ellipsis = function ( cutoff, wordbreak, escapeHtml ) {
|
||||
//var esc = function ( t ) {
|
||||
//return t
|
||||
//.replace( /&/g, '&' )
|
||||
//.replace( /</g, '<' )
|
||||
//.replace( />/g, '>' )
|
||||
//.replace( /"/g, '"' );
|
||||
//};
|
||||
|
||||
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+'…</span>';
|
||||
};
|
||||
};
|
||||
//return '<span class="ellipsis" title="'+esc(d)+'">'+shortened+'…</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 () {
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user