major rcr update

This commit is contained in:
Ross
2024-01-22 20:57:26 +00:00
parent bfabde9ef6
commit 6cecb8514c
29 changed files with 784 additions and 77 deletions
+3 -1
View File
@@ -6,4 +6,6 @@ __pycache__
rad/settings_local.py rad/settings_local.py
venv venv
.venv/ .venv/
.env .env
temp/
+3 -1
View File
@@ -77,7 +77,9 @@ INSTALLED_APPS = [
"django_htmx", "django_htmx",
"crispy_forms", "crispy_forms",
"crispy_bootstrap4", "crispy_bootstrap4",
"autocomplete" "autocomplete",
'markdownify.apps.MarkdownifyConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
+9 -3
View File
@@ -1,11 +1,17 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. # Register your models here.
from reversion.admin import VersionAdmin
from rcr.models import Item, Category, Level, Outcome, OutcomeReason from rcr.models import Item, RadiologyCategory, OncologyCategory, Level, Outcome, OutcomeReason
admin.site.register(Item) @admin.register(Item)
admin.site.register(Category) class ItemAdmin(VersionAdmin):
pass
#admin.site.register(Item, ItemAdmin)
admin.site.register(RadiologyCategory)
admin.site.register(OncologyCategory)
admin.site.register(Level) admin.site.register(Level)
admin.site.register(Outcome) admin.site.register(Outcome)
admin.site.register(OutcomeReason) admin.site.register(OutcomeReason)
Executable
+81
View File
@@ -0,0 +1,81 @@
import django_filters
from .models import Item
from django.contrib.auth.models import User
from django.db.models import Q
def get_rad(request):
if request is None:
return User.objects.none()
queryset = User.objects.filter(groups__name="rcr_radiology_assessor")
return queryset
def get_onc(request):
if request is None:
return User.objects.none()
queryset = User.objects.filter(groups__name="rcr_oncology_assessor")
return queryset
def get_assessor(request):
#if request is None:
# return User.objects.none()
queryset = User.objects.filter(Q(groups__name="rcr_oncology_assessor") | Q(groups__name="rcr_radiology_assessor") )
return queryset
class ItemFilter(django_filters.FilterSet):
assigned_to_radiology = django_filters.ModelMultipleChoiceFilter(
queryset=get_rad, null_label="No user"
)
assigned_to_oncology = django_filters.ModelMultipleChoiceFilter(
queryset=get_onc, null_label="No user"
)
assessed_by = django_filters.ModelMultipleChoiceFilter(
queryset=get_assessor, null_label="No user"
)
specialty = django_filters.MultipleChoiceFilter(choices=Item.SPECIALTY_CHOICES
)
class Meta:
model = Item
fields = {
"name": ["icontains"],
"description": ["icontains"],
"content_type": ["icontains"],
"created_on": ["exact"],
"specialty": ["exact"],
"targeted_to_group": ["exact"],
"assigned_to_radiology": ["exact"],
"assigned_to_oncology": ["exact"],
"assessed_by": ["exact"],
"completed_radiology": ["exact"],
"completed_oncology": ["exact"],
"needs_review": ["exact"],
}
#def __init__(
# self,
# data=None,
# queryset=None,
# prefix=None,
# strict=None,
# user=None,
# request=None,
#):
# if not request.user.groups.filter(name="rapid_checker").exists():
# queryset = queryset.filter(open_access=True) | queryset.filter(
# author__id=request.user.id
# )
# super(RapidFilter, self).__init__(
# data=data, queryset=queryset, prefix=prefix, request=request
# )
# pass
+23 -6
View File
@@ -1,4 +1,3 @@
from django import forms from django import forms
from django.contrib.admin import widgets from django.contrib.admin import widgets
from django.forms import ( from django.forms import (
@@ -12,17 +11,35 @@ from django.forms import (
CharField, CharField,
ValidationError, ValidationError,
modelformset_factory, modelformset_factory,
CheckboxSelectMultiple CheckboxSelectMultiple,
) )
from rcr.models import Item, Category, Level from rcr.models import Item, RadiologyCategory, OncologyCategory, Level
from django.contrib.auth.models import User from django.contrib.auth.models import User
class ItemForm(ModelForm): class ItemForm(ModelForm):
class Meta: class Meta:
model = Item model = Item
exclude = ["rcr_platform_id", "name", "assessed_by", "assessed_date", "assigned_to"] exclude = [
#fields = ["category", "level"] "rcr_platform_id",
"name",
"description",
"content_type",
"created_on",
"number_of_items",
"specialty",
"access_count",
"targeted_to_group",
"assessed_by",
"assessed_date",
"assigned_to_radiology",
"assigned_to_oncology",
"review_link",
"review_link_password",
]
# fields = ["category", "level"]
class AssessorAssignmentForm(Form): class AssessorAssignmentForm(Form):
items = ModelMultipleChoiceField(queryset=Item.objects.filter(assigned_to=None)) items = ModelMultipleChoiceField(queryset=Item.objects.all(), widget=forms.SelectMultiple(attrs={'size': 20}))
View File
View File
+81
View File
@@ -0,0 +1,81 @@
import csv
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from rcr.models import Item
import argparse
import csv
class DictReaderStrip(csv.DictReader):
@property
def fieldnames(self):
if self._fieldnames is None:
# Initialize self._fieldnames
# Note: DictReader is an old-style class, so can't use super()
csv.DictReader.fieldnames.fget(self)
if self._fieldnames is not None:
self._fieldnames = [name.strip() for name in self._fieldnames]
return self._fieldnames
class Command(BaseCommand):
help = "Imports the RCR spreadsheet"
def add_arguments(self, parser):
# parser.add_argument("poll_ids", nargs="+", type=int)
parser.add_argument('--file', type=argparse.FileType('r'))
def handle(self, *args, **options):
#print(options)
#path = Path(options["file"])
#with open(path) as f:
reader = DictReaderStrip(options["file"])
#reader = csv.DictReader(f)
for row in reader:
if row["Access count (all time)"] == "":
row["Access count (all time)"] = None
if row["Number of items"] == "":
row["Number of items"] = None
if row["Number of items"] == "Unknown":
row["Number of items"] = None
try:
item = Item.objects.get(rcr_platform_id=row["Unique Identifier"])
item.name=row["Name"]
item.description=row["Description"]
item.content_type=row["Content type"]
item.created_on=row["Created on"]
item.number_of_items=row["Number of items"]
item.specialty=row["Speciality"]
item.access_count=row["Access count (all time)"]
item.targeted_to_group=row["Targeted to group"]
item.review_link=row["Review link (if applicable)"]
item.review_link_password=row["Review link password (if applicable)"]
item.save()
self.stdout.write(
self.style.SUCCESS(f"Item updated: {item.pk}/{item.name}")
)
except Item.DoesNotExist:
item = Item.objects.create(
rcr_platform_id=row["Unique Identifier"],
name=row["Name"],
description=row["Description"],
content_type=row["Content type"],
created_on=row["Created on"],
number_of_items=row["Number of items"],
specialty=row["Speciality"],
access_count=row["Access count (all time)"],
targeted_to_group=row["Targeted to group"],
review_link=row["Review link (if applicable)"],
review_link_password=row["Review link password (if applicable)"],
)
self.stdout.write(
self.style.SUCCESS(f"Item created: {item.pk}/{item.name}")
)
@@ -0,0 +1,23 @@
# Generated by Django 4.1.4 on 2024-01-16 22:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rcr', '0009_item_content_type_item_created_on_item_description_and_more'),
]
operations = [
migrations.AlterField(
model_name='item',
name='number_of_items',
field=models.IntegerField(null=True),
),
migrations.AlterField(
model_name='item',
name='rcr_platform_id',
field=models.CharField(max_length=255, unique=True),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 4.1.4 on 2024-01-16 22:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rcr', '0010_alter_item_number_of_items_and_more'),
]
operations = [
migrations.AlterField(
model_name='item',
name='number_of_items',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='item',
name='unique_views_2023',
field=models.IntegerField(blank=True, null=True),
),
]
@@ -0,0 +1,28 @@
# Generated by Django 4.1.4 on 2024-01-17 23:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rcr', '0011_alter_item_number_of_items_and_more'),
]
operations = [
migrations.AddField(
model_name='item',
name='comments',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='item',
name='needs_review',
field=models.BooleanField(default=False, help_text='Check if this item needs review.'),
),
migrations.AlterField(
model_name='item',
name='targeted_to_group',
field=models.CharField(blank=True, default='Targeted', max_length=255),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2024-01-19 15:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('rcr', '0012_item_comments_item_needs_review_and_more'),
]
operations = [
migrations.RenameField(
model_name='item',
old_name='completed',
new_name='completed_radiology',
),
]
@@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2024-01-19 15:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rcr', '0013_rename_completed_item_completed_radiology'),
]
operations = [
migrations.AddField(
model_name='item',
name='completed_oncology',
field=models.BooleanField(default=False),
),
]
@@ -0,0 +1,28 @@
# Generated by Django 4.1.4 on 2024-01-22 09:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rcr', '0014_item_completed_oncology'),
]
operations = [
migrations.RenameField(
model_name='item',
old_name='unique_views_2023',
new_name='access_count',
),
migrations.AddField(
model_name='item',
name='review_link',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AddField(
model_name='item',
name='review_link_password',
field=models.CharField(blank=True, default='', max_length=255),
),
]
@@ -0,0 +1,30 @@
# Generated by Django 4.1.4 on 2024-01-22 11:02
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),
('rcr', '0015_rename_unique_views_2023_item_access_count_and_more'),
]
operations = [
migrations.RemoveField(
model_name='item',
name='assigned_to',
),
migrations.AddField(
model_name='item',
name='assigned_to_oncology',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_items_oncology', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='item',
name='assigned_to_radiology',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_items_radiology', to=settings.AUTH_USER_MODEL),
),
]
@@ -0,0 +1,17 @@
# Generated by Django 4.1.4 on 2024-01-22 20:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('rcr', '0016_remove_item_assigned_to_item_assigned_to_oncology_and_more'),
]
operations = [
migrations.RenameModel(
old_name='Category',
new_name='RadiologyCategory',
),
]
@@ -0,0 +1,31 @@
# Generated by Django 4.1.4 on 2024-01-22 20:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rcr', '0017_rename_category_radiologycategory'),
]
operations = [
migrations.CreateModel(
name='OncologyCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.RenameField(
model_name='item',
old_name='category',
new_name='radiology_category',
),
migrations.AddField(
model_name='item',
name='oncology_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rcr.oncologycategory'),
),
]
+37 -8
View File
@@ -2,9 +2,11 @@ from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import reversion
# Create your models here. # Create your models here.
@reversion.register()
class Item(models.Model): class Item(models.Model):
class LevelChoices(models.TextChoices): class LevelChoices(models.TextChoices):
NONE = "", _("None") NONE = "", _("None")
@@ -13,20 +15,29 @@ class Item(models.Model):
POST_GEN = "GEN", _("Post FRCR Generalist") POST_GEN = "GEN", _("Post FRCR Generalist")
POST_SPEC = "SPEC", _("Post FRCR Specialist") POST_SPEC = "SPEC", _("Post FRCR Specialist")
SPECIALTY_CHOICES = (
("Radiology", "Radiology"),
("Oncology", "Oncology"),
("Cross-faculty", "Cross-faculty"),
)
name = models.CharField(max_length=200, blank=True, default="") name = models.CharField(max_length=200, blank=True, default="")
description = models.TextField(blank=True, default="") description = models.TextField(blank=True, default="")
content_type = models.CharField(max_length=200, blank=True, default="") content_type = models.CharField(max_length=200, blank=True, default="")
created_on = models.DateTimeField(null=True, blank=True) created_on = models.DateTimeField(null=True, blank=True)
number_of_items = models.IntegerField(default=1) number_of_items = models.IntegerField(null=True, blank=True)
specialty = models.CharField(max_length=50,blank=True, default="") specialty = models.CharField(max_length=50,blank=True, default="")
unique_views_2023 = models.IntegerField(null=True) access_count = models.IntegerField(null=True, blank=True)
targeted_to_group = models.CharField(max_length=255, default="Targeted") targeted_to_group = models.CharField(max_length=255, default="Targeted", blank=True)
review_link = models.CharField(max_length=255, blank=True, default="")
review_link_password = models.CharField(max_length=255, blank=True, default="")
rcr_platform_id = models.CharField(max_length=255, unique=True) rcr_platform_id = models.CharField(max_length=255, unique=True)
category = models.ForeignKey("Category", on_delete=models.SET_NULL, null=True, blank=True) radiology_category = models.ForeignKey("RadiologyCategory", on_delete=models.SET_NULL, null=True, blank=True, related_name="item_radiology")
oncology_category = models.ForeignKey("OncologyCategory", on_delete=models.SET_NULL, null=True, blank=True, related_name="item_oncology")
level = models.ManyToManyField("Level", blank=True) level = models.ManyToManyField("Level", blank=True)
@@ -34,26 +45,44 @@ class Item(models.Model):
assessed_by = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="assessed_items") assessed_by = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="assessed_items")
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="assigned_items") assigned_to_radiology = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="assigned_items_radiology")
assigned_to_oncology = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="assigned_items_oncology")
outcome = models.ForeignKey("Outcome", on_delete=models.SET_NULL, blank=True, null=True) outcome = models.ForeignKey("Outcome", on_delete=models.SET_NULL, blank=True, null=True)
outcome_reason = models.ForeignKey("OutcomeReason", on_delete=models.SET_NULL, blank=True, null=True) outcome_reason = models.ForeignKey("OutcomeReason", on_delete=models.SET_NULL, blank=True, null=True)
outcome_free_text = models.CharField(max_length=255, help_text="Reason for outcome (if not available above)", default="", blank=True) outcome_free_text = models.CharField(max_length=255, help_text="Reason for outcome (if not available above)", default="", blank=True)
completed = models.BooleanField(default=False)
comments = models.TextField(blank=True)
needs_review = models.BooleanField(default=False, help_text="Check if this item needs review.")
completed_radiology = models.BooleanField(default=False)
completed_oncology = models.BooleanField(default=False)
class Meta: class Meta:
ordering = ("pk",) ordering = ("pk",)
def __str__(self): def __str__(self):
return self.name completed = ""
if self.completed_oncology:
completed = " [Completed ONC]"
if self.completed_radiology:
completed = completed + " [Completed RAD]"
return f"{self.specialty} / {self.content_type} / {self.name}{completed}"
def get_absolute_url(self): def get_absolute_url(self):
return reverse("rcr:radiology_detail_view", kwargs={"pk": self.pk}) return reverse("rcr:radiology_detail_view", kwargs={"pk": self.pk})
class Category(models.Model): class RadiologyCategory(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class OncologyCategory(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
def __str__(self): def __str__(self):
@@ -1,10 +1,26 @@
{% extends 'rcr/base.html' %} {% extends 'rcr/base.html' %}
{% load auth_extras %}
{% block content %} {% block content %}
<h2>Assign items to {{user_to_assign}}</h2> <h2>Assign items to {{user_to_assign}}</h2>
<details>
<summary>Filter</summary>
<form method="get">
{{ item_filter.form.as_p }}
<input type="submit" />
</form>
</details>
{% if request.user|has_group:"rcr_radiology_assessor" %}
Radiology
{% elif request.user|has_group:"rcr_oncology_assessor" %}
Oncology
{% endif %}
assessor.<br/>
<form action="" method="post" enctype="multipart/form-data" id="assessor-assignment-form"> <form action="" method="post" enctype="multipart/form-data" id="assessor-assignment-form">
{% csrf_token %} {% csrf_token %}
{{assessor_assignment_form}} {{assessor_assignment_form}}
+11 -2
View File
@@ -5,13 +5,22 @@
<h2>Assessors:</h2> <h2>Assessors:</h2>
<h3>Radiology</h3>
<ul> <ul>
{% for assessor in assessors %} {% for assessor in rad_assessors %}
<li> <li>
{{assessor}} <a href='{% url "rcr:radiology_assigned_user_view" assessor.pk %}'>(items to assess)</a> <a href='{% url "rcr:radiology_index_completed_by_user_view" assessor.pk %}'>(items assessed)</a> <a href='{% url "rcr:radiology_assessor_assignment_view" assessor.pk %}'>(assign items)</a> {{assessor}} <a href='{% url "rcr:radiology_assigned_user_view" assessor.pk %}'>(items to assess)</a> <a href='{% url "rcr:radiology_index_completed_by_user_view" assessor.pk %}'>(items assessed)</a> <a href='{% url "rcr:radiology_assessor_assignment_view" assessor.pk %}?assigned_to_radiology=null&specialty=Radiology&specialty=Cross-faculty'>(assign items)</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<h3>Oncology</h3>
<ul>
{% for assessor in onc_assessors %}
<li>
{{assessor}} <a href='{% url "rcr:radiology_assigned_user_view" assessor.pk %}'>(items to assess)</a> <a href='{% url "rcr:radiology_index_completed_by_user_view" assessor.pk %}'>(items assessed)</a> <a href='{% url "rcr:radiology_assessor_assignment_view" assessor.pk %}?assigned_to_oncology=null&specialty=Oncology&specialty=Cross-faculty'>(assign items)</a>
</li>
{% endfor %}
</ul>
{% endblock %} {% endblock %}
+3
View File
@@ -76,6 +76,9 @@
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'> <body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<div class="main-nav-header"> <div class="main-nav-header">
<span id="" class="top-bar-link">
<a href="{% url 'rcr:radiology_index_all_view' %}">All</a>
</span>
<span id="" class="top-bar-link"> <span id="" class="top-bar-link">
<a href="{% url 'rcr:radiology_index_view' %}">Index</a> <a href="{% url 'rcr:radiology_index_view' %}">Index</a>
</span> </span>
+39 -13
View File
@@ -2,13 +2,14 @@
{% block content %} {% block content %}
{% if assessor %} {% if assessor %}
Assessor: {{assessor}} Assessor: {{assessor}}
{% endif %} {% endif %}
{% include 'rcr/item_filter.html' %}
{% if completed %} {% if completed %}
<h2>Completed items:</h2> <h2>Completed items:</h2>
@@ -24,25 +25,28 @@
{% endif %} {% endif %}
<ul> <table>
{% for item in items %} {% for item in items %}
<tr class="{{item.content_type}} {% if item.completed_radiology %} completed-radiology{% endif %}{% if item.completed_radiology %} completed-oncology{% endif %}">
{% if item.completed %} <td class="{{item.specialty}}">
<li class="completed"> {{forloop.counter}}:
{% endif %} </td>
{{forloop.counter}}: <td> {{item.content_type}}</td>
{{item.name}} / {{item.rcr_platform_id}} <a href="{% url 'rcr:radiology_detail_view' item.pk %}">view</a> <a href="{% url 'rcr:radiology_update_view' item.pk %}">edit</a> <td>
</li> {{item.name}}
</td>
<td><a href="{% url 'rcr:radiology_detail_view' item.pk %}">view</a> <a href="{% url 'rcr:radiology_update_view' item.pk %}">edit</a>
</td>
{% empty %} {% empty %}
No items found. No items found.
</tr>
{% endfor %} {% endfor %}
</ul>
</table>
<a href="{% url 'rcr:radiology_index_view' %}">Todo</a> <a href="{% url 'rcr:radiology_index_view' %}">Todo</a>
<a href="{% url 'rcr:radiology_index_completed_view' %}">Done</a> <a href="{% url 'rcr:radiology_index_completed_view' %}">Done</a>
{% endblock %} {% endblock %}
@@ -52,6 +56,28 @@
.completed::after { .completed::after {
content: '✓'; content: '✓';
color: green; color: green;
}; }
.Radiology::before {
content: "R";
}
.Radiology {
color: green;
}
.Oncology::before {
content: "O";
}
.Oncology {
color: blue;
}
.Cross-faculty::before {
content: "C";
}
.Cross-faculty {
color: purple;
}
td {
padding-right: 10px;
}
</style> </style>
{% endblock css %} {% endblock css %}
+21 -4
View File
@@ -1,8 +1,11 @@
{% extends 'rcr/base.html' %} {% extends 'rcr/base.html' %}
{% load markdownify %}
{% block content %} {% block content %}
<h2>Item: {{object.name}}</h2> <h2>Item: {{object.name}}</h2>
{% include "rcr/item_rcr_details.html" %}
<p>Category: {{object.category}}</p> <p>Radiology Category: {{object.radiology_category}}</p>
<p>Oncology Category: {{object.oncology_category}}</p>
<p> <p>
Level(s): Level(s):
@@ -22,7 +25,10 @@
Date assessed: {{object.assessed_date}} Date assessed: {{object.assessed_date}}
</p> </p>
<p> <p>
Assigned to: {{object.assigned_to}} Assigned to (radiology): {{object.assigned_to_radiology}}
</p>
<p>
Assigned to (oncology): {{object.assigned_to_oncology}}
</p> </p>
<p> <p>
@@ -32,11 +38,22 @@
{% endfor %} {% endfor %}
</p> </p>
<p> <p>
Completed: {{object.completed}} Completed (radiology): {{object.completed_radiology}}
</p> </p>
<p>
Completed (oncology): {{object.completed_oncology}}
</p>
<p><a href='{% url "rcr:radiology_update_view" object.pk %}'>Edit</a></p> <p><a href='{% url "rcr:radiology_update_view" object.pk %}'>Edit</a></p>
<details><summary> </summary> <details><summary> </summary>
</details> </details>
{% endblock %} {% endblock %}
{% block css %}
<style>
pre {
white-space: pre-wrap;
}
</style>
{% endblock css %}
+13
View File
@@ -0,0 +1,13 @@
{% if item_filter %}
<details>
<summary>
Filters
</summary>
<form method="get">
{{ item_filter.form.as_p }}
<input type="submit" />
</form>
</details>
{% endif %}
+67
View File
@@ -0,0 +1,67 @@
<div class="float-end opacity-25">{{object.rcr_platform_id}}</div>
{% load markdownify %}
{% if item.content_type == "Web link" or item.content_type == "Video" or item.content_type == "Audio" or item.content_type == "PowerPoint" or item.content_type == "Course" %}
<p>Link: <a href="https://cr.rcrlearning.org/#/catalogue/landingpage/item/{{object.rcr_platform_id}}/" title="Link to content on learning hub">click here</a></p>
{% elif item.content_type == "Curriculum" %}
<p>Link: <a href="https://cr.rcrlearning.org/#/catalogue/item/{{object.rcr_platform_id}}/" title="Link to content on learning hub">click here</a></p>
{% else %}
<p>Unknown content type!</p>
{% endif %}
{% if item.review_link %}
<p>Review link: {{item.review_link}}
{% if item.password %}
<br/>Password: {{item.password}}
{% endif %}
</p>
{% endif %}
<p>
<b>Description</b><br/>
<pre> {{object.description|markdownify}}</pre>
</p>
<p>
<details>
<summary>
<b>Description (raw)</b><br/>
</summary>
<pre> {{object.description}}</pre>
</details>
</p>
<div class="d-flex flex-row mb-3 flex-wrap">
<div class="p-2">
<b>Content type</b><br/>
{{object.content_type}}
</div>
<div class="p-2">
<b>Created on</b><br/>
{{object.created_on}}
</div>
<div class="p-2">
<b>Number of items</b><br/>
{{object.number_of_items}}
</div>
<div class="p-2">
<b>Speciality</b><br/>
{{object.specialty}}
</div>
<div class="p-2">
<b>Access count</b><br/>
{{object.access_count}}
</div>
<div class="p-2">
<b>Targeted to group</b><br/>
{{object.targeted_to_group}}
</div>
</div>
+15 -5
View File
@@ -5,18 +5,28 @@
{% comment %} {% if request.user.is_superuser %} {% comment %} {% if request.user.is_superuser %}
(<a href="{% url 'admin:auth_user_change' object.id %}" title="Edit the user using the admin interface">Admin Edit</a>) (<a href="{% url 'admin:auth_user_change' object.id %}" title="Edit the user using the admin interface">Admin Edit</a>)
{% endif %} {% endcomment %} {% endif %} {% endcomment %}
<p>Link: <a href="" title="Link to content on learning hub">click here</a></p>
{% include "rcr/item_rcr_details.html" %}
<form method="post">{% csrf_token %} <form method="post">{% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<input type="submit" value="Update"> <input type="submit" value="Update">
{% if skip == 0 or skip %}
<button> <a href="{% url 'rcr:radiology_update_next_skip_view' skip|add:1 %}">Skip</a> </button>
{% endif %}
</form> </form>
{% if skip == 0 or skip %}
<a href="{% url 'rcr:radiology_update_next_skip_view' skip|add:1 %}"><button>Skip</button></a>
{% endif %}
<details><summary> </summary> <details><summary> </summary>
</details> </details>
{% endblock %} {% endblock %}
{% block css %}
<style>
pre {
white-space: pre-wrap;
}
</style>
{% endblock css %}
+47 -12
View File
@@ -1,5 +1,3 @@
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.conf import settings from django.conf import settings
@@ -12,13 +10,50 @@ app_name = "rcr"
urlpatterns = [ urlpatterns = [
path("radiology/", views.radiology_index, name="radiology_index_view"), path("radiology/", views.radiology_index, name="radiology_index_view"),
path("radiology/completed", views.radiology_index_completed, name="radiology_index_completed_view"), path("radiology/all", views.radiology_index_all, name="radiology_index_all_view"),
path("radiology/assessors", views.radiology_assessors, name="radiology_assessors_view"), path(
path("radiology/assigned/user/<int:user_pk>", views.radiology_assigned_user, name="radiology_assigned_user_view"), "radiology/completed",
path("radiology/assessors/<int:assessor_pk>/assignment", views.radiology_assessor_assignment, name="radiology_assessor_assignment_view"), views.radiology_index_completed,
path("radiology/completed/user/<int:user_pk>", views.radiology_index_completed_by_user, name="radiology_index_completed_by_user_view"), name="radiology_index_completed_view",
path("radiology/item/<int:pk>/", views.ItemDetailView.as_view(), name="radiology_detail_view"), ),
path("radiology/item/next/update", views.ItemUpdateNextView.as_view(), name="radiology_update_next_view"), path(
path("radiology/item/next/update/skip/<int:skip>", views.ItemUpdateNextView.as_view(), name="radiology_update_next_skip_view"), "radiology/assessors",
path("radiology/item/<int:pk>/update", views.ItemUpdateView.as_view(), name="radiology_update_view"), views.radiology_assessors,
] name="radiology_assessors_view",
),
path(
"radiology/assigned/user/<int:user_pk>",
views.radiology_assigned_user,
name="radiology_assigned_user_view",
),
path(
"radiology/assessors/<int:assessor_pk>/assignment",
views.radiology_assessor_assignment,
name="radiology_assessor_assignment_view",
),
path(
"radiology/completed/user/<int:user_pk>",
views.radiology_index_completed_by_user,
name="radiology_index_completed_by_user_view",
),
path(
"radiology/item/<int:pk>/",
views.ItemDetailView.as_view(),
name="radiology_detail_view",
),
path(
"radiology/item/next/update",
views.ItemUpdateNextView.as_view(),
name="radiology_update_next_view",
),
path(
"radiology/item/next/update/skip/<int:skip>",
views.ItemUpdateNextView.as_view(),
name="radiology_update_next_skip_view",
),
path(
"radiology/item/<int:pk>/update",
views.ItemUpdateView.as_view(),
name="radiology_update_view",
),
]
+97 -21
View File
@@ -10,6 +10,8 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.db.models import Q
from rcr.filters import ItemFilter
from rcr.models import Item from rcr.models import Item
from rcr.forms import AssessorAssignmentForm, ItemForm from rcr.forms import AssessorAssignmentForm, ItemForm
@@ -20,7 +22,7 @@ from django.core.exceptions import PermissionDenied
def user_is_rcr(function): def user_is_rcr(function):
def wrap(request, *args, **kwargs): def wrap(request, *args, **kwargs):
if ( if (
request.user.groups.filter(name="rcr_radiology_assessor").exists() request.user.groups.filter(Q(name="rcr_radiology_assessor") | Q(name="rcr_oncology_assessor")).exists()
or request.user.is_superuser or request.user.is_superuser
): ):
return function(request, *args, **kwargs) return function(request, *args, **kwargs)
@@ -31,12 +33,16 @@ def user_is_rcr(function):
wrap.__name__ = function.__name__ wrap.__name__ = function.__name__
return wrap return wrap
def user_is_radiology(user):
return user.groups.filter(name="rcr_radiology_assessor").exists()
def user_is_oncology(user):
return user.groups.filter(name="rcr_oncology_assessor").exists()
class RCRRequiredMixin(object): class RCRRequiredMixin(object):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs) obj = super().get_object(*args, **kwargs)
if ( if (
self.request.user.groups.filter(name="rcr_radiology_assessor").exists() self.request.user.groups.filter(Q(name="rcr_radiology_assessor") | Q(name="rcr_oncology_assessor")).exists()
or self.request.user.is_superuser or self.request.user.is_superuser
): ):
return obj return obj
@@ -45,66 +51,121 @@ class RCRRequiredMixin(object):
return obj return obj
@user_is_rcr
def radiology_index_all(request):
item_filter = ItemFilter(request.GET, queryset=Item.objects.all())
return render(
request,
"rcr/index.html",
{
"items": item_filter.qs,
"completed": False,
"assessor": None,
"item_filter": item_filter,
},
)
@user_is_rcr @user_is_rcr
def radiology_index(request, assigned=True, user_pk: int | None = None): def radiology_index(request, assigned=True, user_pk: int | None = None):
if assigned: assessor = None
items = Item.objects.filter(assigned_to=request.user).exclude(completed=True) if user_pk is not None:
elif user_pk is not None: assessor = User.objects.get(pk=user_pk)
items = Item.objects.filter(assigned_to__pk=user_pk).exclude(completed=True) elif assigned:
assessor = request.user
if assessor is None:
items = Item.objects.all().exclude(completed_radiology=True).exclude(completed_oncology=True)
elif assessor.groups.filter(name="rcr_radiology_assessor").exists():
items = Item.objects.filter(assigned_to_radiology=assessor).exclude(completed_radiology=True)
elif assessor.groups.filter(name="rcr_oncology_assessor").exists():
items = Item.objects.filter(assigned_to_oncology=assessor).exclude(completed_oncology=True)
else: else:
items = Item.objects.all().exclude(completed=True) raise Http404
return render( return render(
request, request,
"rcr/index.html", "rcr/index.html",
{ {
"items": items, "items": items,
"completed": False "completed": False,
"assessor": assessor
}, },
) )
@user_passes_test(lambda u: u.is_superuser) #def radiology_assessor_assignment_rad(request, assessor_pk: int):
def radiology_assessor_assignment(request, assessor_pk: int): # return radiology_assessor_assignment(request, assessor_pk, "rad")
#def radiology_assessor_assignment_onc(request, assessor_pk: int):
# return radiology_assessor_assignment(request, assessor_pk, "onc")
#def radiology_assessor_assignment_cross(request, assessor_pk: int):
# return radiology_assessor_assignment(request, assessor_pk, "cross")
@user_is_rcr
def radiology_assessor_assignment(request, assessor_pk: int, specialty=None):
item_filter = ItemFilter(request.GET, queryset=Item.objects.all())
user = get_object_or_404(User, pk=assessor_pk) user = get_object_or_404(User, pk=assessor_pk)
if request.method == "POST": if request.method == "POST":
assessor_assignment_form = AssessorAssignmentForm(request.POST) assessor_assignment_form = AssessorAssignmentForm(request.POST)
assessor_assignment_form.fields["items"].queryset = item_filter.qs
print(assessor_assignment_form)
if assessor_assignment_form.is_valid(): if assessor_assignment_form.is_valid():
items_to_update = assessor_assignment_form.cleaned_data["items"] items_to_update = assessor_assignment_form.cleaned_data["items"]
for item in items_to_update:
item.assigned_to = user
Item.objects.filter(pk__in=items_to_update).bulk_update(items_to_update, ["assigned_to"])
for item in items_to_update:
if user_is_radiology(user):
item.assigned_to_radiology = user
elif user_is_oncology(user):
item.assigned_to_oncology = user
else:
raise Http404
if user_is_radiology(user):
Item.objects.filter(pk__in=items_to_update).bulk_update(items_to_update, ["assigned_to_radiology"])
elif user_is_oncology(user):
Item.objects.filter(pk__in=items_to_update).bulk_update(items_to_update, ["assigned_to_oncology"])
else:
raise Http404
return HttpResponseRedirect(reverse("rcr:radiology_assessors_view")) return HttpResponseRedirect(reverse("rcr:radiology_assessors_view"))
else: else:
assessor_assignment_form = AssessorAssignmentForm() assessor_assignment_form = AssessorAssignmentForm()
assessor_assignment_form.fields["items"].queryset = item_filter.qs
return render( return render(
request, request,
"rcr/assessor_assignment_form.html", "rcr/assessor_assignment_form.html",
{ {
"assessor_assignment_form": assessor_assignment_form, "assessor_assignment_form": assessor_assignment_form,
"user_to_assign": user "user_to_assign": user,
"item_filter": item_filter
}, },
) )
@user_is_rcr @user_is_rcr
def radiology_assigned_user(request, user_pk=int): def radiology_assigned_user(request, user_pk=int):
return radiology_index(request, user_pk=user_pk) return radiology_index(request, user_pk=user_pk)
pass
@user_is_rcr @user_is_rcr
def radiology_assessors(request): def radiology_assessors(request):
assessors = User.objects.filter(groups__name="rcr_radiology_assessor") rad_assessors = User.objects.filter(groups__name="rcr_radiology_assessor")
onc_assessors = User.objects.filter(groups__name="rcr_oncology_assessor")
return render( return render(
request, request,
"rcr/assessors.html", "rcr/assessors.html",
{ {
"assessors": assessors "rad_assessors": rad_assessors,
"onc_assessors": onc_assessors,
}, },
) )
@@ -117,10 +178,13 @@ def radiology_index_completed(request, user_pk=None):
assessor = None assessor = None
if user_pk is not None: if user_pk is not None:
items = Item.objects.filter(completed=True, assessed_by=user_pk).order_by("pk")
assessor = User.objects.get(pk=user_pk) assessor = User.objects.get(pk=user_pk)
if assessor.groups.filter(name="rcr_radiology_assessor").exists():
items = Item.objects.filter(completed_radiology=True, assessed_by=user_pk).order_by("pk")
elif assessor.groups.filter(name="rcr_oncology_assessor").exists():
items = Item.objects.filter(completed_oncology=True, assessed_by=user_pk).order_by("pk")
else: else:
items = Item.objects.filter(completed=True) items = Item.objects.filter(Q(completed_radiology=True) | Q(completed_oncology=True))
return render( return render(
request, request,
@@ -175,11 +239,23 @@ class ItemUpdateNextView(ItemUpdateBase, RCRRequiredMixin):
return self.render_to_response(context) return self.render_to_response(context)
def get_object(self): def get_object(self):
user = self.request.user
if self.skip < 1: if self.skip < 1:
obj = Item.objects.filter(completed=False).order_by("pk").first() if user.groups.filter(name="rcr_radiology_assessor").exists():
obj = Item.objects.filter(assigned_to_radiology=user, completed_radiology=False).order_by("pk").first()
elif user.groups.filter(name="rcr_oncology_assessor").exists():
obj = Item.objects.filter(assigned_to_oncology=user, completed_oncology=False).order_by("pk").first()
else:
raise Http404
else: else:
try: try:
obj = Item.objects.filter(completed=False)[self.skip] if user.groups.filter(name="rcr_radiology_assessor").exists():
obj = Item.objects.filter(assigned_to_radiology=user, completed_radiology=False).order_by("pk")[self.skip]
elif user.groups.filter(name="rcr_oncology_assessor").exists():
obj = Item.objects.filter(assigned_to_oncology=user, completed_oncology=False).order_by("pk")[self.skip]
else:
raise Http404
#obj = Item.objects.filter(completed=False)[self.skip]
except IndexError: except IndexError:
raise Http404 raise Http404
+2 -1
View File
@@ -41,4 +41,5 @@ ipython
django-htmx-autocomplete django-htmx-autocomplete
faker faker
pytest-faker pytest-faker
blake3 blake3
django-markdownify