From 41a09ea30d53806a8a429a4f1bef4a0413e464cd Mon Sep 17 00:00:00 2001
From: Ross
Date: Tue, 24 May 2022 12:30:11 +0100
Subject: [PATCH] clean up
---
anatomy/static/css/anatomy.css | 9 +
anatomy/templates/anatomy/index.html | 14 -
generic/admin.py | 4 +
generic/forms.py | 20 +-
generic/migrations/0033_userprofile_grade.py | 18 ++
generic/migrations/0034_auto_20220523_2010.py | 29 ++
generic/migrations/0035_userprofile_grade.py | 19 ++
generic/models.py | 13 +
generic/templates/generic/base.html | 1 +
generic/templates/generic/cid_group_view.html | 14 +-
.../templates/generic/cidusergroup_form.html | 14 +-
generic/templates/generic/exam_index.html | 25 ++
.../templates/generic/user_group_view.html | 14 +-
.../templates/generic/userusergroup_form.html | 47 ++-
generic/urls.py | 6 +
generic/views.py | 273 +++++++++++-------
longs/templates/longs/index.html | 11 -
longs/templates/longs/index_old.html | 23 --
physics/templates/physics/index.html | 10 -
rad/settings.py | 21 +-
rad/urls.py | 4 +
rad/views.py | 76 ++++-
rapids/templates/rapids/index.html | 13 -
rapids/templates/rapids/index_old.html | 23 --
rapids/tests/test_urls.py | 11 +-
sbas/templates/sbas/index.html | 10 -
templates/profile.html | 7 +-
templates/user_list_view.html | 15 +
templates/user_update.html | 9 +
29 files changed, 506 insertions(+), 247 deletions(-)
delete mode 100644 anatomy/templates/anatomy/index.html
create mode 100644 generic/migrations/0033_userprofile_grade.py
create mode 100644 generic/migrations/0034_auto_20220523_2010.py
create mode 100644 generic/migrations/0035_userprofile_grade.py
create mode 100644 generic/templates/generic/exam_index.html
delete mode 100644 longs/templates/longs/index.html
delete mode 100755 longs/templates/longs/index_old.html
delete mode 100644 physics/templates/physics/index.html
delete mode 100644 rapids/templates/rapids/index.html
delete mode 100755 rapids/templates/rapids/index_old.html
delete mode 100644 sbas/templates/sbas/index.html
create mode 100644 templates/user_list_view.html
create mode 100644 templates/user_update.html
diff --git a/anatomy/static/css/anatomy.css b/anatomy/static/css/anatomy.css
index 8b96e14c..21226aa6 100644
--- a/anatomy/static/css/anatomy.css
+++ b/anatomy/static/css/anatomy.css
@@ -837,4 +837,13 @@ summary h5 {
bottom: 0px;
background-color: black;
opacity: 80%;
+}
+
+.small-url-button {
+ padding: 0px;
+}
+
+button a, button a:link, button a:visited, button a:hover {
+ text-decoration: none;
+ color: inherit;
}
\ No newline at end of file
diff --git a/anatomy/templates/anatomy/index.html b/anatomy/templates/anatomy/index.html
deleted file mode 100644
index 0837ca97..00000000
--- a/anatomy/templates/anatomy/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends 'anatomy/base.html' %}
-
-{% block content %}
- {% for exam in exams %}
-
-
-
- {% if exam.exam_mode %}
- {% if request.user.is_staff %}
Mark answers{% endif %}
- {% if request.user.is_staff %}
Scores{% endif %}
- {% endif %}
-
- {% endfor %}
-{% endblock %}
diff --git a/generic/admin.py b/generic/admin.py
index 20981ea2..cba9b4b1 100644
--- a/generic/admin.py
+++ b/generic/admin.py
@@ -13,6 +13,8 @@ from .models import (
NoteType,
CidUser,
CidUserExam,
+ UserGrades,
+ UserProfile,
)
# from .models import Examination, Sign, Site, Condition
@@ -28,3 +30,5 @@ admin.site.register(NoteType)
admin.site.register(CidUser)
admin.site.register(CidUserExam)
admin.site.register(CidUserGroup)
+admin.site.register(UserProfile)
+admin.site.register(UserGrades)
diff --git a/generic/forms.py b/generic/forms.py
index 10ac3756..d308746b 100755
--- a/generic/forms.py
+++ b/generic/forms.py
@@ -11,7 +11,7 @@ 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, UserUserGroup
+from generic.models import CidUser, CidUserGroup, Examination, QuestionNote, UserProfile, UserUserGroup
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea
@@ -236,12 +236,26 @@ class CidUserGroupForm(ModelForm):
model = CidUserGroup
fields = ["name", "archive"]
+class UserUserGroupModelChoiceField(ModelMultipleChoiceField):
+ def label_from_instance(self, obj):
+ return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]"
+
class UserUserGroupForm(ModelForm):
- users = ModelMultipleChoiceField(
+ users = UserUserGroupModelChoiceField(
required=False,
queryset=User.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
)
class Meta:
model = UserUserGroup
- fields = ["name", "archive", "users"]
\ No newline at end of file
+ fields = ["name", "archive", "users"]
+
+#class UserForm(ModelForm):
+# class Meta:
+# model = User
+# fields = ["first_name", "last_name", "email"]
+#
+#class UserProfileForm(ModelForm):
+# class Meta:
+# model = UserProfile
+# fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"]
\ No newline at end of file
diff --git a/generic/migrations/0033_userprofile_grade.py b/generic/migrations/0033_userprofile_grade.py
new file mode 100644
index 00000000..8c343ed3
--- /dev/null
+++ b/generic/migrations/0033_userprofile_grade.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.13 on 2022-05-23 17:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('generic', '0032_userprofile_registration_number'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userprofile',
+ name='grade',
+ field=models.IntegerField(blank=True, help_text='ST grade of the user', null=True),
+ ),
+ ]
diff --git a/generic/migrations/0034_auto_20220523_2010.py b/generic/migrations/0034_auto_20220523_2010.py
new file mode 100644
index 00000000..506ba8a3
--- /dev/null
+++ b/generic/migrations/0034_auto_20220523_2010.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.13 on 2022-05-23 19:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('generic', '0033_userprofile_grade'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserGrades',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ],
+ ),
+ migrations.RemoveField(
+ model_name='userprofile',
+ name='grade',
+ ),
+ migrations.AddField(
+ model_name='userprofile',
+ name='peninsula_trainee',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/generic/migrations/0035_userprofile_grade.py b/generic/migrations/0035_userprofile_grade.py
new file mode 100644
index 00000000..84fdfaaa
--- /dev/null
+++ b/generic/migrations/0035_userprofile_grade.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.13 on 2022-05-23 19:10
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('generic', '0034_auto_20220523_2010'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userprofile',
+ name='grade',
+ field=models.ForeignKey(blank=True, help_text='User grade', null=True, on_delete=django.db.models.deletion.CASCADE, to='generic.usergrades'),
+ ),
+ ]
diff --git a/generic/models.py b/generic/models.py
index 0425c209..a6f0cbe2 100644
--- a/generic/models.py
+++ b/generic/models.py
@@ -563,12 +563,25 @@ class UserUserGroup(models.Model):
def get_absolute_url(self):
return reverse("generic:user_group_update", kwargs={"pk": self.pk})
+class UserGrades(models.Model):
+ name = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.name
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)
registration_number = models.CharField(max_length=25, blank=True)
+ grade = models.ForeignKey(UserGrades, null=True, blank=True, help_text="User grade", on_delete=models.CASCADE)
+ peninsula_trainee = models.BooleanField(default=False)
+
+
+ def getusername(self):
+ return self.user.username
+
+ username = property(getusername)
@receiver(post_save, sender=User)
diff --git a/generic/templates/generic/base.html b/generic/templates/generic/base.html
index a180f3ca..3b934d00 100755
--- a/generic/templates/generic/base.html
+++ b/generic/templates/generic/base.html
@@ -21,6 +21,7 @@
Cids (exams) /
Cid Groups /
User Groups /
+ Manage Users /
{% endif %}
{% endblock %}
diff --git a/generic/templates/generic/cid_group_view.html b/generic/templates/generic/cid_group_view.html
index 05eb5365..d6fc119a 100755
--- a/generic/templates/generic/cid_group_view.html
+++ b/generic/templates/generic/cid_group_view.html
@@ -7,22 +7,24 @@
{% for group in groups %}
{{group.name}}
-
-
-
-
-
+
{% endfor %}
-->
-{{form.media}}
+
+ {{form.media}}
-
+
-
+
{% endblock %}
{% block content %}
-Add / Edit Group / {{userusergroup.name}}
-Use this form to create / edit a CID user group.
-
+ {% comment %} {% if userusergroup.users.all %}
+ {{userusergroup.GetGroupUsers}}
+ {% else %}
+ No users currently added to group.
+ {% endif %} {% endcomment %}
+
+
{% endblock %}
\ No newline at end of file
diff --git a/generic/urls.py b/generic/urls.py
index 476bb39b..72d6a23f 100755
--- a/generic/urls.py
+++ b/generic/urls.py
@@ -62,6 +62,9 @@ urlpatterns = [
path(
"cids/group//update", views.CidUserGroupUpdate.as_view(), name="cid_group_update"
),
+ path(
+ "cids/group//delete", views.CidUserGroupDelete.as_view(), name="cid_group_delete"
+ ),
path(
"cids/group/create", views.CidUserGroupCreate.as_view(), name="cid_group_create"
),
@@ -70,6 +73,9 @@ urlpatterns = [
path(
"user/group//update", views.UserUserGroupUpdate.as_view(), name="user_group_update"
),
+ path(
+ "user/group//delete", views.UserUserGroupDelete.as_view(), name="user_group_delete"
+ ),
path(
"user/group/create", views.UserUserGroupCreate.as_view(), name="user_group_create"
),
diff --git a/generic/views.py b/generic/views.py
index 2cb1d00c..463692d7 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -8,6 +8,7 @@ from django.forms.models import model_to_dict
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, user_passes_test
+from django.urls import reverse_lazy
from django.urls.base import reverse
from django.utils import timezone
from django.utils.html import format_html
@@ -29,8 +30,9 @@ import json
import urllib
from django.views import View
-from django.views.generic.edit import CreateView, UpdateView
+from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django_filters.views import FilterView
+from django_filters import FilterSet
from django_tables2.views import SingleTableMixin
from reversion.views import RevisionMixin
from atlas.models import CaseCollection
@@ -71,6 +73,7 @@ import plotly.express as px
# from rad.views import get_question_and_content_type
from django.db.models import Prefetch
+
class AuthorRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
@@ -78,6 +81,7 @@ class AuthorRequiredMixin(object):
raise PermissionDenied() # or Http404
return obj
+
class CidManagerRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.groups.filter(name="cid_user_manager").exists()
@@ -289,7 +293,47 @@ class ExamViews(View, LoginRequiredMixin):
# group_map = {"rapids" : "rapid_checker", "anatomy" : "anatomy_checker", "longs":"long_checker"}
# exam_group = group_map[self.app_name]
- def check_user_access(self, user, exam_id=None):
+ class ExamFilter(FilterSet):
+ class Meta:
+ model = exam
+ fields = {
+ "name": ["contains"],
+ "exam_mode": ["exact"],
+ "active": ["exact"],
+ "archive": ["exact"],
+ "open_access": ["exact"],
+ #'release_date': ['isnull'],
+ }
+ # filter_overrides = {
+ # models.CharField: {
+ # 'filter_class': django_filters.CharFilter,
+ # 'extra': lambda f: {
+ # 'lookup_expr': 'icontains',
+ # },
+ # },
+ # models.BooleanField: {
+ # 'filter_class': django_filters.BooleanFilter,
+ # 'extra': lambda f: {
+ # 'widget': forms.CheckboxInput,
+ # },
+ # },
+ # }
+ def __init__(self, data=None, *args, **kwargs):
+ # if filterset is bound, use initial values as defaults
+ if data is not None:
+ # get a mutable copy of the QueryDict
+ data = data.copy()
+
+ #for name, f in self.base_filters.items():
+ # filter param is either missing or empty, use initial as default
+ if not data.get("archive"):
+ data["archive"] = False
+
+ super().__init__(data, *args, **kwargs)
+
+ self.ExamFilter = ExamFilter
+
+ def check_user_access(self, user: User, exam_id: int = None):
"""Check if a user should be able to access a view
Args:
@@ -547,9 +591,7 @@ class ExamViews(View, LoginRequiredMixin):
)
elif self.app_name == "rapids":
questions = (
- exam.exam_questions.select_related(
-
- )
+ exam.exam_questions.select_related()
.all()
.prefetch_related(
Prefetch(
@@ -559,7 +601,11 @@ class ExamViews(View, LoginRequiredMixin):
),
to_attr="prefetched_primary_answer"
# queryset=self.Answer.objects.filter(),
- ), "images", "abnormality", "region", "examination"
+ ),
+ "images",
+ "abnormality",
+ "region",
+ "examination",
)
)
# questions = (
@@ -620,7 +666,7 @@ class ExamViews(View, LoginRequiredMixin):
def exam_cids(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():
+ # if not request.user.groups.filter(name="cid_user_manager").exists():
# raise PermissionDenied
if request.user not in exam.author.all():
@@ -635,29 +681,24 @@ class ExamViews(View, LoginRequiredMixin):
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,
- }
-
+ "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,
+ }
+
if self.app_name == "atlas":
context["collection"] = exam
- return render(
- request,
- "exam_cids.html",
- context
-
- )
+ return render(request, "exam_cids.html", context)
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
+ # raise PermissionDenied
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
raise PermissionDenied
@@ -666,33 +707,32 @@ class ExamViews(View, LoginRequiredMixin):
exam_groups = exam.user_user_groups.all()
- available_user_users = list(User.objects.filter(user_groups__in=exam_groups).difference(current_user_users))
+ 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()
+ # 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,
- }
-
+ "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
- )
-
+ 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)
if not request.user.groups.filter(name="cid_user_manager").exists():
- #raise PermissionDenied
+ # raise PermissionDenied
if request.user not in exam.author.all():
if not self.check_user_access(request.user, exam_id):
raise PermissionDenied
@@ -701,24 +741,22 @@ class ExamViews(View, LoginRequiredMixin):
exam_groups = exam.cid_user_groups.all()
- available_cid_users = CidUser.objects.filter(group__in=exam_groups).difference(current_cid_users)
+ available_cid_users = CidUser.objects.filter(group__in=exam_groups).difference(
+ current_cid_users
+ )
context = {
- "exam": exam,
- "groups": exam_groups,
- "current_cid_users": current_cid_users,
- "available_cid_users": available_cid_users,
- "app_name": self.app_name,
- }
-
+ "exam": exam,
+ "groups": exam_groups,
+ "current_cid_users": current_cid_users,
+ "available_cid_users": available_cid_users,
+ "app_name": self.app_name,
+ }
+
if self.app_name == "atlas":
context["collection"] = exam
- return render(
- request,
- "exam_cids_edit.html",
- context
- )
+ return render(request, "exam_cids_edit.html", context)
# @method_decorator(login_required)
def exam_notes(self, request, pk):
@@ -751,7 +789,7 @@ class ExamViews(View, LoginRequiredMixin):
if "edit_cid_user" in request.POST:
user_id = request.POST.get("edit_cid_user")
-
+
add = request.POST.get("add") == "true"
cid_user = CidUser.objects.get(pk=user_id)
@@ -765,27 +803,25 @@ class ExamViews(View, LoginRequiredMixin):
else:
app_exam_map[self.app_name].remove(pk)
-
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
+ # 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)
@@ -927,25 +963,30 @@ class ExamViews(View, LoginRequiredMixin):
@method_decorator(login_required)
def index(self, request):
- exams = self.Exam.objects.filter(author__id=request.user.id)
+
+ # self.check_user_access(request.user, exam.pk)
if (
- self.app_name == "rapids"
- and request.user.groups.filter(name="rapid_checker").exists()
- ):
- exams = self.Exam.objects.all()
- if (
- self.app_name == "anatomy"
- and request.user.groups.filter(name="anatomy_checker").exists()
- ):
- exams = self.Exam.objects.all()
- if (
- self.app_name == "longs"
- and request.user.groups.filter(name="long_checker").exists()
+ (
+ self.app_name == "rapids"
+ and request.user.groups.filter(name="rapid_checker").exists()
+ )
+ or (
+ self.app_name == "anatomy"
+ and request.user.groups.filter(name="anatomy_checker").exists()
+ )
+ or (
+ self.app_name == "longs"
+ and request.user.groups.filter(name="long_checker").exists()
+ )
):
exams = self.Exam.objects.all()
+ else:
+ exams = self.Exam.objects.filter(author__id=request.user.id)
- return render(request, "{}/index.html".format(self.app_name), {"exams": exams})
+ filter = self.ExamFilter(request.GET, queryset=exams)
+
+ return render(request, "generic/exam_index.html", {"filter": filter, "app_name": self.app_name})
@method_decorator(login_required)
def exam_question_detail(self, request, pk, sk):
@@ -996,7 +1037,9 @@ 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, user_id=request.user.pk):
+ if exam.exam_mode and not exam.check_cid_user(
+ cid, passcode, user_id=request.user.pk
+ ):
print(exam.name, "fail")
continue
@@ -1061,7 +1104,10 @@ class ExamViews(View, LoginRequiredMixin):
else:
return JsonResponse(
- {"success": False, "error": "cid or eid or answers not defined"}
+ {
+ "success": False,
+ "error": "cid or eid or answers not defined",
+ }
)
if not uid:
@@ -1070,7 +1116,10 @@ class ExamViews(View, LoginRequiredMixin):
not isinstance(eid, int) or (not isinstance(answer["ans"], str))
):
return JsonResponse(
- {"success": False, "error": "cid or eid or answers not defined"}
+ {
+ "success": False,
+ "error": "cid or eid or answers not defined",
+ }
)
exam = get_object_or_404(self.Exam, pk=eid)
@@ -1100,9 +1149,15 @@ class ExamViews(View, LoginRequiredMixin):
if not existing_answers:
if uid:
- ans = self.CidUserAnswer(answer=posted_answer, normal=normal, user=User.objects.get(id=uid))
+ ans = self.CidUserAnswer(
+ answer=posted_answer,
+ normal=normal,
+ user=User.objects.get(id=uid),
+ )
else:
- ans = self.CidUserAnswer(answer=posted_answer, normal=normal, cid=cid)
+ ans = self.CidUserAnswer(
+ answer=posted_answer, normal=normal, cid=cid
+ )
ans.question_id = answer["qid"]
ans.exam_id = eid
@@ -1118,11 +1173,12 @@ class ExamViews(View, LoginRequiredMixin):
case "anatomy":
if not existing_answers:
if uid:
- ans = self.CidUserAnswer(answer=posted_answer, uid=User.objects.get(id=uid))
+ ans = self.CidUserAnswer(
+ answer=posted_answer, uid=User.objects.get(id=uid)
+ )
else:
ans = self.CidUserAnswer(answer=posted_answer, cid=cid)
-
ans.question_id = answer["qid"]
ans.exam_id = eid
ans.score = self.Answer.MarkOptions.UNMARKED
@@ -1168,14 +1224,12 @@ class ExamViews(View, LoginRequiredMixin):
ans.full_clean()
ans.save()
-
-
# if ret is not True:
# return ret
n = n + 1
- # print(UserAnswer.objects.filter(exam__id=q["eid"]))
+ # print(UserAnswer.objects.filter(exam__id=q["eid"]))
# print(request.urlencode())
exam_type, exam_id = request.POST.get("eid").split("/")
@@ -1185,10 +1239,14 @@ class ExamViews(View, LoginRequiredMixin):
)
if request.POST.get("cid").startswith("u-"):
- cid_user_exam = exam.get_or_create_cid_user_exam(user_user=request.user, start_time=t)
+ 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 = exam.get_or_create_cid_user_exam(
+ cid_user=c, start_time=t
+ )
cid_user_exam.end_time = timezone.now()
cid_user_exam.save()
@@ -1303,10 +1361,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)
@@ -1333,7 +1391,7 @@ class ExamViews(View, LoginRequiredMixin):
cids.add(cid)
s = cid_user_answer
- #user_names[cid] = cid
+ # user_names[cid] = cid
q = cid_user_answer.question
@@ -1352,7 +1410,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)
@@ -1440,13 +1498,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,
@@ -1621,7 +1679,10 @@ class CidUserExamView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
sba_exams = [(i.name, i.pk) for i in SbasExam.objects.filter(**filters)]
longs_exams = [(i.name, i.pk) for i in LongExam.objects.filter(**filters)]
anatomy_exams = [(i.name, i.pk) for i in AnatomyExam.objects.filter(**filters)]
- casecollection_exams = [(i.name, i.pk) for i in CaseCollection.objects.filter(archive=False, collection_type__gt = 0)]
+ casecollection_exams = [
+ (i.name, i.pk)
+ for i in CaseCollection.objects.filter(archive=False, collection_type__gt=0)
+ ]
cid_user_groups = [
(i.name, i.pk) for i in CidUserGroup.objects.filter(archive=False)
@@ -1656,7 +1717,10 @@ class CidUserView(CidManagerRequiredMixin, SingleTableMixin, FilterView):
sba_exams = [(i.name, i.pk) for i in SbasExam.objects.filter(**filters)]
longs_exams = [(i.name, i.pk) for i in LongExam.objects.filter(**filters)]
anatomy_exams = [(i.name, i.pk) for i in AnatomyExam.objects.filter(**filters)]
- casecollection_exams = [(i.name, i.pk) for i in CaseCollection.objects.filter(archive=False, collection_type__gt = 0)]
+ casecollection_exams = [
+ (i.name, i.pk)
+ for i in CaseCollection.objects.filter(archive=False, collection_type__gt=0)
+ ]
context["physics_exams"] = physics_exams
context["rapid_exams"] = rapid_exams
@@ -1683,6 +1747,7 @@ def cid_group_view(request):
{"groups": groups},
)
+
@user_is_cid_user_manager
def cid_group_view_all(request):
groups = CidUserGroup.objects.filter()
@@ -1693,6 +1758,7 @@ def cid_group_view_all(request):
{"groups": groups, "view_all": True},
)
+
@user_is_cid_user_manager
def user_group_view(request):
groups = UserUserGroup.objects.filter(archive=False)
@@ -1703,6 +1769,7 @@ def user_group_view(request):
{"groups": groups},
)
+
@user_is_cid_user_manager
def user_group_view_all(request):
groups = UserUserGroup.objects.filter()
@@ -1776,12 +1843,11 @@ def group_email_results(request, pk, resend=False):
def candidate_email_details(request, cid, resend=False):
user = CidUser.objects.get(cid=cid)
- email_sent, comment = user.email_details(
- resend=False
- )
+ email_sent, comment = user.email_details(resend=False)
return JsonResponse({"sent": email_sent, "comment": comment})
+
@user_is_cid_user_manager
def candidate_email_results(request, cid, resend=False):
user = CidUser.objects.get(cid=cid)
@@ -1965,6 +2031,12 @@ class CidUserUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
form_class = CidUserForm
+class CidUserGroupDelete(RevisionMixin, CidManagerRequiredMixin, DeleteView):
+ model = CidUserGroup
+ template_name = "confirm_delete.html"
+ success_url = reverse_lazy("generic:cid_group_view")
+
+
class CidUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
model = CidUserGroup
form_class = CidUserGroupForm
@@ -1974,6 +2046,7 @@ class CidUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
model = CidUserGroup
form_class = CidUserGroupForm
+
class UserUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
model = UserUserGroup
form_class = UserUserGroupForm
@@ -1981,4 +2054,10 @@ class UserUserGroupCreate(RevisionMixin, CidManagerRequiredMixin, CreateView):
class UserUserGroupUpdate(RevisionMixin, CidManagerRequiredMixin, UpdateView):
model = UserUserGroup
- form_class = UserUserGroupForm
\ No newline at end of file
+ form_class = UserUserGroupForm
+
+
+class UserUserGroupDelete(RevisionMixin, CidManagerRequiredMixin, DeleteView):
+ model = UserUserGroup
+ template_name = "confirm_delete.html"
+ success_url = reverse_lazy("generic:user_group_view")
diff --git a/longs/templates/longs/index.html b/longs/templates/longs/index.html
deleted file mode 100644
index d2a9f4fc..00000000
--- a/longs/templates/longs/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends 'longs/base.html' %}
-
-{% block content %}
-{% for exam in exams %}
-
-
- {% if request.user.is_staff %}
Mark answers{% endif %}
- {% if request.user.is_staff %}
Scores{% endif %}
-
-{% endfor %}
-{% endblock %}
\ No newline at end of file
diff --git a/longs/templates/longs/index_old.html b/longs/templates/longs/index_old.html
deleted file mode 100755
index c6044e0d..00000000
--- a/longs/templates/longs/index_old.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends 'longs/base.html' %}
-
-{% block content %}
-My Questions:
-
-
-Other Questions:
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/physics/templates/physics/index.html b/physics/templates/physics/index.html
deleted file mode 100644
index d7e2ca20..00000000
--- a/physics/templates/physics/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends 'physics/base.html' %}
-
-{% block content %}
-{% for exam in exams %}
-
-
- {% if request.user.is_staff %}
Scores{% endif %}
-
-{% endfor %}
-{% endblock %}
\ No newline at end of file
diff --git a/rad/settings.py b/rad/settings.py
index 23791d7e..9ae7f581 100644
--- a/rad/settings.py
+++ b/rad/settings.py
@@ -269,18 +269,27 @@ SENDGRID_API_KEY = (
"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"
)
-EMAIL_HOST = "smtp.sendgrid.net"
-EMAIL_HOST_USER = "apikey" # this is exactly the value 'apikey'
-EMAIL_HOST_PASSWORD = SENDGRID_API_KEY
-EMAIL_PORT = 587
-EMAIL_USE_TLS = True
+# SENDGRID
+#EMAIL_HOST = "smtp.sendgrid.net"
+#EMAIL_HOST_USER = "apikey" # this is exactly the value 'apikey'
+#EMAIL_HOST_PASSWORD = SENDGRID_API_KEY
+#EMAIL_PORT = 587
+#EMAIL_USE_TLS = True
+
# EMAIL_HOST = "smtp.office365.com "
# EMAIL_HOST_USER = "Penra.courses@nhs.net"
# EMAIL_HOST_PASSWORD = '"got3<}%o"~\J",\Di3<'
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_USE_SSl = False
-# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
+
+DEFAULT_FROM_EMAIL = "no-reply@penracourses.org.uk"
+EMAIL_HOST = "webhosting2027.is.cc"
+EMAIL_HOST_USER = "no-reply@penracourses.org.uk"
+EMAIL_HOST_PASSWORD = "I(5E)&1w+Bh)"
+EMAIL_PORT = 465
+#EMAIL_USE_TLS = True
+EMAIL_USE_SSL = True
try:
from .settings_local import *
diff --git a/rad/urls.py b/rad/urls.py
index 2467921b..de8bdced 100644
--- a/rad/urls.py
+++ b/rad/urls.py
@@ -53,8 +53,12 @@ urlpatterns = [
path("sbas/", include("sbas.urls"), name="sbas"),
path("generic/", include("generic.urls"), name="generic"),
path("atlas/", include("atlas.urls"), name="atlas"),
+ path("accounts/update//", views.UpdateUserView.as_view(), name="account_update"),
+ path("accounts/update_profile//", views.UpdateUserProfileView.as_view(), name="account_profile_update"),
+ path("accounts/", views.UserListView.as_view(), name="accounts_list"),
path("accounts/", include("django.contrib.auth.urls")),
path("accounts/profile", views.profile, name="profile"),
+ path("accounts/profile//", views.account_profile, name="account_profile"),
path("", TemplateView.as_view(template_name="index.html"), name="home"),
path("cid/results//", views.cid_results, name="cid_results"),
path("cid//", views.cid_scores, name="cid_scores"),
diff --git a/rad/views.py b/rad/views.py
index 6fe17c97..500cba95 100644
--- a/rad/views.py
+++ b/rad/views.py
@@ -1,6 +1,6 @@
from atlas.models import CaseCollection, CidReportAnswer
from generic.decorators import user_is_cid_user_manager
-from generic.views import get_question_and_content_type
+from generic.views import CidManagerRequiredMixin, get_question_and_content_type
from django.core.exceptions import PermissionDenied
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.csrf import csrf_exempt
@@ -15,7 +15,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView
-from django.views.generic import ListView
+from django.views.generic import ListView, TemplateView
from django.db.models.functions import Lower
@@ -54,7 +54,7 @@ from rapids.views import GenericExamViews as RapidsExamsViews
from longs.views import GenericExamViews as LongsExamViews
from generic.forms import QuestionNoteForm
-from generic.models import CidUser, QuestionNote
+from generic.models import CidUser, QuestionNote, UserProfile
import json
@@ -71,6 +71,12 @@ def profile(request):
return render(request, "profile.html", {"user": user})
+@user_is_cid_user_manager
+def account_profile(request, slug):
+ user = get_object_or_404(User, username=slug)
+ return render(request, "profile.html", {"user": user})
+
+
def cid_selector(request):
return render(
request,
@@ -87,7 +93,7 @@ def cid_scores_admin(request, cid):
@login_required
-#@user_passes_test(lambda u: u.is_superuser)
+# @user_passes_test(lambda u: u.is_superuser)
@user_is_cid_user_manager
def cid_results(request, cid):
cid_user = CidUser.objects.filter(cid=cid).first()
@@ -432,3 +438,65 @@ def server_error(request):
# response.status_code = 404
return response
+
+
+class UserListView(CidManagerRequiredMixin, ListView):
+ model = User
+ template_name = "user_list_view.html"
+
+
+class UpdateUserView(CidManagerRequiredMixin, UpdateView):
+ model = User
+ fields = ["first_name", "last_name", "email"] # Keep listing whatever fields
+ # the combined UserProfile and User exposes.
+ template_name = "user_update.html"
+ slug_field = "username"
+ slug_url_kwarg = "slug"
+ success_url = reverse_lazy("profile")
+
+
+class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView):
+ model = UserProfile
+ fields = [
+ "supervisor_name",
+ "supervisor_email",
+ "grade",
+ "registration_number",
+ "peninsula_trainee",
+ ] # Keep listing whatever fields
+ template_name = "user_update.html"
+ slug_field = "user__username"
+ slug_url_kwarg = "slug"
+ success_url = reverse_lazy("profile")
+
+
+# class UpdateUser(TemplateView):
+#
+# user_form_class = UserForm
+# comment_form_class = UserProfileForm
+# template_name = 'user_update.html'
+# slug_field = 'username'
+# slug_url_kwarg = 'slug'
+#
+# def post(self, request):
+# post_data = request.POST or None
+# user_form = self.user_form_class(post_data, prefix='user')
+# profile_form = self.profile_form_class(post_data, prefix='userprofile')
+#
+# context = self.get_context_data(post_form=user_form,
+# profile_form=profile_form)
+#
+# if user_form.is_valid():
+# self.form_save(user_form)
+# if profile_form.is_valid():
+# self.form_save(profile_form)
+#
+# return self.render_to_response(context)
+#
+# def form_save(self, form):
+# obj = form.save()
+# #messages.success(self.request, "{} saved successfully".format(obj))
+# return obj
+#
+# def get(self, request, *args, **kwargs):
+# return self.post(request, *args, **kwargs)
diff --git a/rapids/templates/rapids/index.html b/rapids/templates/rapids/index.html
deleted file mode 100644
index 8a4b0138..00000000
--- a/rapids/templates/rapids/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends 'rapids/base.html' %}
-
-{% block content %}
- {% for exam in exams %}
-
-
- {% if exam.exam_mode %}
- {% if request.user.is_staff %}
Mark answers{% endif %}
- {% if request.user.is_staff %}
Scores{% endif %}
- {% endif %}
-
- {% endfor %}
-{% endblock %}
diff --git a/rapids/templates/rapids/index_old.html b/rapids/templates/rapids/index_old.html
deleted file mode 100755
index 35f2576a..00000000
--- a/rapids/templates/rapids/index_old.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends 'rapids/base.html' %}
-
-{% block content %}
-My Questions:
-
-
-Other Questions:
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/rapids/tests/test_urls.py b/rapids/tests/test_urls.py
index ecd6e105..a2734e62 100644
--- a/rapids/tests/test_urls.py
+++ b/rapids/tests/test_urls.py
@@ -2,11 +2,18 @@ import pytest
#from django.contrib.auth.models import User
from django.urls import reverse
+APP_NAMES = ("rapids", "anatomy", "longs", "physics", "sbas")
+
@pytest.fixture
def create_superuser(db, django_user_model):
return django_user_model.objects.create_superuser("admin", "ross@xkjq.uk", "adminpassword")
-def test_index(client, create_superuser):
+# Very basic tests, make sure we can hit key urls without error
+
+@pytest.mark.parametrize("app_name", APP_NAMES)
+def test_index(client, create_superuser, app_name):
client.login(username="admin", password="adminpassword")
- response = client.get(reverse('rapids:index'))
+ response = client.get(reverse(f'{app_name}:index'))
+ print(response.status_code)
+ print(response)
assert response.status_code == 200
\ No newline at end of file
diff --git a/sbas/templates/sbas/index.html b/sbas/templates/sbas/index.html
deleted file mode 100644
index 2af12136..00000000
--- a/sbas/templates/sbas/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends 'sbas/base.html' %}
-
-{% block content %}
-{% for exam in exams %}
-
-
- {% if request.user.is_staff %}
Scores{% endif %}
-
-{% endfor %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/profile.html b/templates/profile.html
index d43a62ed..74e28f80 100644
--- a/templates/profile.html
+++ b/templates/profile.html
@@ -2,7 +2,7 @@
{% block content %}
-
Your profile
+
Profile
Username: {{ user.username }}
@@ -13,6 +13,9 @@
Name: {{ user.first_name }}
{{ user.last_name }}
+
+ Grade: {{ user.userprofile.grade }}
+
Supervisor: {{ user.userprofile.supervisor_name }}
Supervisor Email: {{ user.userprofile.supervisor_email }}
@@ -22,5 +25,7 @@
Change password
+ Update user details
+ Update user profile
{% endblock %}
\ No newline at end of file
diff --git a/templates/user_list_view.html b/templates/user_list_view.html
new file mode 100644
index 00000000..15ef4e5e
--- /dev/null
+++ b/templates/user_list_view.html
@@ -0,0 +1,15 @@
+{% extends "generic/base.html" %}
+
+{% block content %}
+ Users
+
+ {% for user in object_list %}
+ - Username: {{ user.username }}
+ Name: {{user.first_name}} {{user.last_name}}
+ Registration number: {{user.registration_number}}
+
+
+
+ {% endfor %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/user_update.html b/templates/user_update.html
new file mode 100644
index 00000000..23864176
--- /dev/null
+++ b/templates/user_update.html
@@ -0,0 +1,9 @@
+{% extends 'generic/base.html' %}
+
+{% block content %}
+Editing user: {{object.username}}
+
+{% endblock %}
\ No newline at end of file