diff --git a/anatomy/static/css/anatomy.css b/anatomy/static/css/anatomy.css index 21226aa6..a9b6d3f8 100644 --- a/anatomy/static/css/anatomy.css +++ b/anatomy/static/css/anatomy.css @@ -846,4 +846,10 @@ summary h5 { button a, button a:link, button a:visited, button a:hover { text-decoration: none; color: inherit; +} + +details.filter { + border: 1px solid gray; + padding: 10px; + margin: 20px; } \ No newline at end of file diff --git a/anatomy/tables.py b/anatomy/tables.py index de2922a7..f32a5839 100644 --- a/anatomy/tables.py +++ b/anatomy/tables.py @@ -55,6 +55,15 @@ class AnatomyQuestionTable(tables.Table): "author", ) sequence = ("view", "image", "exams") + + def __init__(self, data=None, *args, **kwargs): + super().__init__( + data.prefetch_related( + "answers", "question_type", "body_part", "modality", "region", "examination", "exams", "author" + ), + *args, + **kwargs, + ) class AnatomyUserAnswerTable(tables.Table): select = tables.CheckBoxColumn(accessor=("pk")) delete = tables.LinkColumn( diff --git a/anatomy/templates/anatomy/anatomy_question_view.html b/anatomy/templates/anatomy/anatomy_question_view.html deleted file mode 100644 index 8bcb6824..00000000 --- a/anatomy/templates/anatomy/anatomy_question_view.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'anatomy/base.html' %} - -{% load render_table from django_tables2 %} -{% block css %} -{% endblock %} - -{% block content %} - -
-

Filter Anatomy Questions

-
- {{ filter.form }} - -
-
-{% render_table table %} - -{% endblock %} \ No newline at end of file diff --git a/anatomy/templates/anatomy/exam_list.html b/anatomy/templates/anatomy/exam_list.html deleted file mode 100644 index 4a46665e..00000000 --- a/anatomy/templates/anatomy/exam_list.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'anatomy/base.html' %} - -{% block content %} -

Examinations

-
- Active exams:
- - - Inactive exams:
- - - -
-{% endblock %} diff --git a/anatomy/views.py b/anatomy/views.py index 36f005f2..8aa151b3 100644 --- a/anatomy/views.py +++ b/anatomy/views.py @@ -902,10 +902,15 @@ def get_structure_id(request): class AnatomyQuestionView(LoginRequiredMixin, SingleTableMixin, FilterView): model = AnatomyQuestion table_class = AnatomyQuestionTable - template_name = "anatomy/view.html" + template_name = "question_table_view.html" filterset_class = AnatomyQuestionFilter + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["app_name"] = "anatomy" + return context + # def get_queryset(self): # qs = super().get_queryset() diff --git a/generic/tables.py b/generic/tables.py index 0c263935..cf929169 100644 --- a/generic/tables.py +++ b/generic/tables.py @@ -105,7 +105,7 @@ class CidUserExamTable(tables.Table): def __init__(self, data=None, *args, **kwargs): super().__init__( data.prefetch_related( - #"physics_exams", "rapid_exams", "sba_exams", "anatomy_exams", "longs_exams", + "physics_exams", "rapid_exams", "sba_exams", "anatomy_exams", "longs_exams", "group" ), *args, **kwargs, diff --git a/generic/templates/generic/cid_view.html b/generic/templates/generic/cid_view.html index cd35bd74..c648a310 100644 --- a/generic/templates/generic/cid_view.html +++ b/generic/templates/generic/cid_view.html @@ -7,11 +7,15 @@ {% block content %}
+
+

Filter CID Users

+
{{ filter.form }}
+
diff --git a/generic/templates/generic/exam_index.html b/generic/templates/generic/exam_index.html index dacfc20d..6b94e95d 100644 --- a/generic/templates/generic/exam_index.html +++ b/generic/templates/generic/exam_index.html @@ -1,7 +1,7 @@ {% extends app_name|add:'/base.html' %} {% block content %} -
+
Filter exam list @@ -15,7 +15,7 @@

Exam: {{ exam.name }}

{% if exam.exam_mode %} - {% if app_name in "rapids longs anatomy physics" %} + {% if app_name in "rapids longs anatomy" %} {% if request.user.is_staff %}Mark answers{% endif %} {% endif %} {% if request.user.is_staff %}Scores{% endif %} diff --git a/generic/views.py b/generic/views.py index 463692d7..73d14a51 100644 --- a/generic/views.py +++ b/generic/views.py @@ -1370,7 +1370,10 @@ class ExamViews(View, LoginRequiredMixin): by_question = defaultdict(dict) unmarked = set() - questions = exam.exam_questions.all().prefetch_related("answers") + if self.app_name in ("physics", "sbas"): + questions = exam.exam_questions.all() + else: + questions = exam.exam_questions.all().prefetch_related("answers") # We could prefect the CidUserAnswers.answers here (if we didn't cache them) cid_user_answers = self.CidUserAnswer.objects.select_related("question").filter( @@ -1383,7 +1386,7 @@ class ExamViews(View, LoginRequiredMixin): for cid_user_answer in cid_user_answers: # Convoluted (probably...) if cid_user_answer.user is None: - cid = cid_user_answer.cid + cid = f"c/{cid_user_answer.cid}" # cid_passcodes[cid] = cid_user_answer.passcode cids.add(cid) else: diff --git a/longs/models.py b/longs/models.py index 8ba3760b..1570ae44 100644 --- a/longs/models.py +++ b/longs/models.py @@ -150,6 +150,7 @@ class Long(models.Model): return authors def __str__(self): + return "{} / {}".format(self.pk, self.description) examinations = [series.get_examination() for series in self.series.all()] return "{}/{} : {}".format(self.pk, self.description, ", ".join(examinations)) diff --git a/longs/tables.py b/longs/tables.py index 3adfb2e1..fb629b7f 100755 --- a/longs/tables.py +++ b/longs/tables.py @@ -34,12 +34,12 @@ class LongImageColumn(tables.Column): class LongSeriesImageColumn(tables.Column): def render(self, value): - obj = value.first() + obj = value.all() - if obj is None: + if not obj: return "None" - image_object = obj.image + image_object = obj[0].image try: thumbnailer = get_thumbnailer(image_object) return format_html('
[{}]', thumbnailer["exam-list"], value.count()) @@ -73,6 +73,15 @@ class LongTable(tables.Table): fields = ("description", "history", "created_date", "author") sequence = ("view", "series", "exams") + def __init__(self, data=None, *args, **kwargs): + super().__init__( + data.prefetch_related( + "series", "author", "exams", "series__images", "series__examination", "series__plane", "series__contrast" + ), + *args, + **kwargs, + ) + class PopupLinkColumn(tables.Column): def render(self, value): return format_html("test: {}", value) @@ -111,6 +120,15 @@ class LongSeriesTable(tables.Table): fields = ("modality", "examination", "description", "author") sequence = ("view", "popup", "images", "long") + def __init__(self, data=None, *args, **kwargs): + super().__init__( + data.prefetch_related( + "long", "long__series", "images", "author", "examination", "plane", "contrast", "modality" + ), + *args, + **kwargs, + ) + def render_popup(self, value, record): print(self) return format_html("""Popup""", record.pk) diff --git a/longs/templates/longs/exam_list.html b/longs/templates/longs/exam_list.html deleted file mode 100644 index 0c052675..00000000 --- a/longs/templates/longs/exam_list.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'longs/base.html' %} - -{% block content %} -

Examinations

-
- Active exams:
-
    -{% for exam in exams %} - {% if exam.active %} -
  • - {{exam.name}} MarkScores Results Published -
  • - {% endif %} -{% endfor %} -
- - Inactive exams:
-
    -{% for exam in exams %} - {% if not exam.active %} -
  • - {{exam.name}} MarkScores Results Published -
  • - {% endif %} -{% endfor %} -
- - -
-{% endblock %} \ No newline at end of file diff --git a/physics/tables.py b/physics/tables.py index 9f300a8c..7bd70145 100644 --- a/physics/tables.py +++ b/physics/tables.py @@ -62,6 +62,15 @@ class QuestionTable(tables.Table): ) sequence = ("view", "stem", "answers", "exams") + def __init__(self, data=None, *args, **kwargs): + super().__init__( + data.prefetch_related( + "category", "author", "exams" + ), + *args, + **kwargs, + ) + def render_stem(self, value): return mark_safe(value) diff --git a/physics/templates/physics/exam_list.html b/physics/templates/physics/exam_list.html deleted file mode 100644 index af6ad227..00000000 --- a/physics/templates/physics/exam_list.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'physics/base.html' %} - -{% block content %} -

Examinations

-
- Active exams:
-
    -{% for exam in exams %} - {% if exam.active %} -
  • - {{exam.name}} Scores Results Published - Publish results: - -
  • - {% endif %} -{% endfor %} -
- - Inactive exams:
-
    -{% for exam in exams %} - {% if not exam.active %} -
  • - {{exam.name}} Scores Results Published -
  • - {% endif %} -{% endfor %} -
- -

Active exams will be available to take at the below url: - Available exams -

- - -
-{% endblock %} \ No newline at end of file diff --git a/physics/templates/physics/question_view.html b/physics/templates/physics/question_view.html deleted file mode 100644 index d06501cc..00000000 --- a/physics/templates/physics/question_view.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'physics/base.html' %} - -{% block css %} -{% endblock %} - -{% block content %} - -
-

Filter SBA Questions

-
- - {{ filter.form }} - - -
-
- {% autoescape off %} -{% load render_table from django_tables2 %} -{% render_table table %} - {% endautoescape %} - -{% endblock %} \ No newline at end of file diff --git a/physics/views.py b/physics/views.py index 00e6f585..feaa7373 100644 --- a/physics/views.py +++ b/physics/views.py @@ -426,10 +426,16 @@ class QuestionView( ): model = Question table_class = QuestionTable - template_name = "physics/question_view.html" + template_name = "question_table_view.html" filterset_class = QuestionFilter + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["app_name"] = "physics" + return context + + class UserAnswerView(AuthorOrCheckerRequiredMixin, LoginRequiredMixin, DetailView): model = CidUserAnswer diff --git a/rad/filters.py b/rad/filters.py new file mode 100644 index 00000000..352f7530 --- /dev/null +++ b/rad/filters.py @@ -0,0 +1,19 @@ + +import django_filters + + +from django.contrib.auth.models import User + +class UserListFilter(django_filters.FilterSet): + + userprofile__grade = django_filters.CharFilter(label="Grade") + + class Meta: + 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" diff --git a/rad/urls.py b/rad/urls.py index de8bdced..e308bb65 100644 --- a/rad/urls.py +++ b/rad/urls.py @@ -53,6 +53,7 @@ urlpatterns = [ path("sbas/", include("sbas.urls"), name="sbas"), path("generic/", include("generic.urls"), name="generic"), path("atlas/", include("atlas.urls"), name="atlas"), + path("accounts/bulk_create/", views.accounts_bulk_create, name="accounts_bulk_create"), 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"), diff --git a/rad/views.py b/rad/views.py index 500cba95..6d7d89b7 100644 --- a/rad/views.py +++ b/rad/views.py @@ -6,6 +6,8 @@ from django.shortcuts import render, get_object_or_404, redirect from django.views.decorators.csrf import csrf_exempt from django import forms +from django.utils.html import format_html + # from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.models import User @@ -35,6 +37,7 @@ from anatomy.models import CidUserAnswer as AnatomyCidUserAnswer from anatomy.models import Exam as AnatomyExam from anatomy.models import AnatomyQuestion from anatomy.models import Answer as AnatomyAnswer +from rad.filters import UserListFilter from rapids.models import CidUserAnswer as RapidsCidUserAnswer from rapids.models import Exam as RapidsExam @@ -54,7 +57,9 @@ 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, UserProfile +from generic.models import CidUser, QuestionNote, UserGrades, UserProfile + +from django_filters.views import FilterView import json @@ -440,10 +445,12 @@ def server_error(request): return response -class UserListView(CidManagerRequiredMixin, ListView): +class UserListView(CidManagerRequiredMixin, FilterView): model = User template_name = "user_list_view.html" + filterset_class = UserListFilter + class UpdateUserView(CidManagerRequiredMixin, UpdateView): model = User @@ -500,3 +507,92 @@ class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView): # # def get(self, request, *args, **kwargs): # return self.post(request, *args, **kwargs) + + +@user_is_cid_user_manager +def accounts_bulk_create(request): + if request.method == "POST": + context = {} + + users = json.loads(request.POST.get("usersjson")) + print(users) + + created_users = [] + existing_users = [] + error_text = "" + + try: + users = json.loads(request.POST.get("usersjson")) + print(users) + + for user in users: + if not User.objects.filter(username=user["email"]).exists(): + try: + print(user) + user_dict = { + "username": user["email"], + "first_name": user["first_name"], + "last_name": user["last_name"], + "email": user["email"], + } + new_user = User.objects.create_user(**user_dict) + except Exception as error: + error_text = ( + error_text + + f"

Error creating user: { user['email'] }
{error}

" + ) + + try: + + user_profile = UserProfile.objects.get(user=new_user) + user_profile.peninsula_trainee = True + if "supervisor_email" in user: + user_profile.supervisor_email = user["supervisor_email"] + if "supervisor_name" in user: + user_profile.supervisor_name = user["supervisor_name"] + + + if "grade" in user: + grade = UserGrades.objects.get(name=user["grade"]) + user_profile.grade = grade + + user_profile.save() + + created_users.append(user["email"]) + except Exception as error: + error_text = ( + error_text + + f"

Error creating user profile: { user['email'] }
{error}

" + ) + else: + existing_users.append(user["email"]) + + except Exception as error: + error_text = error_text + f"

Error creating users.
{error}

" + + context["error"] = format_html(error_text) + context["created_users"] = created_users + + if existing_users: + context["message"] = "The following users already exist: " + ", ".join( + existing_users + ) + # if not self.check_user_edit_access(request.user): + # data = {"status": "error, insufficient permission"} + # return JsonResponse(data, status=400) + + # exam = get_object_or_404(self.Exam, pk=pk) + + # exam.publish_results = ( + # True if request.POST.get("publish_results") == "true" else False + # ) + # exam.save() + # data = { + # "status": "success", + # "publish_results": exam.publish_results, + # "name": exam.name, + # "id": exam.id, + # } + return render(request, "accounts_bulk_create.html", context) + else: + return render(request, "accounts_bulk_create.html", {}) diff --git a/rapids/models.py b/rapids/models.py index 6904b712..bd79aea6 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -526,6 +526,8 @@ class RapidImage(models.Model): is_dicom = models.BooleanField(default=False) + + def image_tag(self): if self.image: return mark_safe( diff --git a/rapids/tables.py b/rapids/tables.py index 78846f9c..d7dd9bb8 100755 --- a/rapids/tables.py +++ b/rapids/tables.py @@ -12,12 +12,12 @@ from easy_thumbnails.exceptions import InvalidImageFormatError class ImageColumn(tables.Column): def render(self, value): - obj = value.first() + obj = value.all() - if obj is None: + if not obj: return format_html('No image') - image_object = obj.image + image_object = obj[0].image try: thumbnailer = get_thumbnailer(image_object) return format_html('', thumbnailer["exam-list"]) @@ -60,7 +60,7 @@ class RapidTable(tables.Table): def __init__(self, data=None, *args, **kwargs): super().__init__( data.prefetch_related( - "abnormality", "region", "examination", "images", "exams", "author" + "abnormality", "region", "examination", "images", "exams", "author" ), *args, **kwargs, diff --git a/rapids/templates/rapids/exam_list.html b/rapids/templates/rapids/exam_list.html deleted file mode 100644 index 3185bccd..00000000 --- a/rapids/templates/rapids/exam_list.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'rapids/base.html' %} - -{% block content %} -

Examinations

-
- Active exams:
-
    -{% for exam in exams %} - {% if exam.active %} -
  • - {{exam.name}} MarkScores Results Published -
  • - {% endif %} -{% endfor %} -
- - Inactive exams:
-
    -{% for exam in exams %} - {% if not exam.active %} -
  • - {{exam.name}} MarkScores Results Published -
  • - {% endif %} -{% endfor %} -
- - -
-{% endblock %} \ No newline at end of file diff --git a/rapids/tests/test_urls.py b/rapids/tests/test_urls.py index a2734e62..ffb93b99 100644 --- a/rapids/tests/test_urls.py +++ b/rapids/tests/test_urls.py @@ -8,12 +8,17 @@ APP_NAMES = ("rapids", "anatomy", "longs", "physics", "sbas") def create_superuser(db, django_user_model): return django_user_model.objects.create_superuser("admin", "ross@xkjq.uk", "adminpassword") +#@pytest.fixture +#def create_exam(db): +# return Exam.objects.create(name="test exam", exam_mode=True) + # 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(f'{app_name}:index')) + print(reverse(f'{app_name}:index')) print(response.status_code) - print(response) + print(response.content) assert response.status_code == 200 \ No newline at end of file diff --git a/rapids/views.py b/rapids/views.py index afd5d362..b289b520 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -504,10 +504,14 @@ def get_region_id(request): class RapidView(LoginRequiredMixin, SingleTableMixin, FilterView): model = Rapid table_class = RapidTable - template_name = "rapids/view.html" + template_name = "question_table_view.html" filterset_class = RapidFilter + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["app_name"] = "rapids" + return context @login_required diff --git a/sbas/tables.py b/sbas/tables.py index 743748b9..6134af7d 100644 --- a/sbas/tables.py +++ b/sbas/tables.py @@ -56,6 +56,15 @@ class QuestionTable(tables.Table): ) sequence = ("view", "stem", "answers", "exams") + def __init__(self, data=None, *args, **kwargs): + super().__init__( + data.prefetch_related( + "category", "exams", "author" + ), + *args, + **kwargs, + ) + def render_stem(self, value): return mark_safe(value) diff --git a/sbas/templates/sbas/exam_list.html b/sbas/templates/sbas/exam_list.html deleted file mode 100644 index 164e9e12..00000000 --- a/sbas/templates/sbas/exam_list.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends 'sbas/base.html' %} - -{% block content %} -

Examinations

-
- Active exams:
-
    -{% for exam in exams %} - {% if exam.active %} -
  • - {{exam.name}} Scores Results Published -
  • - {% endif %} -{% endfor %} -
- - Inactive exams:
-
    -{% for exam in exams %} - {% if not exam.active %} -
  • - {{exam.name}} Scores Results Published -
  • - {% endif %} -{% endfor %} -
- -

Active exams will be available to take at the below url: - Available exams -

- - -
-{% endblock %} \ No newline at end of file diff --git a/sbas/views.py b/sbas/views.py index f0db9768..1d353bbd 100644 --- a/sbas/views.py +++ b/sbas/views.py @@ -293,10 +293,15 @@ GenericExamViews = ExamViews( class QuestionView(LoginRequiredMixin, SingleTableMixin, FilterView): model = Question table_class = QuestionTable - template_name = "sbas/question_view.html" + template_name = "question_table_view.html" filterset_class = QuestionFilter + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["app_name"] = "anatomy" + return context + class UserAnswerView(LoginRequiredMixin, DetailView): model = CidUserAnswer diff --git a/templates/accounts_bulk_create.html b/templates/accounts_bulk_create.html new file mode 100644 index 00000000..46c65100 --- /dev/null +++ b/templates/accounts_bulk_create.html @@ -0,0 +1,137 @@ + +{% extends 'generic/base.html' %} + + +{% block content %} + {% if created_users %} +
+ The follow users have been created: {{created_users}} +
+ {% endif %} + + {% if error %} +
+ {{error}} +
+ {% endif %} + + {% if message %} +
+ {{message}} +
+ {% endif %} +

+ Prepare a spreadsheet with the following format. + + + + + +
First nameLast nameEmailGradeSupervisorSupervisor email
name 1last name 1email 1grade 1supervisor 1Supervisor email 1
+ + + On your spreadsheet then "copy" the data (not including header), come to this page, put focus on the <textarea> box and "paste" the text in. In some instance (large data sets) this might take a couple of seconds. + Once there simply click the button and check the users that will be created below. +

+
+ + + +
+
    +
+
+ +
+ {% csrf_token %} + + +
+ + + + +{% endblock content %} \ No newline at end of file diff --git a/templates/question_table_view.html b/templates/question_table_view.html new file mode 100644 index 00000000..89e4ba87 --- /dev/null +++ b/templates/question_table_view.html @@ -0,0 +1,23 @@ + +{% extends app_name|add:'/base.html' %} + +{% load render_table from django_tables2 %} +{% block css %} +{% endblock %} + +{% block content %} + +
+
+ +

Filter questions

+
+
+ {{ filter.form }} + +
+
+
+ {% render_table table %} + +{% endblock %} \ No newline at end of file diff --git a/templates/user_list_view.html b/templates/user_list_view.html index 15ef4e5e..1dab46bc 100644 --- a/templates/user_list_view.html +++ b/templates/user_list_view.html @@ -2,14 +2,29 @@ {% block content %}

Users

+
+ + Filter users list + +
+ {{ filter.form.as_p }} + +
+
+ {% endblock %} \ No newline at end of file