numerous updates
This commit is contained in:
+13
-2
@@ -9,8 +9,9 @@ from django.forms import (
|
||||
)
|
||||
from django.forms import inlineformset_factory
|
||||
from atlas.models import CaseCollection
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote
|
||||
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote, UserUserGroup
|
||||
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||
@@ -205,4 +206,14 @@ class CidUserForm(ModelForm):
|
||||
class CidUserGroupForm(ModelForm):
|
||||
class Meta:
|
||||
model = CidUserGroup
|
||||
fields = ["name", "archive"]
|
||||
fields = ["name", "archive"]
|
||||
|
||||
class UserUserGroupForm(ModelForm):
|
||||
users = ModelMultipleChoiceField(
|
||||
required=False,
|
||||
queryset=User.objects.all(),
|
||||
widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
|
||||
)
|
||||
class Meta:
|
||||
model = UserUserGroup
|
||||
fields = ["name", "archive", "users"]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 16:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0026_ciduser_generic_cid_cid_291418_idx'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ciduserexam',
|
||||
name='user_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_userexam', to='generic.ciduser'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ciduserexam',
|
||||
name='cid_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cid_userexam', to='generic.ciduser'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 16:12
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('generic', '0027_auto_20220520_1705'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ciduserexam',
|
||||
name='user_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_userexam', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 17:44
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('generic', '0028_alter_ciduserexam_user_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserUserGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(blank=True, max_length=50)),
|
||||
('archive', models.BooleanField(default=False)),
|
||||
('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 18:27
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('generic', '0029_userusergroup'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userusergroup',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, related_name='user_groups', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-20 19:15
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('generic', '0030_alter_userusergroup_users'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserProfile',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('supervisor_email', models.EmailField(blank=True, max_length=254)),
|
||||
('supervisor_name', models.CharField(blank=True, max_length=100)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
+104
-30
@@ -20,6 +20,10 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.mail import send_mail
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
class Plane(models.Model):
|
||||
plane = models.CharField(max_length=200, unique=True)
|
||||
@@ -138,13 +142,9 @@ class ExamBase(models.Model):
|
||||
|
||||
user_scores = models.JSONField(default=dict)
|
||||
|
||||
# Is this actually used?
|
||||
cid_users = GenericRelation("generic.CidUserExam")
|
||||
|
||||
# time_limit = models.IntegerField(
|
||||
# help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
||||
# default=2100,
|
||||
# )
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@@ -189,49 +189,85 @@ class ExamBase(models.Model):
|
||||
authors = [i for i in self.author.all()]
|
||||
return authors
|
||||
|
||||
def check_cid_user(self, cid, passcode, request=None):
|
||||
def check_cid_user(self, cid, passcode, request=None, user_id=None):
|
||||
if request is not None and request.user.is_superuser:
|
||||
return True
|
||||
|
||||
if self.valid_users.exists():
|
||||
if not self.valid_cid_users.exists() and not self.valid_user_users.exists():
|
||||
return True
|
||||
|
||||
user = self.valid_users.filter(cid=cid).first()
|
||||
if user_id is not None:
|
||||
if self.valid_user_users.filter(pk=user_id).exists():
|
||||
return True
|
||||
|
||||
if not user or user.passcode != passcode:
|
||||
return False
|
||||
if self.valid_cid_users.exists():
|
||||
|
||||
return True
|
||||
cid_user = self.valid_cid_users.filter(cid=cid).first()
|
||||
|
||||
def get_or_create_cid_user_exam(self, cid_user, start_time=None):
|
||||
if cid_user and cid_user.passcode == passcode:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def get_or_create_cid_user_exam(self, cid_user=None, user_user=None, start_time=None):
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
c = CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
).first()
|
||||
if c:
|
||||
return c
|
||||
|
||||
if cid_user is not None:
|
||||
c = CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
).first()
|
||||
if c:
|
||||
return c
|
||||
|
||||
if user_user is not None:
|
||||
c = CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, user_user=cid_user
|
||||
).first()
|
||||
if c:
|
||||
return c
|
||||
|
||||
|
||||
if start_time is None:
|
||||
start_time = timezone.now()
|
||||
new = CidUserExam(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
cid_user=cid_user,
|
||||
start_time=start_time,
|
||||
)
|
||||
|
||||
|
||||
if cid_user is not None:
|
||||
new = CidUserExam(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
cid_user=cid_user,
|
||||
start_time=start_time,
|
||||
)
|
||||
|
||||
if user_user is not None:
|
||||
new = CidUserExam(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
user_user=user_user,
|
||||
start_time=start_time,
|
||||
)
|
||||
|
||||
new.save()
|
||||
return new
|
||||
|
||||
def get_cid_user_exams(self, cid_user=None):
|
||||
def get_cid_user_exams(self, cid_user=None, user_user=None):
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
if cid_user is None:
|
||||
if cid_user is None and user_user is None:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=self.pk,
|
||||
)
|
||||
else:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
)
|
||||
if cid_user is not None:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
||||
)
|
||||
else:
|
||||
return CidUserExam.objects.filter(
|
||||
content_type=content_type, object_id=self.pk, user_user=user_user
|
||||
)
|
||||
|
||||
def get_cid_user_score(self, cid_user):
|
||||
c = str(cid_user)
|
||||
@@ -482,7 +518,8 @@ class CidUserExam(models.Model):
|
||||
start_time = models.DateTimeField(blank=True, null=True)
|
||||
end_time = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
cid_user = models.ForeignKey(CidUser, on_delete=models.CASCADE)
|
||||
cid_user = models.ForeignKey(CidUser, on_delete=models.CASCADE, blank=True, null=True, related_name="cid_userexam")
|
||||
user_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, related_name="user_userexam")
|
||||
|
||||
manual_submission = models.BooleanField(default=False)
|
||||
|
||||
@@ -501,5 +538,42 @@ class CidUserGroup(models.Model):
|
||||
return s
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("generic:group_update", kwargs={"pk": self.pk})
|
||||
return reverse("generic:cid_group_update", kwargs={"pk": self.pk})
|
||||
|
||||
|
||||
class UserUserGroup(models.Model):
|
||||
name = models.CharField(blank=True, max_length=50)
|
||||
archive = models.BooleanField(default=False)
|
||||
|
||||
users = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
related_name="user_groups"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.name)
|
||||
|
||||
def GetGroupUsers(self):
|
||||
users = self.users.all()
|
||||
|
||||
s = ", ".join([f"{user.id} - {user.username} ({user.email})" for user in users])
|
||||
return s
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("generic:user_group_update", kwargs={"pk": self.pk})
|
||||
|
||||
|
||||
class UserProfile(models.Model):
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
supervisor_email = models.EmailField(blank=True)
|
||||
supervisor_name = models.CharField(max_length=100, blank=True)
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
UserProfile.objects.create(user=instance)
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def save_user_profile(sender, instance, **kwargs):
|
||||
instance.profile.save()
|
||||
@@ -19,7 +19,8 @@
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="{% url 'generic:manage_cids' %}">Cids</a> /
|
||||
<a href="{% url 'generic:manage_cid_exams' %}">Cids (exams)</a> /
|
||||
<a href="{% url 'generic:group_view' %}">Groups</a> /
|
||||
<a href="{% url 'generic:cid_group_view' %}">Cid Groups</a> /
|
||||
<a href="{% url 'generic:user_group_view' %}">User Groups</a> /
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
{% extends 'generic/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Groups</h2>
|
||||
<h2>Cid Groups</h2>
|
||||
<p>
|
||||
<a href="{% url 'generic:group_create' %}">Create new group</a>
|
||||
<a href="{% url 'generic:cid_group_create' %}">Create new group</a>
|
||||
</p>
|
||||
{% for group in groups %}
|
||||
<p>{{group.name}}
|
||||
@@ -15,7 +15,7 @@
|
||||
">Email candidate results</button>
|
||||
<button class="url-button" data-url="{% url 'generic:group_email_results_resend' pk=group.pk %}
|
||||
">Resend candidate results</button>
|
||||
<button class="url-button" data-url="{% url 'generic:group_update' pk=group.pk %}
|
||||
<button class="url-button" data-url="{% url 'generic:cid_group_update' pk=group.pk %}
|
||||
">Edit</button>
|
||||
</p>
|
||||
{% endfor %}
|
||||
@@ -29,8 +29,8 @@
|
||||
</script>
|
||||
|
||||
{% if view_all %}
|
||||
<a href="{% url 'generic:group_view' %}">View active</a>
|
||||
<a href="{% url 'generic:cid_group_view' %}">View active</a>
|
||||
{% else %}
|
||||
<a href="{% url 'generic:group_view_all' %}">View all</a>
|
||||
<a href="{% url 'generic:cid_group_view_all' %}">View all</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
<div class="card text-white bg-dark">
|
||||
<h3>CID candidates</h3>
|
||||
{% if cid_users %}
|
||||
<p>{{cid_user_count}} candidates.</p>
|
||||
<p>{{cid_user_count}} CID candidates.</p>
|
||||
|
||||
<ol>
|
||||
{% for cid in cid_users %}
|
||||
@@ -16,8 +18,32 @@
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
<p>This exam has no candidates. Add some with the below link.</p>
|
||||
<p>This exam has no CID candidates. Add some with the below link.</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<a href="{% url exam.app_name|add:':exam_cids_edit' exam.pk %}">Edit Exam CIDs</a>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div class="card text-white bg-dark">
|
||||
<h3>User candidates</h3>
|
||||
{% if user_users %}
|
||||
<p>{{user_user_count}} User candidates.</p>
|
||||
|
||||
<ol>
|
||||
{% for user in user_users %}
|
||||
|
||||
<li>{{user.username}}
|
||||
Email: {{user.email}}
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
<p>This exam has no User candidates. Add some with the below link.</p>
|
||||
{% endif %}
|
||||
<a href="{% url exam.app_name|add:':exam_users_edit' exam.pk %}">Edit Exam Users</a>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
<h3>Users</h3>
|
||||
{% if groups %}
|
||||
Users from the following groups are available: {% for group in groups %}<a href='{{group.get_absolute_url}}'>{{group}}</a>{% endfor %} .
|
||||
<ol>
|
||||
{% for user in current_user_users %}
|
||||
|
||||
<li class="current">{{user.username}} /
|
||||
Email: {{user.email}} <button class="toggle-btn" data-pk="{{user.pk}}" data-user="{{user.username}}">Toggle</button>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
{% for user in available_user_users %}
|
||||
|
||||
<li>{{user.username}} /
|
||||
Email: {{user.email}} <button class="toggle-btn" data-pk="{{user.pk}}" data-user="{{user.username}}">Toggle</button>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
This exam is not associated with any user groups. Edit and add a group to enable user management.
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$(".toggle-btn").click((el) => {
|
||||
parent = $(el.target).parent()
|
||||
$.ajax({
|
||||
url: "{% url exam.app_name|add:':exam_json_edit' exam.pk %}",
|
||||
data: {
|
||||
csrfmiddlewaretoken: "{{csrf_token}}",
|
||||
edit_user_user: el.target.dataset.pk,
|
||||
add: !parent.hasClass("current"),
|
||||
},
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
})
|
||||
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
|
||||
.done(function (data) {
|
||||
console.log(data);
|
||||
|
||||
if (data.status == "success") {
|
||||
if (data.added) {
|
||||
parent.addClass("current");
|
||||
toastr.info(`User ${el.target.dataset.user} added to exam`)
|
||||
} else {
|
||||
parent.removeClass("current");
|
||||
toastr.info(`User ${el.target.dataset.user} removed from exam`)
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
.always(function () {
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.current {
|
||||
color: lightgray;
|
||||
}
|
||||
.current::after {
|
||||
content: "[ADDED TO EXAM]"
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{% extends 'generic/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>User Groups</h2>
|
||||
<p>
|
||||
<a href="{% url 'generic:user_group_create' %}">Create new group</a>
|
||||
</p>
|
||||
{% for group in groups %}
|
||||
<p>{{group.name}}
|
||||
{% comment %} <button class="url-button" data-url="{% url 'generic:group_email' pk=group.pk %}
|
||||
">Email candidate details</button>
|
||||
<button class="url-button" data-url="{% url 'generic:group_email_resend' pk=group.pk %}
|
||||
">Resend candidate details</button>
|
||||
<button class="url-button" data-url="{% url 'generic:group_email_results' pk=group.pk %}
|
||||
">Email candidate results</button>
|
||||
<button class="url-button" data-url="{% url 'generic:group_email_results_resend' pk=group.pk %}
|
||||
">Resend candidate results</button> {% endcomment %}
|
||||
<button class="url-button" data-url="{% url 'generic:user_group_update' pk=group.pk %}
|
||||
">Edit</button>
|
||||
</p>
|
||||
{% endfor %}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(".url-button").click((evt) => {
|
||||
window.location = evt.currentTarget.dataset.url;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
{% if view_all %}
|
||||
<a href="{% url 'generic:user_group_view' %}">View active</a>
|
||||
{% else %}
|
||||
<a href="{% url 'generic:user_group_view_all' %}">View all</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{% extends "generic/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>Add / Edit Group / {{userusergroup.name}}</h2>
|
||||
Use this form to create / edit a CID user group.
|
||||
<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">
|
||||
|
||||
<h3>Users</h3>
|
||||
|
||||
{{userusergroup.GetGroupUsers}}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
+12
-4
@@ -41,8 +41,8 @@ urlpatterns = [
|
||||
),
|
||||
path("cids/manage/", views.CidUserView.as_view(), name="manage_cids"),
|
||||
path("cids/manage/exams", views.CidUserExamView.as_view(), name="manage_cid_exams"),
|
||||
path("cids/group/", views.group_view, name="group_view"),
|
||||
path("cids/group/all", views.group_view_all, name="group_view_all"),
|
||||
path("cids/group/", views.cid_group_view, name="cid_group_view"),
|
||||
path("cids/group/all", views.cid_group_view_all, name="cid_group_view_all"),
|
||||
path("cids/group/<int:pk>/email", views.group_email, name="group_email"),
|
||||
path(
|
||||
"cids/group/<int:pk>/email_results",
|
||||
@@ -60,10 +60,18 @@ urlpatterns = [
|
||||
name="group_email_resend",
|
||||
),
|
||||
path(
|
||||
"cids/group/<int:pk>/update", views.CidUserGroupUpdate.as_view(), name="group_update"
|
||||
"cids/group/<int:pk>/update", views.CidUserGroupUpdate.as_view(), name="cid_group_update"
|
||||
),
|
||||
path(
|
||||
"cids/group/create", views.CidUserGroupCreate.as_view(), name="group_create"
|
||||
"cids/group/create", views.CidUserGroupCreate.as_view(), name="cid_group_create"
|
||||
),
|
||||
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/<int:pk>/update", views.UserUserGroupUpdate.as_view(), name="user_group_update"
|
||||
),
|
||||
path(
|
||||
"user/group/create", views.UserUserGroupCreate.as_view(), name="user_group_create"
|
||||
),
|
||||
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
|
||||
]
|
||||
|
||||
+126
-53
@@ -43,9 +43,10 @@ from .forms import (
|
||||
CidUserForm,
|
||||
ExaminationForm,
|
||||
CidUserGroupForm,
|
||||
UserUserGroupForm,
|
||||
)
|
||||
|
||||
from .models import CidUser, CidUserGroup, Examination, QuestionNote
|
||||
from .models import CidUser, CidUserGroup, Examination, QuestionNote, UserUserGroup
|
||||
|
||||
from rapids.models import Rapid as RapidQuestion
|
||||
from rapids.models import Exam as RapidExam
|
||||
@@ -623,14 +624,18 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if not self.check_user_access(request.user, exam_id):
|
||||
raise PermissionDenied
|
||||
|
||||
cid_users = exam.valid_users.all()
|
||||
|
||||
cid_users = exam.valid_cid_users.all()
|
||||
cid_user_count = cid_users.count()
|
||||
|
||||
user_users = exam.valid_user_users.all()
|
||||
user_user_count = user_users.count()
|
||||
|
||||
context = {
|
||||
"exam": exam,
|
||||
"cid_users": cid_users,
|
||||
"cid_user_count": cid_user_count,
|
||||
"user_users": user_users,
|
||||
"user_user_count": user_user_count,
|
||||
"app_name": self.app_name,
|
||||
}
|
||||
|
||||
@@ -644,6 +649,41 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
|
||||
)
|
||||
|
||||
def exam_users_edit(self, request, exam_id):
|
||||
exam = get_object_or_404(self.Exam, pk=exam_id)
|
||||
|
||||
if not request.user.groups.filter(name="cid_user_manager").exists():
|
||||
#raise PermissionDenied
|
||||
if request.user not in exam.author.all():
|
||||
if not self.check_user_access(request.user, exam_id):
|
||||
raise PermissionDenied
|
||||
|
||||
current_user_users = exam.valid_user_users.all()
|
||||
|
||||
exam_groups = exam.user_user_groups.all()
|
||||
|
||||
available_user_users = list(User.objects.filter(user_groups__in=exam_groups).difference(current_user_users))
|
||||
print(available_user_users)
|
||||
#available_user_users = User.objects.all()
|
||||
|
||||
context = {
|
||||
"exam": exam,
|
||||
"groups": exam_groups,
|
||||
"current_user_users": current_user_users,
|
||||
"available_user_users": available_user_users,
|
||||
"app_name": self.app_name,
|
||||
}
|
||||
|
||||
if self.app_name == "atlas":
|
||||
context["collection"] = exam
|
||||
|
||||
return render(
|
||||
request,
|
||||
"exam_users_edit.html",
|
||||
context
|
||||
)
|
||||
|
||||
|
||||
def exam_cids_edit(self, request, exam_id):
|
||||
exam = get_object_or_404(self.Exam, pk=exam_id)
|
||||
|
||||
@@ -653,7 +693,7 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if not self.check_user_access(request.user, exam_id):
|
||||
raise PermissionDenied
|
||||
|
||||
current_cid_users = exam.valid_users.all()
|
||||
current_cid_users = exam.valid_cid_users.all()
|
||||
|
||||
exam_groups = exam.cid_user_groups.all()
|
||||
|
||||
@@ -725,6 +765,26 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
data = {"status": "success", "added": add}
|
||||
return JsonResponse(data, status=200)
|
||||
|
||||
if "edit_user_user" in request.POST:
|
||||
user_id = request.POST.get("edit_user_user")
|
||||
|
||||
add = request.POST.get("add") == "true"
|
||||
|
||||
user_user = User.objects.get(pk=user_id)
|
||||
|
||||
app_exam_map = {}
|
||||
app_exam_map["rapids"] = user_user.user_rapid_exams
|
||||
#app_exam_map["atlas"] = user_user.casecollection_exams
|
||||
|
||||
if add:
|
||||
app_exam_map[self.app_name].add(pk)
|
||||
else:
|
||||
app_exam_map[self.app_name].remove(pk)
|
||||
|
||||
|
||||
data = {"status": "success", "added": add}
|
||||
return JsonResponse(data, status=200)
|
||||
|
||||
if "add_exam_questions" in request.POST:
|
||||
question_ids = json.loads(request.POST.get("add_exam_questions"))
|
||||
question_objects = self.Question.objects.filter(pk__in=question_ids)
|
||||
@@ -932,7 +992,7 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
for exam in exams:
|
||||
if exam.active or self.check_user_access(request.user, exam.pk):
|
||||
print(exam.name, cid, passcode)
|
||||
if exam.exam_mode and not exam.check_cid_user(cid, passcode):
|
||||
if exam.exam_mode and not exam.check_cid_user(cid, passcode, user_id=request.user.pk):
|
||||
print(exam.name, "fail")
|
||||
continue
|
||||
|
||||
@@ -979,7 +1039,7 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
n = 0
|
||||
|
||||
for answer in json.loads(request.POST.get("answers")):
|
||||
ret_status, ret = self.loadJsonAnswer(answer)
|
||||
ret_status, ret = self.loadJsonAnswer(answer, user=request.user)
|
||||
# try:
|
||||
# ret_status, ret = self.loadJsonAnswer(answer)
|
||||
# except:
|
||||
@@ -996,15 +1056,18 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
|
||||
# print(UserAnswer.objects.filter(exam__id=q["eid"]))
|
||||
# print(request.urlencode())
|
||||
|
||||
exam_type, exam_id = request.POST.get("eid").split("/")
|
||||
|
||||
c = CidUser.objects.filter(cid=request.POST.get("cid")).first()
|
||||
exam = self.Exam.objects.get(pk=exam_id)
|
||||
t = timezone.make_aware(
|
||||
datetime.fromtimestamp(float(request.POST.get("start_time")))
|
||||
)
|
||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c, start_time=t)
|
||||
|
||||
if request.POST.get("cid").startswith("u-"):
|
||||
cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user, start_time=t)
|
||||
else:
|
||||
c = CidUser.objects.filter(cid=request.POST.get("cid")).first()
|
||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid_user=c, start_time=t)
|
||||
|
||||
cid_user_exam.end_time = timezone.now()
|
||||
cid_user_exam.save()
|
||||
@@ -1021,7 +1084,11 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if not exam.active and not self.check_user_access(request.user, pk):
|
||||
raise Http404("No available exam")
|
||||
|
||||
time = datetime.now()
|
||||
|
||||
exam_json = exam.get_exam_json(based=False)
|
||||
exam_json["generated"] = time.isoformat()
|
||||
exam_json["exam_json_id"] = exam.exam_json_id
|
||||
# exam_json["exam_active"] = False
|
||||
return JsonResponse(exam_json)
|
||||
|
||||
@@ -1115,10 +1182,10 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if not exam.exam_mode:
|
||||
raise Http404("Packet not in exam mode")
|
||||
|
||||
user_answers_and_marks = defaultdict(list)
|
||||
#user_answers_and_marks = defaultdict(list)
|
||||
user_answers_marks = defaultdict(list)
|
||||
user_answers = defaultdict(list)
|
||||
user_names = {}
|
||||
#user_names = {}
|
||||
# cid_passcodes = {}
|
||||
|
||||
by_question = defaultdict(dict)
|
||||
@@ -1136,11 +1203,16 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
# Loop through all candidates
|
||||
for cid_user_answer in cid_user_answers:
|
||||
# Convoluted (probably...)
|
||||
cid = cid_user_answer.cid
|
||||
# cid_passcodes[cid] = cid_user_answer.passcode
|
||||
cids.add(cid)
|
||||
if cid_user_answer.user is None:
|
||||
cid = cid_user_answer.cid
|
||||
# cid_passcodes[cid] = cid_user_answer.passcode
|
||||
cids.add(cid)
|
||||
else:
|
||||
cid = f"u/{cid_user_answer.user.username}"
|
||||
cids.add(cid)
|
||||
|
||||
s = cid_user_answer
|
||||
user_names[cid] = cid
|
||||
#user_names[cid] = cid
|
||||
|
||||
q = cid_user_answer.question
|
||||
|
||||
@@ -1159,7 +1231,7 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
unmarked.add(index)
|
||||
user_answers[cid].append(ans)
|
||||
user_answers_marks[cid].append(answer_score)
|
||||
user_answers_and_marks[cid].append((ans, answer_score))
|
||||
#user_answers_and_marks[cid].append((ans, answer_score))
|
||||
|
||||
if self.app_name in ("rapids", "anatomy", "sbas"):
|
||||
by_question[q][cid] = (ans, answer_score)
|
||||
@@ -1247,13 +1319,13 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
"unmarked": unmarked,
|
||||
"questions": questions,
|
||||
"by_question": by_question,
|
||||
"user_answers": dict(user_answers),
|
||||
"user_answers_marks": dict(user_answers_marks),
|
||||
#"user_answers": dict(user_answers),
|
||||
#"user_answers_marks": dict(user_answers_marks),
|
||||
"user_scores": user_scores,
|
||||
"user_scores_normalised": user_scores_normalised,
|
||||
"user_scores_list": user_scores_list,
|
||||
"user_names": user_names,
|
||||
"user_answers_and_marks": user_answers_and_marks,
|
||||
#"user_scores_list": user_scores_list,
|
||||
#"user_names": user_names,
|
||||
#"user_answers_and_marks": user_answers_and_marks,
|
||||
"max_score": max_score,
|
||||
"mean": mean,
|
||||
"median": median,
|
||||
@@ -1480,36 +1552,43 @@ class CidUserView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
|
||||
return context
|
||||
|
||||
|
||||
@login_required
|
||||
@user_is_cid_user_manager
|
||||
def group_view(request):
|
||||
def cid_group_view(request):
|
||||
groups = CidUserGroup.objects.filter(archive=False)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/group_view.html",
|
||||
"generic/cid_group_view.html",
|
||||
{"groups": groups},
|
||||
)
|
||||
|
||||
|
||||
@user_is_cid_user_manager
|
||||
def group_view(request):
|
||||
groups = CidUserGroup.objects.filter(archive=False)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/group_view.html",
|
||||
{"groups": groups, "view_all": False},
|
||||
)
|
||||
|
||||
|
||||
@user_is_cid_user_manager
|
||||
def group_view_all(request):
|
||||
def cid_group_view_all(request):
|
||||
groups = CidUserGroup.objects.filter()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/group_view.html",
|
||||
"generic/cid_group_view.html",
|
||||
{"groups": groups, "view_all": True},
|
||||
)
|
||||
|
||||
@user_is_cid_user_manager
|
||||
def user_group_view(request):
|
||||
groups = UserUserGroup.objects.filter(archive=False)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/user_group_view.html",
|
||||
{"groups": groups},
|
||||
)
|
||||
|
||||
@user_is_cid_user_manager
|
||||
def user_group_view_all(request):
|
||||
groups = UserUserGroup.objects.filter()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/user_group_view.html",
|
||||
{"groups": groups, "view_all": True},
|
||||
)
|
||||
|
||||
@@ -1543,21 +1622,6 @@ def group_email(request, pk, resend=False):
|
||||
{"users": users, "users_count": users.count(), "results": False},
|
||||
)
|
||||
|
||||
# sent = []
|
||||
# not_sent = []
|
||||
# for u in users:
|
||||
# success, msg = u.email_details(resend=resend)
|
||||
# if success:
|
||||
# sent.append(u)
|
||||
# else:
|
||||
# not_sent.append((u, msg))
|
||||
|
||||
# return render(
|
||||
# request,
|
||||
# "generic/group_emailed.html",
|
||||
# {"sent": sent, "not_sent": not_sent, "users_count": users.count()},
|
||||
# )
|
||||
|
||||
|
||||
@user_is_cid_user_manager
|
||||
def group_email_results(request, pk, resend=False):
|
||||
@@ -1788,3 +1852,12 @@ class CidUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
|
||||
class CidUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
|
||||
model = CidUserGroup
|
||||
form_class = CidUserGroupForm
|
||||
|
||||
class UserUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
|
||||
model = UserUserGroup
|
||||
form_class = UserUserGroupForm
|
||||
|
||||
|
||||
class UserUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
|
||||
model = UserUserGroup
|
||||
form_class = UserUserGroupForm
|
||||
Reference in New Issue
Block a user