From 0f3bc68de68c7896cf987c49f19c0352ceced173 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 23 Oct 2023 13:36:41 +0100 Subject: [PATCH] improve dicom uploading --- atlas/api.py | 21 +- atlas/forms.py | 2 +- ...017_series_created_at_series_updated_at.py | 25 ++ ...created_at_series_created_date_and_more.py | 23 ++ ..._uncategoriseddicom_series_instance_uid.py | 19 ++ ..._uncategoriseddicom_series_instance_uid.py | 18 ++ ..._uncategoriseddicom_series_instance_uid.py | 18 ++ ...lter_uncategoriseddicom_series_and_more.py | 24 ++ atlas/models.py | 12 +- atlas/tables.py | 2 + atlas/templates/atlas/case_display_block.html | 7 +- atlas/templates/atlas/series_form.html | 2 +- atlas/templates/atlas/user_uploads.html | 67 ++--- atlas/urls.py | 2 +- generic/filters.py | 4 +- generic/models.py | 4 + generic/tables.py | 6 +- helpers/images.py | 6 +- ...series_created_at_longseries_updated_at.py | 25 ++ ...ted_at_longseries_created_date_and_more.py | 23 ++ longs/templates/longs/longseries_form.html | 2 +- rad/filters.py | 2 + rad/static/css/anatomy.css | 20 +- rad/views.py | 30 ++- .../0003_alter_useranswer_answer.py | 18 ++ templates/base.html | 230 +++++++++--------- templates/profile.html | 6 +- templates/user_update_profile.html | 18 +- 28 files changed, 464 insertions(+), 172 deletions(-) create mode 100644 atlas/migrations/0017_series_created_at_series_updated_at.py create mode 100644 atlas/migrations/0018_rename_created_at_series_created_date_and_more.py create mode 100644 atlas/migrations/0019_uncategoriseddicom_series_instance_uid.py create mode 100644 atlas/migrations/0020_alter_uncategoriseddicom_series_instance_uid.py create mode 100644 atlas/migrations/0021_alter_uncategoriseddicom_series_instance_uid.py create mode 100644 atlas/migrations/0022_alter_uncategoriseddicom_series_and_more.py create mode 100644 longs/migrations/0009_longseries_created_at_longseries_updated_at.py create mode 100644 longs/migrations/0010_rename_created_at_longseries_created_date_and_more.py create mode 100644 sbas/migrations/0003_alter_useranswer_answer.py diff --git a/atlas/api.py b/atlas/api.py index 4ed20578..c35df6e9 100644 --- a/atlas/api.py +++ b/atlas/api.py @@ -66,11 +66,17 @@ def upload_dicom(request, files: List[UploadedFile] = File(...)): return {"uploaded": uploaded, "duplicates": duplicate} -@router.get("/clear_dicoms", auth=django_auth) +@router.post("/clear_dicoms", auth=django_auth) def clear_dicoms(request): - dicoms = UncategorisedDicom.objects.filter(user=request.user) + + if "selection" in request.POST: + dicoms = UncategorisedDicom.objects.filter(series_instance_uid__in=request.POST.getlist("selection")) + + else: + dicoms = UncategorisedDicom.objects.filter(user=request.user) dicoms.delete() + print(dicoms) return True @@ -90,7 +96,12 @@ def uncategorised_dicoms(request): def import_dicoms_helper(request, case_id: int | None = None): - dicoms = UncategorisedDicom.objects.filter(user=request.user) + #dicoms = UncategorisedDicom.objects.filter(user=request.user) + if "selection" in request.POST: + dicoms = UncategorisedDicom.objects.filter(series_instance_uid__in=request.POST.getlist("selection")) + + else: + dicoms = UncategorisedDicom.objects.filter(user=request.user) data = defaultdict(list) for d in dicoms: @@ -136,12 +147,12 @@ def import_dicoms_helper(request, case_id: int | None = None): return series_list -@router.get("/import_dicoms", auth=django_auth, response=List[SeriesSchema]) +@router.post("/import_dicoms", auth=django_auth, response=List[SeriesSchema]) def import_dicoms(request): return import_dicoms_helper(request) -@router.get("/import_dicoms/{case_id}", auth=django_auth, response=List[SeriesSchema]) +@router.post("/import_dicoms/{case_id}", auth=django_auth, response=List[SeriesSchema]) def import_dicoms_case(request, case_id: int): return import_dicoms_helper(request, case_id=case_id) diff --git a/atlas/forms.py b/atlas/forms.py index 155ec906..70dfda67 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -223,7 +223,7 @@ class SeriesForm(ModelForm): class Meta: model = Series - exclude = ["author"] + exclude = ["author", "series_instance_uid"] widgets = { "examination": autocomplete.ModelSelect2( diff --git a/atlas/migrations/0017_series_created_at_series_updated_at.py b/atlas/migrations/0017_series_created_at_series_updated_at.py new file mode 100644 index 00000000..d083010e --- /dev/null +++ b/atlas/migrations/0017_series_created_at_series_updated_at.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.4 on 2023-10-23 08:53 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0016_remove_uncategoriseddicom_is_dicom'), + ] + + operations = [ + migrations.AddField( + model_name='series', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='series', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/atlas/migrations/0018_rename_created_at_series_created_date_and_more.py b/atlas/migrations/0018_rename_created_at_series_created_date_and_more.py new file mode 100644 index 00000000..a61aa931 --- /dev/null +++ b/atlas/migrations/0018_rename_created_at_series_created_date_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.4 on 2023-10-23 09:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0017_series_created_at_series_updated_at'), + ] + + operations = [ + migrations.RenameField( + model_name='series', + old_name='created_at', + new_name='created_date', + ), + migrations.RenameField( + model_name='series', + old_name='updated_at', + new_name='modified_date', + ), + ] diff --git a/atlas/migrations/0019_uncategoriseddicom_series_instance_uid.py b/atlas/migrations/0019_uncategoriseddicom_series_instance_uid.py new file mode 100644 index 00000000..76b4740d --- /dev/null +++ b/atlas/migrations/0019_uncategoriseddicom_series_instance_uid.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.4 on 2023-10-23 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0018_rename_created_at_series_created_date_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='uncategoriseddicom', + name='series_instance_uid', + field=models.CharField(default='test', max_length=255), + preserve_default=False, + ), + ] diff --git a/atlas/migrations/0020_alter_uncategoriseddicom_series_instance_uid.py b/atlas/migrations/0020_alter_uncategoriseddicom_series_instance_uid.py new file mode 100644 index 00000000..5194ad6d --- /dev/null +++ b/atlas/migrations/0020_alter_uncategoriseddicom_series_instance_uid.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-10-23 10:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0019_uncategoriseddicom_series_instance_uid'), + ] + + operations = [ + migrations.AlterField( + model_name='uncategoriseddicom', + name='series_instance_uid', + field=models.CharField(default='', max_length=255), + ), + ] diff --git a/atlas/migrations/0021_alter_uncategoriseddicom_series_instance_uid.py b/atlas/migrations/0021_alter_uncategoriseddicom_series_instance_uid.py new file mode 100644 index 00000000..eaeba039 --- /dev/null +++ b/atlas/migrations/0021_alter_uncategoriseddicom_series_instance_uid.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-10-23 10:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0020_alter_uncategoriseddicom_series_instance_uid'), + ] + + operations = [ + migrations.AlterField( + model_name='uncategoriseddicom', + name='series_instance_uid', + field=models.CharField(default='', max_length=255, null=True), + ), + ] diff --git a/atlas/migrations/0022_alter_uncategoriseddicom_series_and_more.py b/atlas/migrations/0022_alter_uncategoriseddicom_series_and_more.py new file mode 100644 index 00000000..390cd44c --- /dev/null +++ b/atlas/migrations/0022_alter_uncategoriseddicom_series_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.4 on 2023-10-23 11:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0021_alter_uncategoriseddicom_series_instance_uid'), + ] + + operations = [ + migrations.AlterField( + model_name='uncategoriseddicom', + name='series', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='imported_dicoms', to='atlas.series'), + ), + migrations.AlterField( + model_name='uncategoriseddicom', + name='series_instance_uid', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index 157f5b42..2037eb00 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -811,9 +811,11 @@ class UncategorisedDicom(models.Model): ) series = models.ForeignKey( - "Series", related_name="imported_dicoms", on_delete=models.SET_NULL, null=True + "Series", related_name="imported_dicoms", on_delete=models.SET_NULL, null=True, blank=True ) + series_instance_uid = models.CharField(max_length=255, blank=True, null=True) + image_md5_hash = models.CharField(max_length=32, null=True, blank=True) def check_for_duplicates(self, image_hash): @@ -833,10 +835,14 @@ class UncategorisedDicom(models.Model): def save(self, *args, **kwargs): """Override save method to add image hash""" if self.image: + + ds = self.get_dicom_info() + + self.series_instance_uid = ds["SeriesInstanceUID"].value + # if self.check_for_duplicates(): # return DuplicateDicom - image_hash = get_image_dicom_hash(self.image) - + image_hash = get_image_dicom_hash(None, dataset=ds) self.image_md5_hash = image_hash diff --git a/atlas/tables.py b/atlas/tables.py index 737ed241..8265b6fd 100755 --- a/atlas/tables.py +++ b/atlas/tables.py @@ -110,6 +110,8 @@ class SeriesTable(tables.Table): "plane", "contrast", "author", + "created_date", + "modified_date", ) sequence = ("view", "popup", "images", "case") diff --git a/atlas/templates/atlas/case_display_block.html b/atlas/templates/atlas/case_display_block.html index 3e4da7b7..a7b3c9bd 100755 --- a/atlas/templates/atlas/case_display_block.html +++ b/atlas/templates/atlas/case_display_block.html @@ -1,5 +1,5 @@
-
View case in OHIF
+
View case in OHIF
{{ case.created_date|date:"d/m/Y" }} @@ -42,7 +42,10 @@ {% endfor %} - Add new series
+ + Add new series
+ Import new uploads
+
{% comment %}

Scrapped: {{ case.scrapped }} (toggle) {% endcomment %} diff --git a/atlas/templates/atlas/series_form.html b/atlas/templates/atlas/series_form.html index 52b03afb..bb008de8 100755 --- a/atlas/templates/atlas/series_form.html +++ b/atlas/templates/atlas/series_form.html @@ -411,7 +411,7 @@ {{ form.media }} {% endblock %} {% block content %} -

Add Series

+

Series Form

This form will create a image set that can be associated with a atlas question. If the question has already been created it can be linked below.
diff --git a/atlas/templates/atlas/user_uploads.html b/atlas/templates/atlas/user_uploads.html index c9678685..06b43b25 100644 --- a/atlas/templates/atlas/user_uploads.html +++ b/atlas/templates/atlas/user_uploads.html @@ -2,46 +2,55 @@ {% block content %}

Uploaded dicoms

+ Click to select cases to import. If no cases are selected all will be seleced/imported. {% if series_list %} -
    - {% for series, n, tags in series_list %} -
  • - {{series}} [{{n}} images]
    - {{tags}} -
  • - {% endfor %} -
+
    + {% for series, n, tags in series_list %} +
  • + {{series}} [{{n}} images]
    + {{tags}} + +
  • + {% endfor %} +
- {% if case %} -

Importing into case {{case}}

- {% endif %} + {% if case %} +

Importing into case {{case}}

+ {% endif %} - - {% if case %} - + + {% if case %} + - {% else %} - - {% endif %} + {% else %} + + {% endif %} -
+
-
+
{% else %} No uploads awaiting importing have been found. {% endif %} -

View orphan series

+

View orphan series

{% endblock %} diff --git a/atlas/urls.py b/atlas/urls.py index a71da05e..c277b433 100755 --- a/atlas/urls.py +++ b/atlas/urls.py @@ -35,7 +35,7 @@ urlpatterns = [ path("collection/", views.CollectionView.as_view(), name="collection_view"), path("collection/user", views.user_collections, name="user_collections"), path("uploads", views.user_uploads, name="user_uploads"), - path("uploads/case/", views.user_uploads, name="user_uploads"), + path("uploads/case/", views.user_uploads, name="user_uploads_case"), path( "collection/create", views.CaseCollectionCreate.as_view(), diff --git a/generic/filters.py b/generic/filters.py index 38999cf9..2c732291 100644 --- a/generic/filters.py +++ b/generic/filters.py @@ -36,6 +36,7 @@ class CidUserFilter(django_filters.FilterSet): class UserUserFilter(django_filters.FilterSet): userprofile__grade = django_filters.ModelChoiceFilter(queryset=UserGrades.objects.all(),label="Grade") userprofile__supervisor = django_filters.ModelChoiceFilter(queryset=Supervisor.objects.all(), label="Supervisor") + userprofile__peninsula_trainee = django_filters.BooleanFilter(label="Peninsula Trainee") #has_supervisor = django_filters.BooleanFilter(field_name='supervisor', lookup_expr='isnull', exclude=True, label="Has Supervisor") class Meta: @@ -50,7 +51,8 @@ class UserUserFilter(django_filters.FilterSet): #"userprofile__grade": ["exact"], #"userprofile__supervisor": ["exact"], "email": ["contains"], - "is_active": ["exact"] + "is_active": ["exact"], + "groups": ["exact"], } @property diff --git a/generic/models.py b/generic/models.py index a9f745b7..68272aa3 100644 --- a/generic/models.py +++ b/generic/models.py @@ -230,6 +230,9 @@ class SeriesBase(models.Model): help_text="If a series should be freely available to browse", default=True ) + created_date = models.DateTimeField(auto_now_add=True) + modified_date = models.DateTimeField(auto_now=True) + class Meta: abstract = True @@ -1271,6 +1274,7 @@ class UserProfile(models.Model): on_delete=models.CASCADE, ) peninsula_trainee = models.BooleanField(default=False) + #affiliation = models.ForeignKey("Site", on_delete=models.SET_NULL, blank=True, null=True) def getusername(self): return self.user.username diff --git a/generic/tables.py b/generic/tables.py index 1530f128..c1e05e09 100644 --- a/generic/tables.py +++ b/generic/tables.py @@ -252,8 +252,12 @@ class UserUserTable(tables.Table): return "Edit" def render_user(self, value, record): + span_class = "" + if record.userprofile.peninsula_trainee: + span_class = "peninsula-trainee" return format_html( - "{} {}
{}
{}", + "
{} {}
{}
{}
", + span_class, record.first_name, record.last_name, record.username, diff --git a/helpers/images.py b/helpers/images.py index 482c6df4..6a1e1a14 100644 --- a/helpers/images.py +++ b/helpers/images.py @@ -155,8 +155,10 @@ def print_dicom(dataset, indent=0): repr_value)) return "\n
".join(l) -def get_image_dicom_hash(img) -> (str, bool): - dataset = pydicom.dcmread(img) +def get_image_dicom_hash(img, dataset=None) -> (str, bool): + if dataset is None: + dataset = pydicom.dcmread(img) + md5 = hashlib.md5() first = True for i in dataset.pixel_array.astype(str).flatten(): diff --git a/longs/migrations/0009_longseries_created_at_longseries_updated_at.py b/longs/migrations/0009_longseries_created_at_longseries_updated_at.py new file mode 100644 index 00000000..41948c71 --- /dev/null +++ b/longs/migrations/0009_longseries_created_at_longseries_updated_at.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.4 on 2023-10-23 08:53 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('longs', '0008_alter_longseriesimage_series'), + ] + + operations = [ + migrations.AddField( + model_name='longseries', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='longseries', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/longs/migrations/0010_rename_created_at_longseries_created_date_and_more.py b/longs/migrations/0010_rename_created_at_longseries_created_date_and_more.py new file mode 100644 index 00000000..e93124f8 --- /dev/null +++ b/longs/migrations/0010_rename_created_at_longseries_created_date_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.4 on 2023-10-23 09:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('longs', '0009_longseries_created_at_longseries_updated_at'), + ] + + operations = [ + migrations.RenameField( + model_name='longseries', + old_name='created_at', + new_name='created_date', + ), + migrations.RenameField( + model_name='longseries', + old_name='updated_at', + new_name='modified_date', + ), + ] diff --git a/longs/templates/longs/longseries_form.html b/longs/templates/longs/longseries_form.html index 60439902..9aab61a5 100755 --- a/longs/templates/longs/longseries_form.html +++ b/longs/templates/longs/longseries_form.html @@ -317,7 +317,7 @@ {{ form.media }} {% endblock %} {% block content %} -

Add Series

+

Series Form

This form will create a image set that can be associated with a long question. If the question has already been created it can be linked below. diff --git a/rad/filters.py b/rad/filters.py index dd9af2a9..7f7afbab 100644 --- a/rad/filters.py +++ b/rad/filters.py @@ -20,6 +20,8 @@ class UserListFilter(django_filters.FilterSet): "username": ["contains"], "email": ["contains"], "userprofile__grade": ["exact"], + "userprofile__peninsula_trainee": ["exact"], + } # def __init__(self, data=None, queryset=None, *, request=None, prefix=None): diff --git a/rad/static/css/anatomy.css b/rad/static/css/anatomy.css index 1442bb24..00cbbb79 100644 --- a/rad/static/css/anatomy.css +++ b/rad/static/css/anatomy.css @@ -73,7 +73,7 @@ a:link { font-size: 20; } -.hide { +.hide, .hidden { display: none; } @@ -712,7 +712,8 @@ input { text-decoration: underline; } -.date,.small-gray { +.date, +.small-gray { font-size: smaller; opacity: 50%; } @@ -1039,4 +1040,19 @@ h6 { background-color: black; width: 100%; +} + +table .peninsula-trainee::before { + content: "Trainee"; + transform: rotate(-90deg) translate(-45px, -10px); + color: darkblue; + display: inline-block; + float: left; + width: 20px; +} + +#series-list .selected { + color: white; + background-color: #52057b; + user-select: none; } \ No newline at end of file diff --git a/rad/views.py b/rad/views.py index a207b188..8a928260 100644 --- a/rad/views.py +++ b/rad/views.py @@ -600,7 +600,7 @@ class UpdateUserView(CidManagerRequiredMixin, UpdateView): return reverse(view_name, kwargs={"slug": self.object.username}) -class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView): +class UpdateUserProfileAdminView(CidManagerRequiredMixin, UpdateView): model = UserProfile fields = [ "supervisor", @@ -617,6 +617,34 @@ class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView): # No need for reverse_lazy here, because it's called inside the method return reverse(view_name, kwargs={"slug": self.object.username}) +class UpdateUserProfileView(UpdateView): + model = UserProfile + fields = [ + "supervisor", + "grade", + "registration_number", + #"peninsula_trainee", + ] # Keep listing whatever fields + template_name = "user_update_profile.html" + slug_field = "user__username" + slug_url_kwarg = "slug" + + def dispatch(self, request, *args, **kwargs): + if request.user.is_superuser or request.user.groups.filter(name="cid_user_manager").exists(): + self.fields = ["supervisor", "grade", "registration_number", "peninsula_trainee"] + # Check permissions for the request.user here + elif request.user.userprofile.peninsula_trainee: + self.fields = ["supervisor", "grade", "registration_number"] + + else: + self.fields = ["grade", "registration_number"] + return super().dispatch(request, *args, **kwargs) + + + def get_success_url(self): + view_name = "account_profile" + # No need for reverse_lazy here, because it's called inside the method + return reverse(view_name, kwargs={"slug": self.object.username}) # class UpdateUser(TemplateView): # diff --git a/sbas/migrations/0003_alter_useranswer_answer.py b/sbas/migrations/0003_alter_useranswer_answer.py new file mode 100644 index 00000000..4ebe3a68 --- /dev/null +++ b/sbas/migrations/0003_alter_useranswer_answer.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2023-10-23 08:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sbas', '0002_exam_candidates_only_alter_exam_active_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='useranswer', + name='answer', + field=models.CharField(choices=[('a', 'a_answer'), ('b', 'b_answer'), ('c', 'c_answer'), ('d', 'd_answer'), ('e', 'e_answer')], help_text='The user selected answer', max_length=20), + ), + ] diff --git a/templates/base.html b/templates/base.html index a8d7c15e..b1887fb2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -5,133 +5,133 @@ {% load auth_extras %} - - - {% block title %} - PENRA Courses - {% endblock %} - + + + {% block title %} + PENRA Courses + {% endblock %} + {% comment %} {% endcomment %} - + - - - - + + + + - - - - + + + + - - + + {% comment %} {% endcomment %} - - - {% django_htmx_script %} - - - - - - - - - - - - - - - - + + + {% django_htmx_script %} + + + + + + + + + + + + + + + + - - - - - - - - - - {% block js %} - {% endblock %} - - {% block css %} - {% endblock %} - - - - - -
- {% block navigation %} + + + + + + + + + + {% block js %} {% endblock %} -
-
- {% block content %} - {% endblock %} + + {% block css %} + {% endblock %} + + + + + +
+ {% block navigation %} + {% endblock %} +
+
+ {% block content %} + {% endblock %} +
-
- + \ No newline at end of file diff --git a/templates/profile.html b/templates/profile.html index 16977936..8ee02904 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -1,4 +1,5 @@ {% extends 'base.html' %} +{% load auth_extras %} {% block content %}
@@ -54,7 +55,10 @@ Change password - Update user details + + {% if request.user|has_group:"cid_user_manager" %} + Update user details + {% endif %} Update user profile
{% endblock %} \ No newline at end of file diff --git a/templates/user_update_profile.html b/templates/user_update_profile.html index 8d342c8c..932d7203 100644 --- a/templates/user_update_profile.html +++ b/templates/user_update_profile.html @@ -1,11 +1,17 @@ {% extends 'generic/base.html' %} {% load crispy_forms_tags %} +{% load auth_extras %} {% block content %} -

Editing user: {{object.username}}

-This form allows you to edit additional user details. Name and emails can be edited here -{% csrf_token %} - {{ form|crispy }} - - +

Editing user: {{object.username}}

+ This form allows you to edit additional user details. + + {% if request.user|has_group:"cid_user_manager" %} + Name and emails can be edited here + {% endif %} + +
{% csrf_token %} + {{ form|crispy }} + +
{% endblock %} \ No newline at end of file