Improvements to user management

This commit is contained in:
Ross
2022-11-14 14:25:24 +00:00
parent 02eb812122
commit 6e6ba178b5
27 changed files with 515 additions and 20 deletions
+8
View File
@@ -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),
),
]
+12
View File
@@ -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
View File
@@ -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
View File
@@ -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 = ("",)
+26
View File
@@ -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
View File
@@ -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()
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 %}
+5
View File
@@ -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
View File
@@ -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
+5
View File
@@ -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(),
+1
View File
@@ -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",
+4 -1
View File
@@ -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>
+19 -6
View File
@@ -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 %}