Improvements to user management
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
# Current target
|
||||||
|
- User management
|
||||||
|
|
||||||
|
|
||||||
|
# Ongoing
|
||||||
|
|
||||||
|
- Improve documentation
|
||||||
|
- Test coverage
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 09:44
|
||||||
|
|
||||||
|
import anatomy.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0066_remove_exam_stats_graph'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='anatomyquestion',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(help_text="The image to use for the question. Ideally use use unmarked images and annotate (arrow) them on the test system. If you wish to reuse an image that is already uploaded 'clone' the question that contains it.", upload_to=anatomy.models.image_directory_path),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -858,4 +858,16 @@ details.filter {
|
|||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic default styling for forms */
|
||||||
|
|
||||||
|
/* Highlight form errors */
|
||||||
|
tr:has(.errorlist){
|
||||||
|
border: 1px dashed red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorlist + input {
|
||||||
|
background-color: red;
|
||||||
|
color: darkblue;
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="floating-header">
|
<div class="floating-header">
|
||||||
<a href="{% url 'atlas:finding_update' pk=finding.pk %}" title="Edit the Finding">Edit</a>
|
<a href="{% url 'atlas:finding_update' pk=finding.pk %}" title="Edit the Finding">Edit</a>
|
||||||
<a href="{% url 'atlas:finding_delete' pk=finding.pk %}" title="Delete the Finding">Delete</a>
|
<a href="{% url 'atlas:finding_delete' pk=finding.pk %}" title="Delete the Finding">Delete</a>
|
||||||
|
|||||||
+4
-1
@@ -15,13 +15,15 @@ from .models import (
|
|||||||
CidUserExam,
|
CidUserExam,
|
||||||
UserGrades,
|
UserGrades,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
Supervisor,
|
||||||
|
Site
|
||||||
)
|
)
|
||||||
|
|
||||||
# from .models import Examination, Sign, Site, Condition
|
# from .models import Examination, Sign, Site, Condition
|
||||||
|
|
||||||
admin.site.register(Examination)
|
admin.site.register(Examination)
|
||||||
admin.site.register(Sign)
|
admin.site.register(Sign)
|
||||||
# admin.site.register(Site)
|
admin.site.register(Site)
|
||||||
admin.site.register(Condition)
|
admin.site.register(Condition)
|
||||||
admin.site.register(Plane)
|
admin.site.register(Plane)
|
||||||
admin.site.register(Contrast)
|
admin.site.register(Contrast)
|
||||||
@@ -32,3 +34,4 @@ admin.site.register(CidUserExam)
|
|||||||
admin.site.register(CidUserGroup)
|
admin.site.register(CidUserGroup)
|
||||||
admin.site.register(UserProfile)
|
admin.site.register(UserProfile)
|
||||||
admin.site.register(UserGrades)
|
admin.site.register(UserGrades)
|
||||||
|
admin.site.register(Supervisor)
|
||||||
|
|||||||
+21
-2
@@ -6,12 +6,15 @@ from django.forms import (
|
|||||||
ChoiceField,
|
ChoiceField,
|
||||||
CharField,
|
CharField,
|
||||||
HiddenInput,
|
HiddenInput,
|
||||||
|
EmailField,
|
||||||
|
IntegerField
|
||||||
|
|
||||||
)
|
)
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
from atlas.models import CaseCollection
|
from atlas.models import CaseCollection
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote, UserProfile, UserUserGroup
|
from generic.models import CidUser, CidUserGroup, Examination, QuestionNote, Supervisor, UserProfile, UserUserGroup
|
||||||
|
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
from django.forms.widgets import RadioSelect, TextInput, Textarea
|
||||||
@@ -258,4 +261,20 @@ class UserUserGroupForm(ModelForm):
|
|||||||
#class UserProfileForm(ModelForm):
|
#class UserProfileForm(ModelForm):
|
||||||
# class Meta:
|
# class Meta:
|
||||||
# model = UserProfile
|
# model = UserProfile
|
||||||
# fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"]
|
# fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"]
|
||||||
|
|
||||||
|
class UserUserForm(Form):
|
||||||
|
username = EmailField(required=True, help_text="Username / email should be the same. Ideally this should be an nhs email.")
|
||||||
|
first_name = CharField(max_length=255, required=True)
|
||||||
|
last_name = CharField(max_length=255, required=True)
|
||||||
|
grade = IntegerField(min_value=1, max_value=6, required=False)
|
||||||
|
supervisor = CharField(max_length=255, required=False) # Needs to be a user/object ref
|
||||||
|
#
|
||||||
|
# class Meta:
|
||||||
|
#
|
||||||
|
# fields = ("username", "first_name", "last_name")
|
||||||
|
|
||||||
|
class SupervisorForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Supervisor
|
||||||
|
exclude = ("",)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 09:44
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('generic', '0036_ciduserexam_results_emailed_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Supervisor',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254)),
|
||||||
|
('name', models.CharField(blank=True, max_length=255)),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('user', models.OneToOneField(blank=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 13:18
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('generic', '0037_supervisor'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 13:19
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('generic', '0038_alter_supervisor_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 13:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('generic', '0039_alter_supervisor_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, max_length=254, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 13:55
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('generic', '0040_alter_supervisor_email'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='site',
|
||||||
|
name='initials',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='site',
|
||||||
|
name='site',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='site',
|
||||||
|
name='full_name',
|
||||||
|
field=models.CharField(blank=True, help_text='Name of the site', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='site',
|
||||||
|
name='short_code',
|
||||||
|
field=models.CharField(blank=True, help_text='Shortcode/name of the site', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.site'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, help_text='The (nhs.net) email address of the supervisor', max_length=254, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(blank=True, help_text='Name of the supervisor', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supervisor',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(blank=True, help_text='If the supervisor has an account on the test system that can be associated here', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 14:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('generic', '0041_auto_20221114_1355'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='supervisor',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.supervisor'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2022-11-14 14:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('generic', '0042_userprofile_supervisor'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='supervisor',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='trainee', to='generic.supervisor'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+29
-4
@@ -56,11 +56,13 @@ class Examination(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Site(models.Model):
|
class Site(models.Model):
|
||||||
site = models.CharField(max_length=200)
|
"""Model to hold site details"""
|
||||||
initials = models.CharField(max_length=200)
|
full_name = models.CharField(max_length=255, blank=True, help_text="Name of the site")
|
||||||
|
short_code = models.CharField(max_length=255, blank=True, help_text="Shortcode/name of the site")
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.site
|
return self.short_code
|
||||||
|
|
||||||
|
|
||||||
class Condition(tagulous.models.TagModel):
|
class Condition(tagulous.models.TagModel):
|
||||||
@@ -667,6 +669,7 @@ class UserProfile(models.Model):
|
|||||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
supervisor_email = models.EmailField(blank=True)
|
supervisor_email = models.EmailField(blank=True)
|
||||||
supervisor_name = models.CharField(max_length=100, blank=True)
|
supervisor_name = models.CharField(max_length=100, blank=True)
|
||||||
|
supervisor = models.ForeignKey("Supervisor", on_delete=models.SET_NULL, blank=True, null=True, related_name="trainee")
|
||||||
registration_number = models.CharField(max_length=25, 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)
|
grade = models.ForeignKey(UserGrades, null=True, blank=True, help_text="User grade", on_delete=models.CASCADE)
|
||||||
peninsula_trainee = models.BooleanField(default=False)
|
peninsula_trainee = models.BooleanField(default=False)
|
||||||
@@ -675,7 +678,28 @@ class UserProfile(models.Model):
|
|||||||
def getusername(self):
|
def getusername(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Userprofile {self.user}"
|
||||||
|
|
||||||
username = property(getusername)
|
username = property(getusername)
|
||||||
|
|
||||||
|
class Supervisor(models.Model):
|
||||||
|
"""Model to hold individual supervisor details (email and name)
|
||||||
|
|
||||||
|
This can be linked with a user account.
|
||||||
|
"""
|
||||||
|
email = models.EmailField(blank=True, unique=True, help_text="The (nhs.net) email address of the supervisor")
|
||||||
|
name = models.CharField(max_length=255, blank=True, help_text="Name of the supervisor")
|
||||||
|
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True, help_text="If the supervisor has an account on the test system that can be associated here")
|
||||||
|
site = models.ForeignKey("Site", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.name} ({self.email})"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('generic:supervisor_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
@@ -685,4 +709,5 @@ def create_user_profile(sender, instance, created, **kwargs):
|
|||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def save_user_profile(sender, instance, **kwargs):
|
def save_user_profile(sender, instance, **kwargs):
|
||||||
instance.userprofile.save()
|
instance.userprofile.save()
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="{% url 'generic:cid_group_view' %}">Cid Groups</a> /
|
<a href="{% url 'generic:cid_group_view' %}">Cid Groups</a> /
|
||||||
<a href="{% url 'generic:user_group_view' %}">User Groups</a> /
|
<a href="{% url 'generic:user_group_view' %}">User Groups</a> /
|
||||||
<a href="{% url 'accounts_list' %}">Manage Users</a> /
|
<a href="{% url 'accounts_list' %}">Manage Users</a> /
|
||||||
|
<a href="{% url 'generic:supervisor' %}">Manage Supervisors</a>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -91,13 +91,13 @@
|
|||||||
<summary>Email results</summary>
|
<summary>Email results</summary>
|
||||||
User results emailed: {{exam.exam_results_emailed|default:"Never"}}<br/>
|
User results emailed: {{exam.exam_results_emailed|default:"Never"}}<br/>
|
||||||
<button id="email-results-button"
|
<button id="email-results-button"
|
||||||
title="Email results to users and their supervisors"
|
title="Email results to users and their supervisor"
|
||||||
hx-get="{% url exam.app_name|add:':exam_report_email' exam_id=exam.pk %}"
|
hx-get="{% url exam.app_name|add:':exam_report_email' exam_id=exam.pk %}"
|
||||||
hx-target="#user-details"
|
hx-target="#user-details"
|
||||||
hx-confirm="This will email results, please make sure scores have been refreshed before continuing"
|
hx-confirm="This will email results, please make sure scores have been refreshed before continuing"
|
||||||
hx-prompt="Please enter an additional email to send to (user and supervisor will automatically be used if available) if required"
|
hx-prompt="Please enter an additional email to send to (user and supervisor will automatically be used if available) if required"
|
||||||
>Email user results</button>
|
>Email user results</button>
|
||||||
<button id="email-unsent-results-button" title="Email results to users and their supervisors"
|
<button id="email-unsent-results-button" title="Email results to users and their supervisor"
|
||||||
hx-get="{% url exam.app_name|add:':exam_report_email_unsent' exam_id=exam.pk %}"
|
hx-get="{% url exam.app_name|add:':exam_report_email_unsent' exam_id=exam.pk %}"
|
||||||
hx-target="#user-details"
|
hx-target="#user-details"
|
||||||
hx-confirm="This will email results, please make sure scores have been refreshed before continuing"
|
hx-confirm="This will email results, please make sure scores have been refreshed before continuing"
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'generic/base.html' %}
|
||||||
|
|
||||||
|
{% block navigation %}
|
||||||
|
{{block.super}}
|
||||||
|
<br/>
|
||||||
|
Supervisors: <a href="{% url 'generic:supervisor' %}">View all</a>
|
||||||
|
<a href="{% url 'generic:supervisor_create' %}">Create new</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'generic/supervisor_base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Supervisor: {{object.name}}</h2>
|
||||||
|
{{object.email}}
|
||||||
|
|
||||||
|
<br/>User account: {{object.user}}
|
||||||
|
<br/>Site: {{object.site}}
|
||||||
|
<br/>Trainee(s): {% for trainee in object.trainee.all %}
|
||||||
|
<a href="{% url 'account_profile' trainee.user.username %}">{{trainee.user}}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'generic:supervisor_edit' pk=object.pk %}"> Edit</a>
|
||||||
|
<a href="{% url 'generic:supervisor_delete' pk=object.pk %}"> Delete</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td, th { padding-left: 10px }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends "generic/supervisor_base.html" %}
|
||||||
|
<!-- {% load static from static %} -->
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
|
||||||
|
{{form.media}}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- {{ form.media }} -->
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Edit Supervisor / {{object.cid}}</h2>
|
||||||
|
Use this form to create / edit a supervisor
|
||||||
|
{% if errors %}
|
||||||
|
<div class="alert alert-info" role="alert">{{errors}}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" class="submit-button" value="Submit" name="submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'generic/supervisor_base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>Supervisors</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for supervisor in object_list %}
|
||||||
|
<li><a href="{% url 'generic:supervisor_detail' pk=supervisor.pk %}">Supervisor: {{supervisor.name}}</a>
|
||||||
|
<br/>{{supervisor.email}}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<style>
|
||||||
|
td, th { padding-left: 10px }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "generic/base.html" %}
|
||||||
|
<!-- {% load static from static %} -->
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
|
||||||
|
{{form.media}}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- {{ form.media }} -->
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Edit User / {{ciduser.cid}}</h2>
|
||||||
|
Use this form to create a user.
|
||||||
|
{% if errors %}
|
||||||
|
<div class="alert alert-info" role="alert">{{errors}}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" class="submit-button" value="Submit" name="submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -92,6 +92,11 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
|
path("cids/create", views.manage_cid_users, name="manage_cid_users"),
|
||||||
path("cids/create/email", views.create_cid_email, name="create_cid_email"),
|
path("cids/create/email", views.create_cid_email, name="create_cid_email"),
|
||||||
|
path("supervisor", views.SupervisorList.as_view(), name="supervisor"),
|
||||||
|
path("supervisor/<int:pk>", views.SupervisorDetail.as_view(), name="supervisor_detail"),
|
||||||
|
path("supervisor/<int:pk>/edit", views.SupervisorUpdate.as_view(), name="supervisor_edit"),
|
||||||
|
path("supervisor/create", views.SupervisorCreate.as_view(), name="supervisor_create"),
|
||||||
|
path("supervisor/<int:pk>/delete", views.SupervisorDelete.as_view(), name="supervisor_delete"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+87
-2
@@ -1,5 +1,6 @@
|
|||||||
from collections import defaultdict, Counter
|
from collections import defaultdict, Counter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import secrets
|
||||||
import statistics
|
import statistics
|
||||||
import threading
|
import threading
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
@@ -35,6 +36,8 @@ import urllib
|
|||||||
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||||
|
from django.views.generic.list import ListView
|
||||||
|
from django.views.generic.detail import DetailView
|
||||||
from django_filters.views import FilterView
|
from django_filters.views import FilterView
|
||||||
from django_filters import FilterSet, OrderingFilter
|
from django_filters import FilterSet, OrderingFilter
|
||||||
from django_tables2.views import SingleTableMixin
|
from django_tables2.views import SingleTableMixin
|
||||||
@@ -49,10 +52,12 @@ from .forms import (
|
|||||||
CidUserForm,
|
CidUserForm,
|
||||||
ExaminationForm,
|
ExaminationForm,
|
||||||
CidUserGroupForm,
|
CidUserGroupForm,
|
||||||
|
SupervisorForm,
|
||||||
|
UserUserForm,
|
||||||
UserUserGroupForm,
|
UserUserGroupForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import CidUser, CidUserGroup, Examination, QuestionNote, UserUserGroup, get_next_cid
|
from .models import CidUser, CidUserGroup, Examination, QuestionNote, Supervisor, UserGrades, UserProfile, UserUserGroup, get_next_cid
|
||||||
|
|
||||||
from rapids.models import Rapid as RapidQuestion
|
from rapids.models import Rapid as RapidQuestion
|
||||||
from rapids.models import Exam as RapidExam
|
from rapids.models import Exam as RapidExam
|
||||||
@@ -2500,4 +2505,84 @@ class ExamDeleteBase(RevisionMixin, DeleteView):
|
|||||||
|
|
||||||
|
|
||||||
def exam_inactive(request, context):
|
def exam_inactive(request, context):
|
||||||
return render(request, "exam_inactive.html", context)
|
return render(request, "exam_inactive.html", context)
|
||||||
|
|
||||||
|
#class UserUserCreate(CidManagerRequiredMixin, CreateView):
|
||||||
|
# model = User
|
||||||
|
# form_class = UserUserForm
|
||||||
|
# template_name: str = "generic/user_creation_form.html"
|
||||||
|
#
|
||||||
|
# success_url = reverse_lazy("accounts_list")
|
||||||
|
|
||||||
|
def create_user(request, context=None):
|
||||||
|
# if this is a POST request we need to process the form data
|
||||||
|
if request.method == 'POST':
|
||||||
|
# create a form instance and populate it with data from the request:
|
||||||
|
form = UserUserForm(request.POST)
|
||||||
|
# check whether it's valid:
|
||||||
|
if form.is_valid():
|
||||||
|
try:
|
||||||
|
user_dict = {
|
||||||
|
"username": request.POST["username"],
|
||||||
|
"first_name": request.POST["first_name"],
|
||||||
|
"last_name": request.POST["last_name"],
|
||||||
|
"email": request.POST["username"],
|
||||||
|
"password": secrets.token_hex(nbytes=16)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
new_user = User.objects.create_user(**user_dict)
|
||||||
|
except Exception as error:
|
||||||
|
errors = f"Unable to create account: {error}"
|
||||||
|
return render(request, 'generic/user_creation_form.html', {'form': form, "errors": errors})
|
||||||
|
|
||||||
|
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 request.POST["grade"]:
|
||||||
|
grade = UserGrades.objects.get(name=f"ST{ request.POST['grade'] }")
|
||||||
|
user_profile.grade = grade
|
||||||
|
|
||||||
|
user_profile.save()
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
errors = f"Unable to create account profile {error}"
|
||||||
|
return render(request, 'generic/user_creation_form.html', {'form': form, "errors": errors})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(reverse_lazy("accounts_list"))
|
||||||
|
return reverse_lazy("accounts_list")
|
||||||
|
|
||||||
|
# if a GET (or any other method) we'll create a blank form
|
||||||
|
else:
|
||||||
|
form = UserUserForm()
|
||||||
|
|
||||||
|
return render(request, 'generic/user_creation_form.html', {'form': form})
|
||||||
|
|
||||||
|
class SupervisorDetail(CidManagerRequiredMixin, DetailView):
|
||||||
|
model = Supervisor
|
||||||
|
|
||||||
|
|
||||||
|
class SupervisorDelete(CidManagerRequiredMixin, DeleteView):
|
||||||
|
model = Supervisor
|
||||||
|
template_name: str = "confirm_delete.html"
|
||||||
|
success_url = reverse_lazy("generic:supervisor")
|
||||||
|
|
||||||
|
class SupervisorUpdate(CidManagerRequiredMixin, UpdateView):
|
||||||
|
model = Supervisor
|
||||||
|
form_class = SupervisorForm
|
||||||
|
#success_url = reverse_lazy("generic:supervisor_detail", kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
class SupervisorCreate(CidManagerRequiredMixin, CreateView):
|
||||||
|
model = Supervisor
|
||||||
|
form_class = SupervisorForm
|
||||||
|
#success_url = reverse_lazy("generic:supervisor_detail")
|
||||||
|
|
||||||
|
class SupervisorList(CidManagerRequiredMixin, ListView):
|
||||||
|
model = Supervisor
|
||||||
@@ -27,6 +27,8 @@ from rapids import views as rapid_views
|
|||||||
from anatomy import views as anatomy_views
|
from anatomy import views as anatomy_views
|
||||||
from longs import views as long_views
|
from longs import views as long_views
|
||||||
|
|
||||||
|
from generic import views as generic_views
|
||||||
|
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from django.conf.urls import handler400, handler403, handler404, handler500
|
from django.conf.urls import handler400, handler403, handler404, handler500
|
||||||
@@ -61,6 +63,9 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"accounts/bulk_create/", views.accounts_bulk_create, name="accounts_bulk_create"
|
"accounts/bulk_create/", views.accounts_bulk_create, name="accounts_bulk_create"
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"accounts/create/", generic_views.create_user, name="create_user"
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"accounts/update/<str:slug>/",
|
"accounts/update/<str:slug>/",
|
||||||
views.UpdateUserView.as_view(),
|
views.UpdateUserView.as_view(),
|
||||||
|
|||||||
@@ -525,6 +525,7 @@ class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView):
|
|||||||
fields = [
|
fields = [
|
||||||
"supervisor_name",
|
"supervisor_name",
|
||||||
"supervisor_email",
|
"supervisor_email",
|
||||||
|
"supervisor",
|
||||||
"grade",
|
"grade",
|
||||||
"registration_number",
|
"registration_number",
|
||||||
"peninsula_trainee",
|
"peninsula_trainee",
|
||||||
|
|||||||
@@ -16,10 +16,13 @@
|
|||||||
<div>
|
<div>
|
||||||
Grade: {{ user.userprofile.grade }}
|
Grade: {{ user.userprofile.grade }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div style="border:1px dashed red">
|
||||||
Supervisor: {{ user.userprofile.supervisor_name }}<br/>
|
Supervisor: {{ user.userprofile.supervisor_name }}<br/>
|
||||||
Supervisor Email: {{ user.userprofile.supervisor_email }}<br/>
|
Supervisor Email: {{ user.userprofile.supervisor_email }}<br/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
Supervisor: {{ user.userprofile.supervisor }}<br/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Registration number: {{ user.userprofile.registration_number }}
|
Registration number: {{ user.userprofile.registration_number }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
</form>
|
</form>
|
||||||
</details>
|
</details>
|
||||||
{{object_list|length}} Users shown.
|
{{object_list|length}} Users shown.
|
||||||
<ul>
|
<ul id="user-list">
|
||||||
{% for list_user in object_list %}
|
{% for list_user in object_list %}
|
||||||
<li>Username: <a href="{% url 'account_profile' list_user.username %}">{{ list_user.username }}</a><br/>
|
<li><span class="username">Username: <a href="{% url 'account_profile' list_user.username %}">{{ list_user.username }}</a></span>
|
||||||
Name: {{list_user.first_name}} {{list_user.last_name}}<br/>
|
<span class="name">Name: {{list_user.first_name}} {{list_user.last_name}}</span>
|
||||||
Grade: {{list_user.userprofile.grade}} / Supervisor: {{list_user.userprofile.supervisor_name}} [{{list_user.userprofile.supervisor_email}}]<br/>
|
{% if list_user.userprofile.grade %}<span class="grade">Grade: {{list_user.userprofile.grade}}</span>{% endif %} Supervisor: {{list_user.userprofile.supervisor_name}} [{{list_user.userprofile.supervisor_email}}]
|
||||||
Registration number: {{list_user.registration_number}}
|
<br/> Registration number: {{list_user.registration_number}}
|
||||||
<button class="small-url-button"><a href="{% url 'account_update' list_user.username %}">Edit user</a></button>
|
<button class="small-url-button"><a href="{% url 'account_update' list_user.username %}">Edit user</a></button>
|
||||||
<button class="small-url-button"><a href="{% url 'account_profile_update' list_user.username %}">Edit profile</a></button>
|
<button class="small-url-button"><a href="{% url 'account_profile_update' list_user.username %}">Edit profile</a></button>
|
||||||
|
|
||||||
@@ -30,5 +30,18 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="{% url 'accounts_bulk_create' %}">Bulk create users</a>
|
<a href="{% url 'accounts_bulk_create' %}">Bulk create users</a>
|
||||||
|
<a href="{% url 'create_user' %}">Create single user</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#user-list .name, #user-list .grade {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
Reference in New Issue
Block a user