Repository: hydroshare/hydroshare (language: python)

Issue:
[#5316] Upgrade to Django 4.2
Resolves #5316 
<!--

Please read, and add your text at the bottom of this message.

Thanks for contributing code to HydroShare. In order to maintain code quality and expedite this process, please assist the development team by making sure the following is present in this pull request.

For more information, see https://docs.google.com/document/d/1dzxqlZW5fKNEyQSeKiSFq-SmS-VOPCva95XXkBjPExs

-->

### Pull Request Checklist: 
- [ ] Positive Test Case Written by Dev

<!-- Enter steps that a QA engineer, stakeholder, or user documentation writer would follow to test the positive or "successful" case of the functionality your code provides or fixes -->

- [x] Automated Testing

<!-- Our Jenkins Instance is set up to automatically test every commit from a pull request. Code coverage must not decrease so new functionality or code paths added during a bug fix must have appropriate tests written. Every test must pass, including PEP8 code formatting tests. -->

- [ ] Sufficient User and Developer Documentation

<!-- Please email your positive test case lbrazil@cuahsi.org, who will make the decision regarding user documentation. -->

- [x] Passing Jenkins Build

<!-- Our Jenkins Instance is set up to automatically test every commit from a pull request. Every test must pass, including PEP8 code formatting tests. -->

- [x] Peer Code review and approval

<!-- This is the process by which a peer developer on the HydroShare team will read the changeset, provide feedback, and ultimately give a formal approval to the code before it passes PR status. -->

### Positive Test Case
1. [Enter positive test case here]



Discussion / hints:

Fix patch:
diff --git a/.dockerignore b/.dockerignore
index b512c09d47..d992b0ffac 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,1 +1,2 @@
-node_modules
\ No newline at end of file
+node_modules
+.git
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index c0a4d72bfb..a3e785f890 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,6 +5,18 @@ FROM hydroshare/hs_docker_base:262c2ca
 RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
     locale-gen
 
+RUN pip install psycopg==3.1.*
+RUN pip install django==4.2.*
+RUN pip install djangorestframework==3.14.*
+RUN pip install django-haystack==3.2.*
+RUN pip install drf-yasg==1.21.*
+RUN pip install django-robots==6.1
+RUN pip install django-autocomplete-light==3.11.*
+RUN pip install django-widget-tweaks==1.5.*
+RUN pip install django-crispy-forms==2.1
+RUN pip install crispy-bootstrap3==2024.1
+RUN pip install Mezzanine==6.0.0
+
 # https://www.digicert.com/kb/digicert-root-certificates.htm
 # Get the .pem file from digicert and add it to the bundle used by certifi
 # Could also use the REQUESTS_CA_BUNDLE environment variable to point to the .pem file
diff --git a/Dockerfile-multistage-node b/Dockerfile-multistage-node
index e29a2002b9..70f8276409 100644
--- a/Dockerfile-multistage-node
+++ b/Dockerfile-multistage-node
@@ -36,6 +36,18 @@ COPY --from=node-build /hydroshare /hydroshare
 RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
     locale-gen
 
+RUN pip install psycopg==3.1.*
+RUN pip install django==4.2.*
+RUN pip install djangorestframework==3.14.*
+RUN pip install django-haystack==3.2.*
+RUN pip install drf-yasg==1.21.*
+RUN pip install django-robots==6.1
+RUN pip install django-autocomplete-light==3.11.*
+RUN pip install django-widget-tweaks==1.5.*
+RUN pip install django-crispy-forms==2.1
+RUN pip install crispy-bootstrap3==2024.1
+RUN pip install Mezzanine==6.0.0
+
 # https://www.digicert.com/kb/digicert-root-certificates.htm
 # Get the .pem file from digicert and add it to the bundle used by certifi
 # Could also use the REQUESTS_CA_BUNDLE environment variable to point to the .pem file
diff --git a/django_irods/urls.py b/django_irods/urls.py
index 9615dd9a41..0fbd2d606c 100644
--- a/django_irods/urls.py
+++ b/django_irods/urls.py
@@ -1,14 +1,14 @@
-from django.conf.urls import url
-from django_irods.views import rest_check_task_status, rest_download, download
+from django.urls import re_path
+
+from django_irods.views import download, rest_check_task_status, rest_download
 
 urlpatterns = [
     # for download request from resource landing page
-    url(r'^download/(?P<path>.*)$', download, name='django_irods_download'),
+    re_path(r'^download/(?P<path>.*)$', download, name='django_irods_download'),
     # for download request from REST API
-    url(r'^rest_download/(?P<path>.*)$', rest_download,
-        name='rest_download'),
+    re_path(r'^rest_download/(?P<path>.*)$', rest_download, name='rest_download'),
     # for REST API poll
-    url(r'^rest_check_task_status/(?P<task_id>[A-z0-9\-]+)$',
-        rest_check_task_status,
-        name='rest_check_task_status'),
+    re_path(r'^rest_check_task_status/(?P<task_id>[A-z0-9\-]+)$',
+            rest_check_task_status,
+            name='rest_check_task_status'),
 ]
diff --git a/django_irods/views.py b/django_irods/views.py
index d6842a1a06..c53d013142 100755
--- a/django_irods/views.py
+++ b/django_irods/views.py
@@ -22,7 +22,7 @@
                                 get_resource_bag_task, get_task_notification,
                                 get_task_user_id)
 from hs_core.tasks import create_bag_by_irods, create_temp_zip
-from hs_core.views.utils import ACTION_TO_AUTHORIZE, authorize
+from hs_core.views.utils import ACTION_TO_AUTHORIZE, authorize, is_ajax
 from hs_file_types.enums import AggregationMetaFilePath
 
 logger = logging.getLogger(__name__)
@@ -260,7 +260,7 @@ def download(request, path, use_async=True,
                     response = HttpResponse()
                     response.content = content_msg
                     return response
-        elif request.is_ajax():
+        elif is_ajax(request):
             task_dict = {
                 'id': datetime.datetime.today().isoformat(),
                 'name': "bag download",
diff --git a/hs_access_control/urls.py b/hs_access_control/urls.py
index 0fbae1274b..530793b0bd 100644
--- a/hs_access_control/urls.py
+++ b/hs_access_control/urls.py
@@ -1,26 +1,27 @@
-from django.conf.urls import url
+from django.urls import path, re_path
+
 from hs_access_control import views
 
 urlpatterns = [
     # Community responders return JSON
-    url(r'^_internal/community/(?P<cid>[0-9]+)/(?P<action>[a-z]+)/(?P<gid>[0-9]+)/$',
-        views.CommunityView.as_view(), name='access_manage_community'),
-    url(r'^_internal/community/(?P<cid>[0-9]+)/(?P<action>owner)/(?P<uid>[a-zA-Z0-9]+)/(?P<addrem>add|remove)$',
-        views.CommunityView.as_view(), name='access_manage_community'),
-    url(r'^_internal/community/(?P<cid>[0-9]+)/(?P<action>[a-z]+)/',
-        views.CommunityView.as_view(), name='access_manage_community'),
+    re_path(r'^_internal/community/(?P<cid>[0-9]+)/(?P<action>[a-z]+)/(?P<gid>[0-9]+)/$',
+            views.CommunityView.as_view(), name='access_manage_community'),
+    re_path(r'^_internal/community/(?P<cid>[0-9]+)/(?P<action>owner)/(?P<uid>[a-zA-Z0-9]+)/(?P<addrem>add|remove)$',
+            views.CommunityView.as_view(), name='access_manage_community'),
+    re_path(r'^_internal/community/(?P<cid>[0-9]+)/(?P<action>[a-z]+)/',
+            views.CommunityView.as_view(), name='access_manage_community'),
 
     # Group responders return JSON
-    url(r'^_internal/group/(?P<gid>[0-9]+)/$',
-        views.GroupView.as_view(), name='access_manage_group'),
-    url(r'^_internal/group/(?P<gid>[0-9]+)/(?P<action>[a-z_ ]*)/(?P<cid>[0-9]+)/$',
-        views.GroupView.as_view(), name='access_manage_group'),
+    path('_internal/group/<int:gid>/',
+         views.GroupView.as_view(), name='access_manage_group'),
+    re_path(r'^_internal/group/(?P<gid>[0-9]+)/(?P<action>[a-z_ ]*)/(?P<cid>[0-9]+)/$',
+            views.GroupView.as_view(), name='access_manage_group'),
 
     # Community request responders also return JSON
-    url(r'^_internal/crequest/$',
-        views.CommunityRequestView.as_view(), name='access_manage_crequests'),
-    url(r'^_internal/crequest/(?P<action>[a-z_]+)/$',
-        views.CommunityRequestView.as_view(), name='access_manage_crequests'),
-    url(r'^_internal/crequest/(?P<action>[a-z_]+)/(?P<crid>[0-9]+)/$',
-        views.CommunityRequestView.as_view(), name='access_manage_crequests'),
+    path('_internal/crequest/',
+         views.CommunityRequestView.as_view(), name='access_manage_crequests'),
+    re_path(r'^_internal/crequest/(?P<action>[a-z_]+)/$',
+            views.CommunityRequestView.as_view(), name='access_manage_crequests'),
+    re_path(r'^_internal/crequest/(?P<action>[a-z_]+)/(?P<crid>[0-9]+)/$',
+            views.CommunityRequestView.as_view(), name='access_manage_crequests'),
 ]
diff --git a/hs_collection_resource/urls.py b/hs_collection_resource/urls.py
index 212130ff42..eef2dfed2e 100644
--- a/hs_collection_resource/urls.py
+++ b/hs_collection_resource/urls.py
@@ -1,12 +1,13 @@
-from django.conf.urls import url
+from django.urls import re_path
+
 from hs_collection_resource import views
 
 urlpatterns = [
-    url(r'^_internal/(?P<shortkey>[A-z0-9]+)/update-collection/$', views.update_collection),
-    url(r'^_internal/(?P<shortkey>[A-z0-9]+)/update-collection-for-deleted-resources/$',
-        views.update_collection_for_deleted_resources),
-    url(r'^_internal/calculate-collection-coverages/(?P<shortkey>[A-z0-9]+)/$',
-        views.calculate_collection_coverages, name='calculate-collection-coverages'),
-    url(r'^_internal/(?P<shortkey>[A-z0-9]+)/get-collectable-resources/$',
-        views.get_collectable_resources_modal, name='get-collectable-resources'),
+    re_path(r'^_internal/(?P<shortkey>[A-z0-9]+)/update-collection/$', views.update_collection),
+    re_path(r'^_internal/(?P<shortkey>[A-z0-9]+)/update-collection-for-deleted-resources/$',
+            views.update_collection_for_deleted_resources),
+    re_path(r'^_internal/calculate-collection-coverages/(?P<shortkey>[A-z0-9]+)/$',
+            views.calculate_collection_coverages, name='calculate-collection-coverages'),
+    re_path(r'^_internal/(?P<shortkey>[A-z0-9]+)/get-collectable-resources/$',
+            views.get_collectable_resources_modal, name='get-collectable-resources'),
 ]
diff --git a/hs_communities/static/js/community.js b/hs_communities/static/js/community.js
index aac6f2baae..8b4bf7dcdf 100644
--- a/hs_communities/static/js/community.js
+++ b/hs_communities/static/js/community.js
@@ -236,16 +236,7 @@ $(document).ready(function () {
         this.$set(this.isRemovingOwner, userId, false)
       },
       addOwner: async function () {
-        let userId;
-
-        if ($("#user-deck > .hilight").length > 0) {
-          userId = parseInt($("#user-deck > .hilight")[0].getAttribute("data-value"));
-        }
-        else {
-          return false;   // No user selected
-        }
-
-        $(".hilight > span").click(); // Clear the autocomplete
+        let userId = $("#id_user").find(':selected').val();
 
         const url = `/access/_internal/community/${this.community.id}/owner/${userId}/add`
         try {
@@ -253,6 +244,8 @@ $(document).ready(function () {
           if (response.community) {
             this.community.owners = response.community.owners
             customAlert('Add Community Owner', 'User has been added as an owner of this Community', 'success', 6000, true);
+            // close the modal
+            $("#community-settings-add-owner").modal('hide')
           }
         }
         catch (e) {
@@ -279,4 +272,10 @@ $(document).ready(function () {
       }
     }
   });
+
+  // prevent default form submission
+  let button = $("#community-settings-add-owner-btn");
+  button.click(function (e) {
+    e.preventDefault();
+  });
 });
\ No newline at end of file
diff --git a/hs_communities/templates/hs_communities/community-settings-add-owner.html b/hs_communities/templates/hs_communities/community-settings-add-owner.html
index f1069a4487..4a2475505a 100644
--- a/hs_communities/templates/hs_communities/community-settings-add-owner.html
+++ b/hs_communities/templates/hs_communities/community-settings-add-owner.html
@@ -8,6 +8,7 @@
         <label>Select a HydroShare user to add as an owner to this Community</label>
         <div class="add-view-user-form">
           {{ add_view_invite_user_form.user }}
+          {{ add_view_invite_user_form.media }}
         </div>
       </div>
       <div class="modal-footer">
diff --git a/hs_communities/templates/hs_communities/community-settings.html b/hs_communities/templates/hs_communities/community-settings.html
index 18ee18f0ca..f6c269af9e 100644
--- a/hs_communities/templates/hs_communities/community-settings.html
+++ b/hs_communities/templates/hs_communities/community-settings.html
@@ -115,7 +115,7 @@
       <div class="panel panel-default">
         <div class="panel-heading flex justify-space-between align-center">
           <div>Community Owners</div>
-          <button data-toggle="modal" data-target="#community-settings-add-owner"
+          <button id="community-settings-add-owner-btn" data-toggle="modal" data-target="#community-settings-add-owner"
               class="btn btn-success has-space-left btn-sm">
               <i class="fa fa-plus" aria-hidden="true"></i> Add Owner</button>
         </div>
diff --git a/hs_communities/templates/hs_communities/community.html b/hs_communities/templates/hs_communities/community.html
index 6517d98f8d..66437c2bfe 100644
--- a/hs_communities/templates/hs_communities/community.html
+++ b/hs_communities/templates/hs_communities/community.html
@@ -216,4 +216,9 @@ <h4 class="text-muted">{{ community.purpose|linebreaks }}</h4>
   ];
 
 </script>
+<script>
+  // https://github.com/select2/select2/issues/1436#issuecomment-21028474
+  // incompatibility between Select2 and Bootstrap modals
+  $.fn.modal.Constructor.prototype.enforceFocus = function() {};
+</script>
 {% endblock %}
\ No newline at end of file
diff --git a/hs_communities/templates/hs_communities/my-communities.html b/hs_communities/templates/hs_communities/my-communities.html
index 35410d2887..7ad344e7b6 100644
--- a/hs_communities/templates/hs_communities/my-communities.html
+++ b/hs_communities/templates/hs_communities/my-communities.html
@@ -109,4 +109,9 @@ <h4 style="word-break: break-all"><a href="/community/{{ c.id }}">{{ c.name }}</
 {{ block.super }}
 <script type="text/javascript" src="{% static 'js/bootstrap-formhelpers.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/my-communities.js' %}"></script>
+<script>
+  // https://github.com/select2/select2/issues/1436#issuecomment-21028474
+  // incompatibility between Select2 and Bootstrap modals
+  $.fn.modal.Constructor.prototype.enforceFocus = function() {};
+</script>
 {% endblock %}
\ No newline at end of file
diff --git a/hs_composite_resource/urls.py b/hs_composite_resource/urls.py
index 78d6b49805..66526577d3 100644
--- a/hs_composite_resource/urls.py
+++ b/hs_composite_resource/urls.py
@@ -1,12 +1,12 @@
-from django.conf.urls import url
+from django.urls import re_path
 
 from . import views
 
 urlpatterns = [
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<coverage_type>[A-z]+)/update-coverage/$',
-        views.update_resource_coverage, name="update_resource_coverage"),
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<coverage_type>[A-z]+)/delete-coverage/$',
-        views.delete_resource_coverage, name="delete_resource_coverage"),
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/aggregation-files-to-sync/$',
-        views.check_aggregation_files_to_sync, name="aggregation_files_to_sync"),
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<coverage_type>[A-z]+)/update-coverage/$',
+            views.update_resource_coverage, name="update_resource_coverage"),
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<coverage_type>[A-z]+)/delete-coverage/$',
+            views.delete_resource_coverage, name="delete_resource_coverage"),
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/aggregation-files-to-sync/$',
+            views.check_aggregation_files_to_sync, name="aggregation_files_to_sync"),
 ]
diff --git a/hs_core/admin.py b/hs_core/admin.py
index 7b183918e0..f79ef6a225 100755
--- a/hs_core/admin.py
+++ b/hs_core/admin.py
@@ -1,13 +1,14 @@
 from django import forms
 from django.contrib import messages
+from django.contrib.admin.actions import \
+    delete_selected as django_delete_selected
 from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
-from django.contrib.admin.actions import delete_selected as django_delete_selected
 from django.contrib.auth.forms import UserCreationForm
-from django.contrib.gis import admin
 from django.contrib.contenttypes.admin import GenericTabularInline
+from django.contrib.gis import admin
 from django.utils.translation import gettext_lazy as _
 
-from .models import ResourceFile, User, BaseResource, Creator
+from .models import BaseResource, Creator, ResourceFile, User
 
 
 class UserAdmin(DjangoUserAdmin):
@@ -29,6 +30,7 @@ def delete_model(self, request, obj):
         else:
             return super(UserAdmin, self).delete_model(request, obj)
 
+    @admin.action(description=django_delete_selected.short_description)
     def delete_selected(self, request, queryset):
         # prevent user delete if user is an owner/author on a published resource
         user_no_del = []
@@ -46,7 +48,6 @@ def delete_selected(self, request, queryset):
             self.message_user(request, message, messages.ERROR)
         if queryset.count():
             return django_delete_selected(self, request, queryset)
-    delete_selected.short_description = django_delete_selected.short_description
 
 
 class UserCreationFormExtended(UserCreationForm):
diff --git a/hs_core/autocomplete_light_registry.py b/hs_core/autocomplete_light_registry.py
index b16f00174a..cca8efac89 100755
--- a/hs_core/autocomplete_light_registry.py
+++ b/hs_core/autocomplete_light_registry.py
@@ -1,38 +1,61 @@
-from autocomplete_light import shortcuts as autocomplete_light
-from django.contrib.auth.models import User, Group
-
-
-class UserAutocomplete(autocomplete_light.AutocompleteModelBase):
-    search_fields = ['username', 'first_name', 'last_name']
-    split_words = True
-
-    def choices_for_request(self):
-        self.choices = self.choices.filter(is_active=True)
-        return super(UserAutocomplete, self).choices_for_request()
-
-    def choice_label(self, choice):
-        label = " ".join([choice.first_name or "", choice.userprofile.middle_name or "", choice.last_name or ""])
-
-        if choice.userprofile.organization:
-            if choice.first_name or choice.last_name:
+from dal import autocomplete
+from django.contrib.auth.models import Group, User
+from django.db.models import Q
+
+
+class UserAutocompleteView(autocomplete.Select2QuerySetView):
+    search_fields = ["username", "first_name", "last_name"]
+
+    def get_queryset(self):
+        qs = User.objects.filter(is_active=True)
+
+        if self.q:
+            name_parts = self.q.split(" ", 1)
+            name_part_one = name_parts[0]
+            name_part_two = name_parts[1] if len(name_parts) > 1 else ""
+            if name_part_two:
+                qs = qs.filter(
+                    Q(username__icontains=self.q)
+                    | Q(first_name__icontains=name_part_one, last_name__icontains=name_part_two)
+                )
+            else:
+                qs = qs.filter(
+                    Q(username__icontains=self.q)
+                    | Q(first_name__icontains=name_part_one)
+                    | Q(last_name__icontains=name_part_one)
+                )
+            return qs
+
+        return qs
+
+    def get_result_label(self, item):
+        label = " ".join(
+            [
+                item.first_name or "",
+                item.userprofile.middle_name or "",
+                item.last_name or "",
+            ]
+        )
+
+        if item.userprofile.organization:
+            if item.first_name or item.last_name:
                 label += ", "
-            label += choice.userprofile.organization
+            label += item.userprofile.organization
 
-        if choice.username:
-            label += "".join([" (", choice.username, ")"])
+        if item.username:
+            label += "".join([" (", item.username, ")"])
 
         return label
 
 
-autocomplete_light.register(User, UserAutocomplete)
-
-
-class GroupAutocomplete(autocomplete_light.AutocompleteModelBase):
-    search_fields = ['name']
+class GroupAutocompleteView(autocomplete.Select2QuerySetView):
+    search_fields = ["name"]
 
-    def choices_for_request(self):
-        self.choices = self.choices.filter(gaccess__active=True).exclude(name='Hydroshare Author')
-        return super(GroupAutocomplete, self).choices_for_request()
+    def get_queryset(self):
+        qs = Group.objects.filter(gaccess__active=True).exclude(name="Hydroshare Author")
 
+        if self.q:
+            qs = qs.filter(name__icontains=self.q)
+            return qs
 
-autocomplete_light.register(Group, GroupAutocomplete)
+        return qs
diff --git a/hs_core/debug_urls.py b/hs_core/debug_urls.py
index d2b8a5e838..2047c3099b 100755
--- a/hs_core/debug_urls.py
+++ b/hs_core/debug_urls.py
@@ -1,24 +1,34 @@
 """Extra URLs that add debugging capabilities to resources."""
 
-from django.conf.urls import url
+from django.urls import re_path
+
 from hs_core import views
 
 urlpatterns = [
     # Resource Debugging: print consistency problems in a resource
-    url(r'^debug/resource/(?P<shortkey>[0-9a-f-]+)/$',
+    re_path(
+        r"^debug/resource/(?P<shortkey>[0-9a-f-]+)/$",
         views.debug_resource_view.debug_resource,
-        name='debug_resource'),
-    url(r'^debug/resource/(?P<shortkey>[0-9a-f-]+)/irods-issues/$',
+        name="debug_resource",
+    ),
+    re_path(
+        r"^debug/resource/(?P<shortkey>[0-9a-f-]+)/irods-issues/$",
         views.debug_resource_view.irods_issues,
-        name='debug_resource'),
-    url(r'^debug/resource/(?P<shortkey>[0-9a-f-]+)/non-preferred-paths/$',
+        name="debug_resource",
+    ),
+    re_path(
+        r"^debug/resource/(?P<shortkey>[0-9a-f-]+)/non-preferred-paths/$",
         views.debug_resource_view.check_for_non_preferred_paths,
-        name='debug_resource'),
-
-    url(r'^taskstatus/(?P<task_id>[A-z0-9\-]+)/$',
+        name="debug_resource",
+    ),
+    re_path(
+        r"^taskstatus/(?P<task_id>[A-z0-9\-]+)/$",
         views.debug_resource_view.check_task_status,
-        name='get_debug_task_status'),
-    url(r'^taskstatus/path-check-task/(?P<task_id>[A-z0-9\-]+)/$',
+        name="get_debug_task_status",
+    ),
+    re_path(
+        r"^taskstatus/path-check-task/(?P<task_id>[A-z0-9\-]+)/$",
         views.debug_resource_view.check_non_preferred_paths_task_status,
-        name='get_path_check_task_status'),
+        name="get_path_check_task_status",
+    ),
 ]
diff --git a/hs_core/metadata_terms_urls.py b/hs_core/metadata_terms_urls.py
index f77054ec37..90e01f8278 100644
--- a/hs_core/metadata_terms_urls.py
+++ b/hs_core/metadata_terms_urls.py
@@ -1,9 +1,10 @@
+from django.urls import path, re_path
 
-from django.conf.urls import url
 from hs_core import views
 
 # URLs to resolve hydroshare metadata terms urls that are part of the science metadata xml document
 
-urlpatterns = [url(r'^terms/$', views.get_metadata_terms_page, name='get_metadata_terms_page'),
-               url(r'^terms/([A-z]*)/$', views.get_metadata_terms_page, name='get_metadata_terms_page'),
-               ]
+urlpatterns = [
+    path("terms/", views.get_metadata_terms_page, name="get_metadata_terms_page"),
+    re_path(r"^terms/([A-z]*)/$", views.get_metadata_terms_page, name="get_metadata_terms_page"),
+]
diff --git a/hs_core/migrations/0085_alter_baseresource_creator_and_more.py b/hs_core/migrations/0085_alter_baseresource_creator_and_more.py
new file mode 100644
index 0000000000..67e335bebd
--- /dev/null
+++ b/hs_core/migrations/0085_alter_baseresource_creator_and_more.py
@@ -0,0 +1,117 @@
+# Generated by Django 4.2.16 on 2024-09-05 14:50
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('hs_core', '0084_remove_federation_from_db'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='baseresource',
+            name='creator',
+            field=models.ForeignKey(help_text='This is the person who first uploaded the resource', on_delete=django.db.models.deletion.CASCADE, related_name='creator_of_%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AlterField(
+            model_name='baseresource',
+            name='last_changed_by',
+            field=models.ForeignKey(default=1, help_text='The person who last changed the resource', on_delete=django.db.models.deletion.CASCADE, related_name='last_changed_%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AlterField(
+            model_name='baseresource',
+            name='user',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to=settings.AUTH_USER_MODEL, verbose_name='Author'),
+        ),
+        migrations.AlterField(
+            model_name='citation',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='contributor',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='coverage',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='creator',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='date',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='description',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='format',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='fundingagency',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='geospatialrelation',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='identifier',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='language',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='publisher',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='relation',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='rights',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='subject',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='title',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='type',
+            name='content_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_related', to='contenttypes.contenttype'),
+        ),
+    ]
diff --git a/hs_core/resourcemap_urls.py b/hs_core/resourcemap_urls.py
index ee5f04f655..1be781ce43 100644
--- a/hs_core/resourcemap_urls.py
+++ b/hs_core/resourcemap_urls.py
@@ -1,15 +1,22 @@
-from django.conf.urls import url
+from django.urls import re_path
+
 from hs_core import views
 
 urlpatterns = [
-
     # URLs to resolve hydroshare file urls that are part of the resourcemap xml document
-    url(r'^resource/(?P<shortkey>[A-z0-9]+)/data/contents/(.*)/$', views.file_download_url_mapper,
-        name='get_resource_file'),
-
-    url(r'^resource/(?P<shortkey>[A-z0-9]+)/data/([^/]+)/$', views.file_download_url_mapper,
-        name='get_resourcemap_or_metadata_file'),
-
-    url(r'^resource/(?P<shortkey>[A-z0-9]+)/([^/]+)/$', views.file_download_url_mapper,
-        name='get_a_bagit_file'),
+    re_path(
+        r"^resource/(?P<shortkey>[A-z0-9]+)/data/contents/(.*)/$",
+        views.file_download_url_mapper,
+        name="get_resource_file",
+    ),
+    re_path(
+        r"^resource/(?P<shortkey>[A-z0-9]+)/data/([^/]+)/$",
+        views.file_download_url_mapper,
+        name="get_resourcemap_or_metadata_file",
+    ),
+    re_path(
+        r"^resource/(?P<shortkey>[A-z0-9]+)/([^/]+)/$",
+        views.file_download_url_mapper,
+        name="get_a_bagit_file",
+    ),
 ]
diff --git a/hs_core/robots.py b/hs_core/robots.py
index e852f58418..ec02b69ad6 100644
--- a/hs_core/robots.py
+++ b/hs_core/robots.py
@@ -16,7 +16,7 @@ def process_request(self, request):
 
         This is used to filter non-human activity from the usage logs
         """
-        user_agent = request.META.get('HTTP_USER_AGENT', None)
+        user_agent = request.headers.get("user-agent", None)
         request.is_human = True
 
         if user_agent is None or robot_detection.is_robot(user_agent):
diff --git a/hs_core/signals.py b/hs_core/signals.py
index f2f04acdbe..0663f0af10 100644
--- a/hs_core/signals.py
+++ b/hs_core/signals.py
@@ -1,36 +1,30 @@
 import django.dispatch
 
-pre_create_resource = django.dispatch.Signal(providing_args=['metadata', 'files'])
-post_create_resource = django.dispatch.Signal(
-    providing_args=['sender', 'resource', 'user', 'metadata', 'validate_files'])
+pre_create_resource = django.dispatch.Signal()
+post_create_resource = django.dispatch.Signal()
 
-pre_add_files_to_resource = django.dispatch.Signal(providing_args=['files', 'resource'])
-pre_delete_file_from_resource = django.dispatch.Signal(providing_args=['file', 'resource'])
-post_delete_file_from_resource = django.dispatch.Signal(providing_args=['resource'])
+pre_add_files_to_resource = django.dispatch.Signal()
+pre_delete_file_from_resource = django.dispatch.Signal()
+post_delete_file_from_resource = django.dispatch.Signal()
 
-pre_metadata_element_create = django.dispatch.Signal(providing_args=['element_name', 'request'])
-pre_metadata_element_update = django.dispatch.Signal(providing_args=['element_name', 'element_id',
-                                                                     'request'])
-post_metadata_element_update = django.dispatch.Signal(providing_args=['element_name', 'element_id'])
+pre_metadata_element_create = django.dispatch.Signal()
+pre_metadata_element_update = django.dispatch.Signal()
+post_metadata_element_update = django.dispatch.Signal()
 
-pre_download_file = django.dispatch.Signal(providing_args=['sender', 'request', 'resource',
-                                                           'download_file_name', 'file_size'])
-pre_download_resource = django.dispatch.Signal(providing_args=['sender', 'request', 'resource'])
+pre_download_file = django.dispatch.Signal()
+pre_download_resource = django.dispatch.Signal()
 
-pre_check_bag_flag = django.dispatch.Signal(providing_args=['resource'])
+pre_check_bag_flag = django.dispatch.Signal()
 
-pre_delete_resource = django.dispatch.Signal(providing_args=['request', 'user', 'shortkey', 'resource',
-                                                             'resource_title', 'resource_type'])
-pre_move_or_rename_file_or_folder = django.dispatch.Signal(providing_args=['resource',
-                                                                           'src_full_path',
-                                                                           'tgt_full_path'])
+pre_delete_resource = django.dispatch.Signal()
+pre_move_or_rename_file_or_folder = django.dispatch.Signal()
 
-post_remove_file_aggregation = django.dispatch.Signal(providing_args=['resource', 'files'])
-post_add_generic_aggregation = django.dispatch.Signal(providing_args=['resource', 'file'])
-post_add_geofeature_aggregation = django.dispatch.Signal(providing_args=['resource', 'file'])
-post_add_netcdf_aggregation = django.dispatch.Signal(providing_args=['resource', 'file'])
-post_add_raster_aggregation = django.dispatch.Signal(providing_args=['resource', 'file'])
-post_add_reftimeseries_aggregation = django.dispatch.Signal(providing_args=['resource', 'file'])
-post_add_timeseries_aggregation = django.dispatch.Signal(providing_args=['resource', 'file'])
-post_raccess_change = django.dispatch.Signal(providing_args=['resource'])
-post_spam_whitelist_change = django.dispatch.Signal(providing_args=['instance'])
+post_remove_file_aggregation = django.dispatch.Signal()
+post_add_generic_aggregation = django.dispatch.Signal()
+post_add_geofeature_aggregation = django.dispatch.Signal()
+post_add_netcdf_aggregation = django.dispatch.Signal()
+post_add_raster_aggregation = django.dispatch.Signal()
+post_add_reftimeseries_aggregation = django.dispatch.Signal()
+post_add_timeseries_aggregation = django.dispatch.Signal()
+post_raccess_change = django.dispatch.Signal()
+post_spam_whitelist_change = django.dispatch.Signal()
diff --git a/hs_core/templates/pages/baseresource.html b/hs_core/templates/pages/baseresource.html
index 3abff51934..161074c9eb 100644
--- a/hs_core/templates/pages/baseresource.html
+++ b/hs_core/templates/pages/baseresource.html
@@ -15,6 +15,7 @@
     <link rel='stylesheet' type='text/css' href="https://unpkg.com/leaflet-fullscreen@1.0.2/dist/leaflet.fullscreen.css" />
     <link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
 
+
 {# ======= JSON-LD Structure ======= #}
 <script id="schemaorg" type="application/ld+json">
 {
@@ -61,7 +62,7 @@
     ],
     {% endif %}
     "creator": {{ creators | creator_json_ld_element | safe }},
-    {# There is discussion about adding more linked data fields for referenced items #} 
+    {# There is discussion about adding more linked data fields for referenced items #}
     {# https://github.com/hydroshare/hydroshare/issues/4566#issuecomment-1124283370 #}
     {% if geospatial_relations %}
     "about": [
@@ -460,7 +461,7 @@
 
             <div class="col-xs-12 content-block">
                 <h3>
-                  <span>Content</span> 
+                  <span>Content</span>
                   {% if resource_edit_mode %}
                   <a data-toggle="tooltip" data-placement="auto"
                       href="https://www.youtube.com/watch?v=wTPGJY9-k18" target="_blank"
@@ -890,7 +891,7 @@ <h4 class="modal-title">Confirm files deletion</h4>
                                             {% if max_file_size > 0 %}
                                                 <div class="fb-upload-caption">
                                                     <i class="fa fa-upload" aria-hidden="true"></i>&nbsp;&nbsp;
-                                                    Upload files smaller than {{ max_file_size_for_display|floatformat:"0" }}GB in size by dragging & dropping, or click 
+                                                    Upload files smaller than {{ max_file_size_for_display|floatformat:"0" }}GB in size by dragging & dropping, or click
                                                     <a class="dz-clickable upload-toggle">here</a> to select them. Uploads > 1GB are still in beta. Uploads are not resumable.
                                                 </div>
                                             {% endif %}
@@ -1036,7 +1037,7 @@ <h3>Configuration</h3>
       const RES_KEYWORDS = {{ keywords|safe }};
       const RES_FUNDING_AGENCIES = [
         {% for agency in fundingagencies %}
-            {   
+            {
                 "agency_id": "{{ agency.id }}",
                 "award_number": "{{ agency.award_number }}",
                 "award_title": "{{ agency.award_title | escapejs }}",
@@ -1054,7 +1055,6 @@ <h3>Configuration</h3>
       const RESOURCE_MODE = "{% if resource_edit_mode %}Edit{% else %}View{% endif %}";
       const RESOURCE_PUBLISHED_OR_UNDER_REVIEW = "{{ cm.raccess.published_or_review_pending }}" === "True";
       const CURRENT_USER_ID = {{ request.user.id|default:"null" }};
-      let STATIC_URL = "{{ STATIC_URL }}";
       const USERS_JSON = {{ users_json|safe }};
       const AUTHORS = [
           {% for author in creators %}
@@ -1118,6 +1118,6 @@ <h3>Configuration</h3>
     <script type="text/javascript" charset="utf8" src="{% static 'js/jquery.dataTables.js' %}"></script>
     <script type="text/javascript" src="{% static 'js/copyToClipboard.js' %}"></script>
     <script type="text/javascript" src="{% static 'js/generic-resource.js' %}"></script>
-
+    {#the following loads the asset for the DAL autocomplete#}
+    {{ add_view_resource_permission_form.media }}
 {% endblock %}
-
diff --git a/hs_core/templates/pages/my-groups.html b/hs_core/templates/pages/my-groups.html
index 1d9f219b62..fca1421bc5 100644
--- a/hs_core/templates/pages/my-groups.html
+++ b/hs_core/templates/pages/my-groups.html
@@ -207,4 +207,9 @@ <h4><a href="/group/{{ group.id }}" class="text-danger">{{ group.name }}</a></h4
 {{ block.super }}
 <script type="text/javascript" src="{% static 'js/bootstrap-formhelpers.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/my-groups.js' %}"></script>
+<script>
+    // https://github.com/select2/select2/issues/1436#issuecomment-21028474
+    // incompatibility between Select2 and Bootstrap modals
+    $.fn.modal.Constructor.prototype.enforceFocus = function() {};
+</script>
 {% endblock %}
\ No newline at end of file
diff --git a/hs_core/urls.py b/hs_core/urls.py
index fa32deea27..12bbd9db66 100755
--- a/hs_core/urls.py
+++ b/hs_core/urls.py
@@ -1,126 +1,299 @@
-from django.conf.urls import url
+from django.urls import path, re_path
+
 from hs_core import views
 from hs_core.views.autocomplete import autocomplete
 
 urlpatterns = [
     # internal API
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/add-files-to-resource/$',
-        views.add_files_to_resource, name='add_files_to_resource'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/change-quota-holder/$',
-        views.change_quota_holder, name='change_quota_holder'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/(?P<element_name>[A-z]+)/add-metadata/$',
-        views.add_metadata_element, name='add_metadata_element'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/(?P<element_name>[A-z]+)/(?P<element_id>[A-z0-9]+)/update-metadata/$',
-        views.update_metadata_element, name='update_metadata_element'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/(?P<element_name>[A-z]+)/(?P<element_id>[A-z0-9]+)/delete-metadata/$',
-        views.delete_metadata_element, name='delete_metadata_element'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/delete-author/(?P<element_id>[A-z0-9]+)/$',
-        views.delete_author, name='delete_author'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/get-metadata/$',
-        views.get_resource_metadata, name='get_metadata'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/update-key-value-metadata/$',
-        views.update_key_value_metadata, name="update_key_value_metadata"),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/relevant-tools/$',
-        views.get_relevant_tools, name="get_relevant_tools"),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/delete-resource-file/(?P<f>[0-9]+)/$',
-        views.delete_file, name='delete_file'),
-    url(r'^_internal/(?P<shortkey>[A-z0-9]+)/delete-multiple-files/$',
-        views.delete_multiple_files, name='delete_multiple_files'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/delete-resource/(?P<usertext>[A-z]+)/$',
-        views.delete_resource, name='delete_resource'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/create-new-version-resource/$',
-        views.create_new_version_resource, name='create_resource_version'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/copy-resource/$', views.copy_resource,
-        name='copy_resource'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/list-referenced-content/$', views.list_referenced_content,
-        name='list_referenced_content'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/set-resource-flag/$',
-        views.set_resource_flag, name='set_resource_flag'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/share-resource-with-user/(?P<privilege>[a-z]+)/(?P<user_id>[0-9]+)/$',
-        views.share_resource_with_user, name='share_resource_with_user'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/unshare-resource-with-user/(?P<user_id>[0-9]+)/$',
-        views.unshare_resource_with_user, name='unshare_resource_with_user'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/undo-share-resource-with-user/(?P<user_id>[0-9]+)/$',
-        views.undo_share_resource_with_user, name='undo_share_resource_with_user'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/share-resource-with-group/(?P<privilege>[a-z]+)/(?P<group_id>[0-9]+)/$',
-        views.share_resource_with_group, name='share_resource_with_group'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/unshare-resource-with-group/(?P<group_id>[0-9]+)/$',
-        views.unshare_resource_with_group, name='unshare_resource_with_group'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/undo-share-resource-with-group/(?P<group_id>[0-9]+)/$',
-        views.undo_share_resource_with_group, name='undo_share_resource_with_group'),
-    url(r'^_internal/create-user-group/$', views.create_user_group, name='create_user_group'),
-    url(r'^_internal/request-new-community/$', views.request_new_community, name='request_new_community'),
-    url(r'^_internal/update-user-community/(?P<community_id>[0-9]+)$', views.update_user_community,
-        name='update_user_community'),
-    url(r'^_internal/update-user-group/(?P<group_id>[0-9]+)$', views.update_user_group,
-        name='update_user_group'),
-    url(r'^_internal/delete-user-group/(?P<group_id>[0-9]+)$', views.delete_user_group,
-        name='delete_user_group'),
-    url(r'^_internal/restore-user-group/(?P<group_id>[0-9]+)$', views.restore_user_group,
-        name='restore_user_group'),
-    url(r'^_internal/share-group-with-user/(?P<group_id>[0-9]+)/(?P<user_id>[0-9]+)/(?P<privilege>[a-z]+)/$',
-        views.share_group_with_user, name='share_group_with_user'),
-    url(r'^_internal/unshare-group-with-user/(?P<group_id>[0-9]+)/(?P<user_id>[0-9]+)/$',
-        views.unshare_group_with_user, name='unshare_group_with_user'),
-    url(r'^_internal/make-group-membership-request/(?P<group_id>[0-9]+)/(?P<user_id>[0-9]+)/$',
-        views.make_group_membership_request, name='make_group_membership_request'),
-    url(r'^_internal/make-group-membership-request/(?P<group_id>[0-9]+)/$',
-        views.make_group_membership_request, name='make_group_membership_request'),
-    url(r'^_internal/act-on-group-membership-request/(?P<membership_request_id>[0-9]+)/(?P<action>[a-z]+)/$',
-        views.act_on_group_membership_request, name='act_on_group_membership_request'),
-    url(r'^_internal/group_membership/(?P<token>[-\w]+)/(?P<uidb36>[-\w]+)/(?P<membership_request_id>[0-9]+)/',
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/add-files-to-resource/$",
+        views.add_files_to_resource,
+        name="add_files_to_resource",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/change-quota-holder/$",
+        views.change_quota_holder,
+        name="change_quota_holder",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/(?P<element_name>[A-z]+)/add-metadata/$",
+        views.add_metadata_element,
+        name="add_metadata_element",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/(?P<element_name>[A-z]+)/(?P<element_id>[A-z0-9]+)/update-metadata/$",
+        views.update_metadata_element,
+        name="update_metadata_element",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/(?P<element_name>[A-z]+)/(?P<element_id>[A-z0-9]+)/delete-metadata/$",
+        views.delete_metadata_element,
+        name="delete_metadata_element",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/delete-author/(?P<element_id>[A-z0-9]+)/$",
+        views.delete_author,
+        name="delete_author",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/get-metadata/$",
+        views.get_resource_metadata,
+        name="get_metadata",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/update-key-value-metadata/$",
+        views.update_key_value_metadata,
+        name="update_key_value_metadata",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/relevant-tools/$",
+        views.get_relevant_tools,
+        name="get_relevant_tools",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/delete-resource-file/(?P<f>[0-9]+)/$",
+        views.delete_file,
+        name="delete_file",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[A-z0-9]+)/delete-multiple-files/$",
+        views.delete_multiple_files,
+        name="delete_multiple_files",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/delete-resource/(?P<usertext>[A-z]+)/$",
+        views.delete_resource,
+        name="delete_resource",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/create-new-version-resource/$",
+        views.create_new_version_resource,
+        name="create_resource_version",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/copy-resource/$",
+        views.copy_resource,
+        name="copy_resource",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/list-referenced-content/$",
+        views.list_referenced_content,
+        name="list_referenced_content",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/set-resource-flag/$",
+        views.set_resource_flag,
+        name="set_resource_flag",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/share-resource-with-user/(?P<privilege>[a-z]+)/(?P<user_id>[0-9]+)/$",
+        views.share_resource_with_user,
+        name="share_resource_with_user",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/unshare-resource-with-user/(?P<user_id>[0-9]+)/$",
+        views.unshare_resource_with_user,
+        name="unshare_resource_with_user",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/undo-share-resource-with-user/(?P<user_id>[0-9]+)/$",
+        views.undo_share_resource_with_user,
+        name="undo_share_resource_with_user",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/share-resource-with-group/(?P<privilege>[a-z]+)/(?P<group_id>[0-9]+)/$",
+        views.share_resource_with_group,
+        name="share_resource_with_group",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/unshare-resource-with-group/(?P<group_id>[0-9]+)/$",
+        views.unshare_resource_with_group,
+        name="unshare_resource_with_group",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/undo-share-resource-with-group/(?P<group_id>[0-9]+)/$",
+        views.undo_share_resource_with_group,
+        name="undo_share_resource_with_group",
+    ),
+    path(
+        "_internal/create-user-group/",
+        views.create_user_group,
+        name="create_user_group",
+    ),
+    path(
+        "_internal/request-new-community/",
+        views.request_new_community,
+        name="request_new_community",
+    ),
+    path(
+        "_internal/update-user-community/<int:community_id>",
+        views.update_user_community,
+        name="update_user_community",
+    ),
+    path(
+        "_internal/update-user-group/<int:group_id>",
+        views.update_user_group,
+        name="update_user_group",
+    ),
+    path(
+        "_internal/delete-user-group/<int:group_id>",
+        views.delete_user_group,
+        name="delete_user_group",
+    ),
+    path(
+        "_internal/restore-user-group/<int:group_id>",
+        views.restore_user_group,
+        name="restore_user_group",
+    ),
+    re_path(
+        r"^_internal/share-group-with-user/(?P<group_id>[0-9]+)/(?P<user_id>[0-9]+)/(?P<privilege>[a-z]+)/$",
+        views.share_group_with_user,
+        name="share_group_with_user",
+    ),
+    path(
+        "_internal/unshare-group-with-user/<int:group_id>/<int:user_id>/",
+        views.unshare_group_with_user,
+        name="unshare_group_with_user",
+    ),
+    path(
+        "_internal/make-group-membership-request/<int:group_id>/<int:user_id>/",
+        views.make_group_membership_request,
+        name="make_group_membership_request",
+    ),
+    path(
+        "_internal/make-group-membership-request/<int:group_id>/",
+        views.make_group_membership_request,
+        name="make_group_membership_request",
+    ),
+    re_path(
+        r"^_internal/act-on-group-membership-request/(?P<membership_request_id>[0-9]+)/(?P<action>[a-z]+)/$",
+        views.act_on_group_membership_request,
+        name="act_on_group_membership_request",
+    ),
+    re_path(
+        r"^_internal/group_membership/(?P<token>[-\w]+)/(?P<uidb36>[-\w]+)/(?P<membership_request_id>[0-9]+)/",
         views.group_membership,
-        name='group_membership'),
-    url(r'^_internal/metadata_review/(?P<shortkey>[0-9a-f-]+)/\
-        (?P<action>[a-z]+)/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[-\w]+)/',
+        name="group_membership",
+    ),
+    re_path(
+        r"^_internal/metadata_review/(?P<shortkey>[0-9a-f-]+)/"
+        r"(?P<action>[a-z]+)/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[-\w]+)/",
         views.metadata_review,
-        name='metadata_review'),
-    url(r'^_internal/metadata_review/(?P<shortkey>[0-9a-f-]+)/(?P<action>[a-z]+)/',
+        name="metadata_review"
+    ),
+    re_path(
+        r"^_internal/metadata_review/(?P<shortkey>[0-9a-f-]+)/(?P<action>[a-z]+)/",
         views.metadata_review,
-        name='metadata_review_noauth'),
-    url(r'^_internal/spam_allowlist/(?P<shortkey>[0-9a-f-]+)/(?P<action>[a-z]+)/',
+        name="metadata_review_noauth",
+    ),
+    re_path(
+        r"^_internal/spam_allowlist/(?P<shortkey>[0-9a-f-]+)/(?P<action>[a-z]+)/",
         views.spam_allowlist,
-        name='spam_allowlist'),
-    url(r'^_internal/get-user-or-group-data/(?P<user_or_group_id>[0-9]+)/(?P<is_group>[a-z]+)$',
-        views.get_user_or_group_data, name='get_user_or_group_data'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/publish/$', views.publish),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/submit-for-review/$', views.submit_for_review),
-    url(r'^_internal/create-resource/do/$', views.create_resource, name='create_resource'),
-    url(r'^_internal/verify-account/$', views.verify_account),
-    url(r'^_internal/resend_verification_email/$', views.resend_verification_email),
-    url(r'^_internal/(?P<resource_type>[A-z]+)/supported-file-types/$',
-        views.get_supported_file_types_for_resource_type, name='resource_type_file_types'),
-    url(r'^_internal/(?P<resource_type>[A-z]+)/allow-multiple-file/$',
-        views.is_multiple_file_upload_allowed, name="resource_type_multiple_file_upload"),
-    url(r'^_internal/search/autocomplete/', autocomplete),
-    url(r'^_internal/data-store-structure/$', views.resource_folder_hierarchy.data_store_structure),
-    url(r'^_internal/data-store-folder-zip/$',
-        views.resource_folder_hierarchy.data_store_folder_zip, name="zip_folder"),
-    url(r'^_internal/zip-by-aggregation-file/$',
-        views.resource_folder_hierarchy.zip_aggregation_file, name="zip_by_aggr_file"),
-    url(r'^_internal/data-store-folder-unzip/$',
-        views.resource_folder_hierarchy.data_store_folder_unzip),
-    url(r'^_internal/data-store-create-folder/$',
-        views.resource_folder_hierarchy.data_store_create_folder),
-    url(r'^_internal/data-store-add-reference/$',
-        views.resource_folder_hierarchy.data_store_add_reference),
-    url(r'^_internal/data-store-edit-reference-url/$',
-        views.resource_folder_hierarchy.data_store_edit_reference_url),
-    url(r'^_internal/data-store-move-or-rename/$',
-        views.resource_folder_hierarchy.data_store_file_or_folder_move_or_rename),
-    url(r'^_internal/data-store-move-to-folder/$',
-        views.resource_folder_hierarchy.data_store_move_to_folder),
-    url(r'^_internal/data-store-rename-file-or-folder/$',
-        views.resource_folder_hierarchy.data_store_rename_file_or_folder),
-    url(r'^_internal/data-store-delete-folder/$',
-        views.resource_folder_hierarchy.data_store_remove_folder),
-    url(r'^_internal/update_quota_usage/(?P<username>[\w.@+-]+)/$',
-        views.update_quota_usage, name='update_quota_usage'),
-    url(r'^_internal/(?P<shortkey>[0-9a-f-]+)/non-preferred-paths/$', views.get_non_preferred_paths,
-        name="get_non_preferred_paths"),
-    url(r'^_internal/get_tasks_by_user/$', views.get_tasks_by_user, name='get_tasks_by_user'),
-    url(r'^_internal/get_task/(?P<task_id>[0-9a-f-]+)$', views.get_task, name='get_task'),
-    url(r'^_internal/abort_task/(?P<task_id>[0-9a-f-]+)$', views.abort_task, name='abort_task'),
-    url(r'^_internal/dismiss_task/(?P<task_id>[0-9a-f-]+)$', views.dismiss_task, name='dismiss_task'),
-    url(r'^_internal/set_task_delivered/(?P<task_id>[0-9a-f-]+)$', views.set_task_delivered, name='set_task_delivered'),
+        name="spam_allowlist",
+    ),
+    re_path(
+        r"^_internal/get-user-or-group-data/(?P<user_or_group_id>[0-9]+)/(?P<is_group>[a-z]+)$",
+        views.get_user_or_group_data,
+        name="get_user_or_group_data",
+    ),
+    re_path(r"^_internal/(?P<shortkey>[0-9a-f-]+)/publish/$", views.publish),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/submit-for-review/$",
+        views.submit_for_review,
+    ),
+    path(
+        "_internal/create-resource/do/", views.create_resource, name="create_resource"
+    ),
+    path("_internal/verify-account/", views.verify_account),
+    path("_internal/resend_verification_email/", views.resend_verification_email),
+    re_path(
+        r"^_internal/(?P<resource_type>[A-z]+)/supported-file-types/$",
+        views.get_supported_file_types_for_resource_type,
+        name="resource_type_file_types",
+    ),
+    re_path(
+        r"^_internal/(?P<resource_type>[A-z]+)/allow-multiple-file/$",
+        views.is_multiple_file_upload_allowed,
+        name="resource_type_multiple_file_upload",
+    ),
+    re_path(r"^_internal/search/autocomplete/", autocomplete),
+    path(
+        "_internal/data-store-structure/",
+        views.resource_folder_hierarchy.data_store_structure,
+    ),
+    path(
+        "_internal/data-store-folder-zip/",
+        views.resource_folder_hierarchy.data_store_folder_zip,
+        name="zip_folder",
+    ),
+    path(
+        "_internal/zip-by-aggregation-file/",
+        views.resource_folder_hierarchy.zip_aggregation_file,
+        name="zip_by_aggr_file",
+    ),
+    path(
+        "_internal/data-store-folder-unzip/",
+        views.resource_folder_hierarchy.data_store_folder_unzip,
+    ),
+    path(
+        "_internal/data-store-create-folder/",
+        views.resource_folder_hierarchy.data_store_create_folder,
+    ),
+    path(
+        "_internal/data-store-add-reference/",
+        views.resource_folder_hierarchy.data_store_add_reference,
+    ),
+    path(
+        "_internal/data-store-edit-reference-url/",
+        views.resource_folder_hierarchy.data_store_edit_reference_url,
+    ),
+    path(
+        "_internal/data-store-move-or-rename/",
+        views.resource_folder_hierarchy.data_store_file_or_folder_move_or_rename,
+    ),
+    path(
+        "_internal/data-store-move-to-folder/",
+        views.resource_folder_hierarchy.data_store_move_to_folder,
+    ),
+    path(
+        "_internal/data-store-rename-file-or-folder/",
+        views.resource_folder_hierarchy.data_store_rename_file_or_folder,
+    ),
+    path(
+        "_internal/data-store-delete-folder/",
+        views.resource_folder_hierarchy.data_store_remove_folder,
+    ),
+    re_path(
+        r"^_internal/update_quota_usage/(?P<username>[\w.@+-]+)/$",
+        views.update_quota_usage,
+        name="update_quota_usage",
+    ),
+    re_path(
+        r"^_internal/(?P<shortkey>[0-9a-f-]+)/non-preferred-paths/$",
+        views.get_non_preferred_paths,
+        name="get_non_preferred_paths",
+    ),
+    path(
+        "_internal/get_tasks_by_user/",
+        views.get_tasks_by_user,
+        name="get_tasks_by_user",
+    ),
+    re_path(
+        r"^_internal/get_task/(?P<task_id>[0-9a-f-]+)$", views.get_task, name="get_task"
+    ),
+    re_path(
+        r"^_internal/abort_task/(?P<task_id>[0-9a-f-]+)$",
+        views.abort_task,
+        name="abort_task",
+    ),
+    re_path(
+        r"^_internal/dismiss_task/(?P<task_id>[0-9a-f-]+)$",
+        views.dismiss_task,
+        name="dismiss_task",
+    ),
+    re_path(
+        r"^_internal/set_task_delivered/(?P<task_id>[0-9a-f-]+)$",
+        views.set_task_delivered,
+        name="set_task_delivered",
+    ),
 ]
diff --git a/hs_core/views/__init__.py b/hs_core/views/__init__.py
index 75484d8a47..518c3fa029 100755
--- a/hs_core/views/__init__.py
+++ b/hs_core/views/__init__.py
@@ -2,7 +2,7 @@
 import json
 import logging
 
-from autocomplete_light import shortcuts as autocomplete_light
+from dal import autocomplete as dal_autocomplete
 from dateutil import tz
 from django import forms
 from django.contrib import messages
@@ -52,7 +52,6 @@
     update_quota_usage as update_quota_usage_utility,
 )
 from hs_core.hydroshare.utils import (
-    get_resource_by_shortkey,
     resolve_request,
     resource_modified,
 )
@@ -99,6 +98,7 @@
     send_action_to_take_email,
     upload_from_irods,
     get_default_support_user,
+    is_ajax,
 )
 
 logger = logging.getLogger(__name__)
@@ -428,26 +428,26 @@ def _get_resource_sender(element_name, resource):
 
 def get_supported_file_types_for_resource_type(request, resource_type, *args, **kwargs):
     resource_cls = hydroshare.check_resource_type(resource_type)
-    if request.is_ajax:
+    if is_ajax(request):
         # TODO: use try catch
         ajax_response_data = {
             "file_types": json.dumps(resource_cls.get_supported_upload_file_types())
         }
         return HttpResponse(json.dumps(ajax_response_data))
     else:
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
 
 def is_multiple_file_upload_allowed(request, resource_type, *args, **kwargs):
     resource_cls = hydroshare.check_resource_type(resource_type)
-    if request.is_ajax:
+    if is_ajax(request):
         # TODO: use try catch
         ajax_response_data = {
             "allow_multiple_file": resource_cls.allow_multiple_file_upload()
         }
         return HttpResponse(json.dumps(ajax_response_data))
     else:
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
 
 def get_relevant_tools(request, shortkey, *args, **kwargs):
@@ -488,7 +488,7 @@ def update_key_value_metadata(request, shortkey, *args, **kwargs):
         res_metadata = res.metadata
         res_metadata.set_dirty(True)
 
-    if request.is_ajax():
+    if is_ajax(request):
         if is_update_success:
             ajax_response_data = {
                 "status": "success",
@@ -508,7 +508,7 @@ def update_key_value_metadata(request, shortkey, *args, **kwargs):
     else:
         messages.error(request, err_message)
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 res_id = openapi.Parameter(
@@ -650,7 +650,7 @@ def add_metadata_element(request, shortkey, element_name, *args, **kwargs):
                 elif "errors" in response:
                     err_msg = err_msg.format(element_name, response["errors"])
 
-    if request.is_ajax():
+    if is_ajax(request):
         if is_add_success:
             res_public_status = "public" if res.raccess.public else "not public"
             res_discoverable_status = (
@@ -697,7 +697,7 @@ def add_metadata_element(request, shortkey, element_name, *args, **kwargs):
     if "resource-mode" in request.POST:
         request.session["resource-mode"] = "edit"
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def get_resource_metadata(request, shortkey, *args, **kwargs):
@@ -786,7 +786,7 @@ def update_metadata_element(
             elif "errors" in response:
                 err_msg = err_msg.format(element_name, response["errors"])
 
-    if request.is_ajax():
+    if is_ajax(request):
         if is_update_success:
             res_public_status = "public" if res.raccess.public else "not public"
             res_discoverable_status = (
@@ -835,7 +835,7 @@ def update_metadata_element(
     if "resource-mode" in request.POST:
         request.session["resource-mode"] = "edit"
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @swagger_auto_schema(method="get", auto_schema=None)
@@ -884,7 +884,7 @@ def delete_metadata_element(
     res.update_public_and_discoverable()
     resource_modified(res, request.user, overwrite_bag=False)
     request.session["resource-mode"] = "edit"
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def delete_author(request, shortkey, element_id, *args, **kwargs):
@@ -920,7 +920,7 @@ def delete_file(request, shortkey, f, *args, **kwargs):
     else:
         request.session["meta-status"] = METADATA_STATUS_INSUFFICIENT
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def delete_multiple_files(request, shortkey, *args, **kwargs):
@@ -940,7 +940,7 @@ def delete_multiple_files(request, shortkey, *args, **kwargs):
             request.session["resource-mode"] = "edit"
             request.session["validation_error"] = str(err)
             logger.warning(str(err))
-            return HttpResponseRedirect(request.META["HTTP_REFERER"])
+            return HttpResponseRedirect(request.headers["referer"])
         except ObjectDoesNotExist as ex:
             # Since some specific resource types such as feature resource type delete all other
             # dependent content files together when one file is deleted, we make this specific
@@ -956,7 +956,7 @@ def delete_multiple_files(request, shortkey, *args, **kwargs):
     else:
         request.session["meta-status"] = METADATA_STATUS_INSUFFICIENT
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def delete_resource(request, shortkey, usertext, *args, **kwargs):
@@ -975,7 +975,7 @@ def delete_resource(request, shortkey, usertext, *args, **kwargs):
         )
 
     res_title = res.metadata.title
-    if request.is_ajax():
+    if is_ajax(request):
         task_id = get_resource_delete_task(shortkey)
         if not task_id:
             # make resource being deleted not discoverable to inform solr to remove this resource from solr index
@@ -1020,7 +1020,7 @@ def delete_resource(request, shortkey, usertext, *args, **kwargs):
         except ValidationError as ex:
             request.session["validation_error"] = str(ex)
             logger.warning(str(ex))
-            return HttpResponseRedirect(request.META["HTTP_REFERER"])
+            return HttpResponseRedirect(request.headers["referer"])
 
 
 def list_referenced_content(request, shortkey, *args, **kwargs):
@@ -1043,7 +1043,7 @@ def copy_resource(request, shortkey, *args, **kwargs):
     res, authorized, user = authorize(
         request, shortkey, needed_permission=ACTION_TO_AUTHORIZE.VIEW_RESOURCE
     )
-    if request.is_ajax():
+    if is_ajax(request):
         task = copy_resource_task.apply_async((shortkey, None, user.username))
         task_id = task.task_id
         task_dict = get_or_create_task_notification(
@@ -1110,7 +1110,7 @@ def create_new_version_resource(request, shortkey, *args, **kwargs):
     # obsoleted resource is allowed
     res.locked_time = datetime.datetime.now(tz.UTC)
     res.save()
-    if request.is_ajax():
+    if is_ajax(request):
         task = create_new_version_resource_task.apply_async((shortkey, user.username))
         task_id = task.task_id
         task_dict = get_or_create_task_notification(
@@ -1166,7 +1166,7 @@ def publish(request, shortkey, *args, **kwargs):
     except ValidationError as exp:
         request.session["validation_error"] = str(exp)
         logger.warning(str(exp))
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def submit_for_review(request, shortkey, *args, **kwargs):
@@ -1181,7 +1181,7 @@ def submit_for_review(request, shortkey, *args, **kwargs):
                 and adherence to community guidelines. {' '.join(missing)}
                 """
         messages.error(request, message)
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     try:
         hydroshare.submit_resource_for_review(shortkey, request.user)
@@ -1200,7 +1200,7 @@ def submit_for_review(request, shortkey, *args, **kwargs):
                 If you decide that you no longer wish to have this resource reviewed for publication,
                 please contact help@cuahsi.org"""
         messages.success(request, message)
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def set_resource_flag(request, shortkey, *args, **kwargs):
@@ -1605,81 +1605,56 @@ def update(self, group_to_update, request):
         self._set_privacy_level(group_to_update, privacy_level)
 
 
-class PatchedChoiceWidget(autocomplete_light.ChoiceWidget):
-    """Patching the render() function of ChoiceWidget class to work with Django 3.2"""
-
-    def __init__(
-        self,
-        autocomplete=None,
-        widget_js_attributes=None,
-        autocomplete_js_attributes=None,
-        extra_context=None,
-        registry=None,
-        widget_template=None,
-        widget_attrs=None,
-        *args,
-        **kwargs,
-    ):
-        super(PatchedChoiceWidget, self).__init__(
-            autocomplete,
-            widget_js_attributes,
-            autocomplete_js_attributes,
-            extra_context,
-            registry,
-            widget_template,
-            widget_attrs,
-            *args,
-            **kwargs,
-        )
-
-    def render(self, name, value, attrs=None, renderer=None):
-        """Adding the 'renderer' parameter to fix the exception
-        'render() got an unexpected keyword argument 'renderer'"""
-
-        return super(PatchedChoiceWidget, self).render(name, value, attrs)
-
-
 @processor_for(BaseResource)
 def add_generic_context(request, page):
+    user_widget = dal_autocomplete.ModelSelect2(
+        url="user-autocomplete",
+        attrs={
+            "data-placeholder": "Search by name or username",
+            'data-minimum-input-length': 3
+        },
+    )
 
     class AddUserForm(forms.Form):
-        user = forms.ModelChoiceField(
-            User.objects.filter(is_active=True).all(),
-            widget=PatchedChoiceWidget(
-                autocomplete="UserAutocomplete", attrs={"id": "user"}
-            ),
+        user_invite_to_group = forms.ModelChoiceField(
+            queryset=User.objects.none(),
+            widget=user_widget,
         )
 
     class AddUserContriForm(forms.Form):
-        user = forms.ModelChoiceField(
-            User.objects.filter(is_active=True).all(),
-            widget=PatchedChoiceWidget(
-                autocomplete="UserAutocomplete", attrs={"id": "user"}
-            ),
+        contributor = forms.ModelChoiceField(
+            queryset=User.objects.none(),
+            widget=user_widget,
         )
 
     class AddUserInviteForm(forms.Form):
         user = forms.ModelChoiceField(
-            User.objects.filter(is_active=True).all(),
-            widget=PatchedChoiceWidget(
-                autocomplete="UserAutocomplete", attrs={"id": "user"}
-            ),
+            queryset=User.objects.none(),
+            widget=user_widget,
+        )
+
+    class AddUserResourcePermissionForm(forms.Form):
+        user_resource_permission = forms.ModelChoiceField(
+            queryset=User.objects.none(),
+            widget=user_widget,
         )
 
     class AddUserHSForm(forms.Form):
-        user = forms.ModelChoiceField(
-            User.objects.filter(is_active=True).all(),
-            widget=PatchedChoiceWidget(
-                autocomplete="UserAutocomplete", attrs={"id": "user"}
-            ),
+        author = forms.ModelChoiceField(
+            queryset=User.objects.none(),
+            widget=user_widget,
         )
 
     class AddGroupForm(forms.Form):
         group = forms.ModelChoiceField(
-            Group.objects.filter(gaccess__active=True)
-            .exclude(name="Hydroshare Author")
-            .all(),
-            widget=PatchedChoiceWidget(autocomplete="GroupAutocomplete"),
+            queryset=Group.objects.none(),
+            widget=dal_autocomplete.ModelSelect2(
+                url="group-autocomplete",
+                attrs={
+                    "data-placeholder": "Search by name",
+                    'data-minimum-input-length': 3
+                }
+            ),
         )
 
     return {
@@ -1687,9 +1662,9 @@ class AddGroupForm(forms.Form):
         "add_view_invite_user_form": AddUserInviteForm(),
         "add_view_hs_user_form": AddUserHSForm(),
         "add_view_user_form": AddUserForm(),
+        "add_view_resource_permission_form": AddUserResourcePermissionForm(),
         # Reuse the same class AddGroupForm() leads to duplicated IDs.
         "add_view_group_form": AddGroupForm(),
-        "add_edit_group_form": AddGroupForm(),
     }
 
 
@@ -1795,7 +1770,7 @@ def create_user_group(request, *args, **kwargs):
             request, "Group creation errors:{}.".format(group_form.errors.as_json)
         )
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -1818,7 +1793,7 @@ def update_user_community(request, community_id, *args, **kwargs):
     else:
         messages.error(request, "Community update errors:{}.".format(community_form.errors.as_json))
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -1850,7 +1825,7 @@ def update_user_group(request, group_id, *args, **kwargs):
             "Group update errors: You don't have permission to update this group",
         )
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -1866,7 +1841,7 @@ def delete_user_group(request, group_id, *args, **kwargs):
             "Group delete errors: You don't have permission to delete" " this group.",
         )
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -1881,7 +1856,7 @@ def restore_user_group(request, group_id, *args, **kwargs):
             "Group restore errors: You don't have permission to restore" " this group.",
         )
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -1912,7 +1887,7 @@ def share_group_with_user(request, group_id, user_id, privilege, *args, **kwargs
     else:
         messages.error(request, "Invalid privilege for sharing group with user")
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -1944,7 +1919,7 @@ def unshare_group_with_user(request, group_id, user_id, *args, **kwargs):
     if requesting_user == user_to_unshare_with:
         return HttpResponseRedirect(reverse("my_groups"))
     else:
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -2202,7 +2177,7 @@ def act_on_group_membership_request(
         else:
             messages.error(request, "Group is not active")
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -2638,8 +2613,16 @@ def get_groups_with_ownership(ugp_queryset, user):
 
 
 class AddUserForm(forms.Form):
-    user = forms.ModelChoiceField(
-        User.objects.all(), widget=PatchedChoiceWidget("UserAutocomplete")
+    user_widget = dal_autocomplete.ModelSelect2(
+        url="user-autocomplete",
+        attrs={
+            "data-placeholder": "Search by name or username",
+            'data-minimum-input-length': 3
+        },
+    )
+    user_invite_to_group = forms.ModelChoiceField(
+        queryset=User.objects.none(),
+        widget=user_widget,
     )
 
 
@@ -2667,7 +2650,7 @@ def request_new_community(request, *args, **kwargs):
     else:
         messages.error(request, "Community request errors:{}.".format(community_form.errors.as_json))
 
-    return HttpResponseRedirect(request.META['HTTP_REFERER'])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 # Any HydroShare user (whether authenticated or not) can access the landing page for a public or discoverable Group.
@@ -2829,7 +2812,7 @@ def my_resources(request, *args, **kwargs):
     Renders either a full my-resources page, or just a table of new resources
     """
 
-    if not request.is_ajax():
+    if not is_ajax(request):
         filter = request.GET.getlist("f", default=[])
     else:
         filter = [request.GET["new_filter"]]
@@ -2848,7 +2831,7 @@ def my_resources(request, *args, **kwargs):
 
     context = {"collection": resource_collection}
 
-    if not request.is_ajax():
+    if not is_ajax(request):
         return render(request, "pages/my-resources.html", context)
     else:
         from django.template.loader import render_to_string
diff --git a/hs_core/views/resource_folder_hierarchy.py b/hs_core/views/resource_folder_hierarchy.py
index ddd49dc1c3..622a987d71 100755
--- a/hs_core/views/resource_folder_hierarchy.py
+++ b/hs_core/views/resource_folder_hierarchy.py
@@ -2,32 +2,35 @@
 import logging
 import os
 
-from django.core.exceptions import SuspiciousFileOperation, ValidationError, ObjectDoesNotExist
-from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
-
+from django.core.exceptions import (ObjectDoesNotExist,
+                                    SuspiciousFileOperation, ValidationError)
+from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
+from drf_yasg import openapi
+from drf_yasg.utils import swagger_auto_schema
 from rest_framework.decorators import api_view
-from rest_framework.exceptions import NotFound, status, PermissionDenied, \
-    ValidationError as DRF_ValidationError
+from rest_framework.exceptions import NotFound, PermissionDenied
+from rest_framework.exceptions import ValidationError as DRF_ValidationError
+from rest_framework.exceptions import status
 from rest_framework.response import Response
 
 from django_irods.icommands import SessionException
 from hs_core.hydroshare import delete_resource_file
-from hs_core.hydroshare.utils import get_file_mime_type, resolve_request, QuotaException
+from hs_core.hydroshare.utils import (QuotaException, get_file_mime_type,
+                                      resolve_request)
 from hs_core.models import ResourceFile
 from hs_core.task_utils import get_or_create_task_notification
 from hs_core.tasks import FileOverrideException, unzip_task
 from hs_core.views import utils as view_utils
-
-from hs_core.views.utils import authorize, ACTION_TO_AUTHORIZE, zip_folder, unzip_file, \
-    create_folder, remove_folder, move_or_rename_file_or_folder, move_to_folder, \
-    rename_file_or_folder, irods_path_is_directory, \
-    add_reference_url_to_resource, edit_reference_url_in_resource, zip_by_aggregation_file
-
-from hs_file_types.models import FileSetLogicalFile, ModelInstanceLogicalFile, ModelProgramLogicalFile
-
-from drf_yasg.utils import swagger_auto_schema
-from drf_yasg import openapi
-
+from hs_core.views.utils import (ACTION_TO_AUTHORIZE,
+                                 add_reference_url_to_resource, authorize,
+                                 create_folder, edit_reference_url_in_resource,
+                                 irods_path_is_directory, is_ajax,
+                                 move_or_rename_file_or_folder, move_to_folder,
+                                 remove_folder, rename_file_or_folder,
+                                 unzip_file, zip_by_aggregation_file,
+                                 zip_folder)
+from hs_file_types.models import (FileSetLogicalFile, ModelInstanceLogicalFile,
+                                  ModelProgramLogicalFile)
 
 logger = logging.getLogger(__name__)
 
@@ -453,7 +456,7 @@ def data_store_folder_unzip(request, **kwargs):
     remove_original_zip = request.POST.get('remove_original_zip', 'true').lower() == 'true'
     unzip_to_folder = request.POST.get('unzip_to_folder', 'false').lower() == 'true'
 
-    if request.is_ajax():
+    if is_ajax(request):
         task = unzip_task.apply_async((user.pk, res_id, zip_with_rel_path, remove_original_zip, overwrite,
                                        auto_aggregate, ingest_metadata, unzip_to_folder))
         task_id = task.task_id
diff --git a/hs_core/views/utils.py b/hs_core/views/utils.py
index 3edfba9057..7198cb8c2c 100755
--- a/hs_core/views/utils.py
+++ b/hs_core/views/utils.py
@@ -7,9 +7,9 @@
 from collections import namedtuple
 from datetime import datetime
 from tempfile import NamedTemporaryFile
+from urllib import parse
 from urllib.error import HTTPError, URLError
 from urllib.request import Request, urlopen
-from urllib import parse
 from uuid import uuid4
 
 import paramiko
@@ -17,18 +17,18 @@
 from django.apps import apps
 from django.contrib.auth.models import Group, User
 from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
-from django.core.exceptions import SuspiciousFileOperation
+from django.core.exceptions import (ObjectDoesNotExist, PermissionDenied,
+                                    SuspiciousFileOperation)
 from django.core.files.base import File
-from django.urls import reverse
 from django.core.validators import URLValidator
-from django.db.models import When, Case, Value, BooleanField, Prefetch
+from django.db.models import BooleanField, Case, Prefetch, Value, When
 from django.db.models.query import prefetch_related_objects
 from django.http import HttpResponse, QueryDict
-from django.utils.http import int_to_base36, urlsafe_base64_encode
+from django.urls import reverse
 from django.utils.encoding import force_bytes
+from django.utils.http import int_to_base36, urlsafe_base64_encode
 from mezzanine.conf import settings
-from mezzanine.utils.email import subject_template, send_mail_template
+from mezzanine.utils.email import send_mail_template, subject_template
 from mezzanine.utils.urls import next_url
 from rest_framework import status
 from rest_framework.exceptions import NotFound, ValidationError
@@ -38,13 +38,16 @@
 from hs_access_control.models import PrivilegeCodes
 from hs_core import hydroshare
 from hs_core.enums import RelationTypes
-from hs_core.hydroshare import add_resource_files, validate_resource_file_size
-from hs_core.hydroshare import check_resource_type, delete_resource_file
-from hs_core.hydroshare.utils import check_aggregations, get_file_mime_type, validate_user_quota, QuotaException
-from hs_core.models import AbstractMetaDataElement, BaseResource, Relation, \
-    ResourceFile, get_user, CoreMetaData
-from hs_core.signals import pre_metadata_element_create, post_delete_file_from_resource
-from hs_core.tasks import create_temp_zip, FileOverrideException
+from hs_core.hydroshare import (add_resource_files, check_resource_type,
+                                delete_resource_file,
+                                validate_resource_file_size)
+from hs_core.hydroshare.utils import (QuotaException, check_aggregations,
+                                      get_file_mime_type, validate_user_quota)
+from hs_core.models import (AbstractMetaDataElement, BaseResource,
+                            CoreMetaData, Relation, ResourceFile, get_user)
+from hs_core.signals import (post_delete_file_from_resource,
+                             pre_metadata_element_create)
+from hs_core.tasks import FileOverrideException, create_temp_zip
 from hs_file_types.utils import set_logical_file_type
 from theme.backends import without_login_date_token_generator
 
@@ -1395,7 +1398,8 @@ def ingest_bag(resource, bag_file, user):
     :param bag_file: The ResourceFile of the zipped bag in the resource
     :param user: The HydroShare user object to do the action as
     """
-    from hs_file_types.utils import identify_metadata_files, ingest_metadata_files
+    from hs_file_types.utils import (identify_metadata_files,
+                                     ingest_metadata_files)
 
     istorage = resource.get_irods_storage()
     zip_with_full_path = os.path.join(resource.file_path, bag_file.short_path)
@@ -1859,3 +1863,7 @@ def _path_move_rename(user, res_id, src_path, tgt_path, validate_move_rename=Tru
         resource.set_flag_to_recreate_aggregation_meta_files(orig_path=src_full_path, new_path=tgt_full_path)
 
     hydroshare.utils.resource_modified(resource, user, overwrite_bag=False)
+
+
+def is_ajax(request):
+    return request.headers.get('x-requested-with') == 'XMLHttpRequest'
diff --git a/hs_dictionary/admin.py b/hs_dictionary/admin.py
index 25f667869a..aec957415d 100644
--- a/hs_dictionary/admin.py
+++ b/hs_dictionary/admin.py
@@ -1,23 +1,18 @@
 from django.contrib import admin
-from .models import University, UncategorizedTerm, SubjectArea
 
+from .models import SubjectArea, UncategorizedTerm, University
 
+
+@admin.register(University)
 class UniversityAdmin(admin.ModelAdmin):
     list_display = ['name']
 
 
-admin.site.register(University, UniversityAdmin)
-
-
+@admin.register(UncategorizedTerm)
 class UncategorizedTermAdmin(admin.ModelAdmin):
     pass
 
 
-admin.site.register(UncategorizedTerm, UncategorizedTermAdmin)
-
-
+@admin.register(SubjectArea)
 class SubjectAreaAdmin(admin.ModelAdmin):
     list_display = ['name']
-
-
-admin.site.register(SubjectArea, SubjectAreaAdmin)
diff --git a/hs_file_types/models/model_program.py b/hs_file_types/models/model_program.py
index 5f30f1fdd3..5290811ef1 100644
--- a/hs_file_types/models/model_program.py
+++ b/hs_file_types/models/model_program.py
@@ -1,21 +1,22 @@
 import glob
 import json
 import os
+from urllib.parse import urlparse
 
 import jsonschema
-from urllib.parse import urlparse
+from dateutil import parser
 from django.conf import settings
 from django.contrib.postgres.fields import ArrayField
-from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
 from django.db import models
-from django.template import Template, Context
+from django.template import Context, Template
 from dominate import tags as dom_tags
 from rdflib import Literal, URIRef
-from dateutil import parser
 
 from hs_core.hs_rdf import HSTERMS
 from hs_core.hydroshare import current_site_url
 from hs_core.models import ResourceFile
+
 from .base_model_program_instance import AbstractModelLogicalFile
 from .generic import GenericFileMetaDataMixin
 
@@ -123,7 +124,14 @@ def delete(self, using=None, keep_parents=False):
         can be regenerated for those linked model instance aggregations"""
 
         mp_aggr = self.logical_file
-        for mi_metadata in mp_aggr.mi_metadata_objects.all():
+        try:
+            # this query using reverse relation will raise ValueError if there are no related mi metadata objects
+            # this seems to be a problem in django 4.2
+            mi_metadata_objects = list(mp_aggr.mi_metadata_objects.all())
+        except ValueError:
+            mi_metadata_objects = []
+
+        for mi_metadata in mi_metadata_objects:
             mi_metadata.is_dirty = True
             mi_metadata.save(update_fields=["is_dirty"])
 
@@ -485,8 +493,14 @@ def delete(self, using=None, keep_parents=False):
         """Overriding the base model delete() method to set any associated
         model instance aggregation metadata to dirty so that xml metadata file
         can be regenerated"""
+        try:
+            # this query using reverse relation will raise ValueError if there are no related mi metadata objects
+            # this seems to be a problem in django 4.2
+            mi_metadata_objects = list(self.mi_metadata_objects.all())
+        except ValueError:
+            mi_metadata_objects = []
 
-        for mi_metadata in self.mi_metadata_objects.all():
+        for mi_metadata in mi_metadata_objects:
             mi_metadata.is_dirty = True
             mi_metadata.save(update_fields=["is_dirty"])
 
diff --git a/hs_file_types/urls.py b/hs_file_types/urls.py
index 34fc9ccc10..157936ceb4 100644
--- a/hs_file_types/urls.py
+++ b/hs_file_types/urls.py
@@ -1,128 +1,129 @@
 # coding=utf-8
-from django.conf.urls import url
+from django.urls import path, re_path
+
 from hs_file_types import views
 
 urlpatterns = [
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<file_id>[0-9]+)/'
-        r'(?P<hs_file_type>[A-z]+)/set-file-type/$',
-        views.set_file_type,
-        name="set_file_type"),
-
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<hs_file_type>[A-z]+)/set-file-type/$',
-        views.set_file_type,
-        name="set_file_type"),
-
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/remove-aggregation/$',
-        views.remove_aggregation,
-        name="remove_aggregation"),
-
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/delete-aggregation/$',
-        views.delete_aggregation,
-        name="delete_aggregation"),
-
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/move-aggregation/(?P<tgt_path>.+)$',
-        views.move_aggregation,
-        name="move_aggregation"),
-
-    url(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/move-aggregation/$',
-        views.move_aggregation,
-        name="move_aggregation"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/(?P<element_name>[A-z]+)/'
-        r'(?P<element_id>[0-9]+)/update-file-metadata/$',
-        views.update_metadata_element,
-        name="update_file_metadata"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/(?P<element_name>[A-z]+)/'
-        r'add-file-metadata/$',
-        views.add_metadata_element,
-        name="add_file_metadata"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/coverage/'
-        r'(?P<element_id>[0-9]+)/delete-file-coverage/$',
-        views.delete_coverage_element,
-        name="delete_file_coverage"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
-        r'update-file-keyvalue-metadata/$',
-        views.update_key_value_metadata,
-        name="update_file_keyvalue_metadata"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
-        r'delete-file-keyvalue-metadata/$',
-        views.delete_key_value_metadata,
-        name="delete_file_keyvalue_metadata"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
-        r'add-file-keyword-metadata/$',
-        views.add_keyword_metadata,
-        name="add_file_keyword_metadata"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
-        r'delete-file-keyword-metadata/$',
-        views.delete_keyword_metadata,
-        name="delete_file_keyword_metadata"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
-        r'update-filetype-dataset-name/$',
-        views.update_dataset_name,
-        name="update_filetype_datatset_name"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/(?P<coverage_type>[A-z]+)/'
-        r'update-coverage-fileset/$',
-        views.update_aggregation_coverage,
-        name="update_fileset_coverage"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/'
-        r'update-reftimeseries-abstract/$',
-        views.update_refts_abstract,
-        name="update_reftimeseries_abstract"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/'
-        r'update-timeseries-abstract/$',
-        views.update_timeseries_abstract,
-        name="update_timeseries_abstract"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/'
-        r'update-modelprogram-metadata/$',
-        views.update_model_program_metadata,
-        name="update_modelprogram_metadata"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/'
-        r'update-modelinstance-metadata/$',
-        views.update_model_instance_metadata,
-        name="update_modelinstance_metadata"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/'
-        r'update-modelinstance-metadata-json/$',
-        views.update_model_instance_metadata_json,
-        name="update_modelinstance_metadata_json"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/'
-        r'update-modelinstance-meta-schema/$',
-        views.update_model_instance_meta_schema,
-        name="update_modelinstance_meta_schema"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/update-netcdf-file/$',
-        views.update_netcdf_file,
-        name="update_netcdf_file"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/update-sqlite-file/$',
-        views.update_sqlite_file,
-        name="update_sqlite_file"),
-
-    url(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/(?P<metadata_mode>[a-z]+)/'
-        r'get-file-metadata/$',
-        views.get_metadata,
-        name="get_file_metadata"),
-
-    url(r'^_internal/(?P<file_type_id>[0-9]+)/(?P<series_id>[A-Za-z0-9-]+)/'
-        r'(?P<resource_mode>[a-z]+)/'
-        r'get-timeseries-file-metadata/$',
-        views.get_timeseries_metadata,
-        name="get_timeseries_file_metadata"),
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<file_id>[0-9]+)/'
+            r'(?P<hs_file_type>[A-z]+)/set-file-type/$',
+            views.set_file_type,
+            name="set_file_type"),
+
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/(?P<hs_file_type>[A-z]+)/set-file-type/$',
+            views.set_file_type,
+            name="set_file_type"),
+
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/remove-aggregation/$',
+            views.remove_aggregation,
+            name="remove_aggregation"),
+
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/delete-aggregation/$',
+            views.delete_aggregation,
+            name="delete_aggregation"),
+
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/move-aggregation/(?P<tgt_path>.+)$',
+            views.move_aggregation,
+            name="move_aggregation"),
+
+    re_path(r'^_internal/(?P<resource_id>[0-9a-f]+)/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/move-aggregation/$',
+            views.move_aggregation,
+            name="move_aggregation"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/(?P<element_name>[A-z]+)/'
+            r'(?P<element_id>[0-9]+)/update-file-metadata/$',
+            views.update_metadata_element,
+            name="update_file_metadata"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/(?P<element_name>[A-z]+)/'
+            r'add-file-metadata/$',
+            views.add_metadata_element,
+            name="add_file_metadata"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/coverage/'
+            r'(?P<element_id>[0-9]+)/delete-file-coverage/$',
+            views.delete_coverage_element,
+            name="delete_file_coverage"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
+            r'update-file-keyvalue-metadata/$',
+            views.update_key_value_metadata,
+            name="update_file_keyvalue_metadata"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
+            r'delete-file-keyvalue-metadata/$',
+            views.delete_key_value_metadata,
+            name="delete_file_keyvalue_metadata"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
+            r'add-file-keyword-metadata/$',
+            views.add_keyword_metadata,
+            name="add_file_keyword_metadata"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
+            r'delete-file-keyword-metadata/$',
+            views.delete_keyword_metadata,
+            name="delete_file_keyword_metadata"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/'
+            r'update-filetype-dataset-name/$',
+            views.update_dataset_name,
+            name="update_filetype_datatset_name"),
+
+    re_path(r'^_internal/(?P<file_type_id>[0-9]+)/(?P<coverage_type>[A-z]+)/'
+            r'update-coverage-fileset/$',
+            views.update_aggregation_coverage,
+            name="update_fileset_coverage"),
+
+    path('_internal/<int:file_type_id>/update-reftimeseries-abstract/'
+         r'update-reftimeseries-abstract/',
+         views.update_refts_abstract,
+         name="update_reftimeseries_abstract"),
+
+    path('_internal/<int:file_type_id>/update-timeseries-abstract/'
+         r'update-timeseries-abstract/',
+         views.update_timeseries_abstract,
+         name="update_timeseries_abstract"),
+
+    path('_internal/<int:file_type_id>/update-modelprogram-metadata/'
+         r'update-modelprogram-metadata/',
+         views.update_model_program_metadata,
+         name="update_modelprogram_metadata"),
+
+    path('_internal/<int:file_type_id>/update-modelinstance-metadata/'
+         r'update-modelinstance-metadata/',
+         views.update_model_instance_metadata,
+         name="update_modelinstance_metadata"),
+
+    path('_internal/<int:file_type_id>/update-modelinstance-metadata-json/'
+         r'update-modelinstance-metadata-json/',
+         views.update_model_instance_metadata_json,
+         name="update_modelinstance_metadata_json"),
+
+    path('_internal/<int:file_type_id>/update-modelinstance-meta-schema/'
+         r'update-modelinstance-meta-schema/',
+         views.update_model_instance_meta_schema,
+         name="update_modelinstance_meta_schema"),
+
+    path('_internal/<int:file_type_id>/update-netcdf-file/',
+         views.update_netcdf_file,
+         name="update_netcdf_file"),
+
+    path('_internal/<int:file_type_id>/update-sqlite-file/',
+         views.update_sqlite_file,
+         name="update_sqlite_file"),
+
+    re_path(r'^_internal/(?P<hs_file_type>[A-z]+)/(?P<file_type_id>[0-9]+)/(?P<metadata_mode>[a-z]+)/'
+            r'get-file-metadata/$',
+            views.get_metadata,
+            name="get_file_metadata"),
+
+    re_path(r'^_internal/(?P<file_type_id>[0-9]+)/(?P<series_id>[A-Za-z0-9-]+)/'
+            r'(?P<resource_mode>[a-z]+)/'
+            r'get-timeseries-file-metadata/$',
+            views.get_timeseries_metadata,
+            name="get_timeseries_file_metadata"),
 ]
diff --git a/hs_labels/urls.py b/hs_labels/urls.py
index a73161ad22..c928badaf8 100644
--- a/hs_labels/urls.py
+++ b/hs_labels/urls.py
@@ -1,7 +1,8 @@
-from django.conf.urls import url
+from django.urls import path, re_path
+
 from hs_labels import views
 
 urlpatterns = [
-    url(r'^_internal/(?P<shortkey>[A-z0-9]+)/label-resource-action/$', views.resource_labeling_action),
-    url(r'^_internal/label-resource-action/$', views.resource_labeling_action),
+    re_path(r'^_internal/(?P<shortkey>[A-z0-9]+)/label-resource-action/$', views.resource_labeling_action),
+    path('_internal/label-resource-action/', views.resource_labeling_action),
 ]
diff --git a/hs_metrics/urls.py b/hs_metrics/urls.py
index 06a1049a18..ce38ba5cd5 100755
--- a/hs_metrics/urls.py
+++ b/hs_metrics/urls.py
@@ -1,9 +1,10 @@
-from django.conf.urls import url
+from django.urls import path
+
 from hs_metrics import views
 
 urlpatterns = [
     # users API
 
-    url(r'^metrics/$', views.HydroshareSiteMetrics.as_view()),
+    path('metrics/', views.HydroshareSiteMetrics.as_view()),
 
 ]
diff --git a/hs_rest_api/urls.py b/hs_rest_api/urls.py
index f4662ca06f..98373c34a7 100755
--- a/hs_rest_api/urls.py
+++ b/hs_rest_api/urls.py
@@ -1,28 +1,26 @@
-from django.conf.urls import url, include
+from django.urls import include, path, re_path
+from drf_yasg import openapi
+from drf_yasg.views import get_schema_view
+from rest_framework import permissions
 
-from hs_dictionary import views as dict_views
 from hs_core import views as core_views
+from hs_core.views.resource_folder_hierarchy import (
+    data_store_add_reference_public, data_store_edit_reference_url_public,
+    ingest_metadata_files)
+from hs_dictionary import views as dict_views
 from hs_file_types import views as file_type_views
-from hs_core.views.resource_folder_hierarchy import data_store_add_reference_public, \
-    data_store_edit_reference_url_public, ingest_metadata_files
 
+from .discovery import DiscoverSearchView
 from .resources.file_metadata import FileMetaDataRetrieveUpdateDestroy
-
-from drf_yasg.views import get_schema_view
-from drf_yasg import openapi
-from rest_framework import permissions
-
 from .views.resource_share import ShareResourceGroup, ShareResourceUser
-from .discovery import DiscoverSearchView
-
 
 hsapi_urlpatterns = [
-    url('^hsapi/', include('hs_rest_api.urls')),
-    url('^hsapi/', include('hs_core.urls')),
-    url('^hsapi/', include('hs_labels.urls')),
-    url('^hsapi/', include('hs_collection_resource.urls')),
-    url('^hsapi/', include('hs_file_types.urls')),
-    url('^hsapi/', include('hs_composite_resource.urls')),
+    path('hsapi/', include('hs_rest_api.urls')),
+    path('hsapi/', include('hs_core.urls')),
+    path('hsapi/', include('hs_labels.urls')),
+    path('hsapi/', include('hs_collection_resource.urls')),
+    path('hsapi/', include('hs_file_types.urls')),
+    path('hsapi/', include('hs_composite_resource.urls')),
 ]
 
 schema_view_yasg = get_schema_view(
@@ -35,221 +33,221 @@
     ),
     validators=[],
     public=True,
-    permission_classes=(permissions.AllowAny,),
+    permission_classes=[permissions.AllowAny],
     patterns=hsapi_urlpatterns,
 )
 
 urlpatterns = [
     # Swagger Docs View
-    url(r'^(?P<format>\.json|\.yaml)$', schema_view_yasg.without_ui(cache_timeout=None),
-        name='schema-json'),
-    url(r'^$', schema_view_yasg.with_ui('swagger', cache_timeout=None),
-        name='schema-swagger-ui'),
-    url(r'^redoc/$', schema_view_yasg.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
+    re_path(r'^(?P<format>\.json|\.yaml)$', schema_view_yasg.without_ui(cache_timeout=None),
+            name='schema-json'),
+    path('', schema_view_yasg.with_ui('swagger', cache_timeout=None),
+         name='schema-swagger-ui'),
+    path('redoc/', schema_view_yasg.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
 
     # resource API
-    url(r'^resource/types/$', core_views.resource_rest_api.ResourceTypes.as_view(),
-        name='list_resource_types'),
+    path('resource/types/', core_views.resource_rest_api.ResourceTypes.as_view(),
+         name='list_resource_types'),
 
-    url(r'^resource/content_types/$', core_views.resource_rest_api.ContentTypes.as_view(),
-        name='list_contenttypes'),
+    path('resource/content_types/', core_views.resource_rest_api.ContentTypes.as_view(),
+         name='list_contenttypes'),
 
-    url(r'^resource/$', core_views.resource_rest_api.ResourceListCreate.as_view(),
-        name='list_create_resource'),
+    path('resource/', core_views.resource_rest_api.ResourceListCreate.as_view(),
+         name='list_create_resource'),
 
     # Public endpoint for resource flags
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/flag/$', core_views.set_resource_flag_public,
-        name='public_set_resource_flag'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/flag/$', core_views.set_resource_flag_public,
+            name='public_set_resource_flag'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/$',
-        core_views.resource_rest_api.ResourceReadUpdateDelete.as_view(),
-        name='get_update_delete_resource'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/$',
+            core_views.resource_rest_api.ResourceReadUpdateDelete.as_view(),
+            name='get_update_delete_resource'),
 
     # Create new version of a resource
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/version/$', core_views.create_new_version_resource_public,
-        name='new_version_resource_public'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/version/$', core_views.create_new_version_resource_public,
+            name='new_version_resource_public'),
 
     # public copy resource endpoint
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/copy/$',
-        core_views.copy_resource_public, name='copy_resource_public'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/copy/$',
+            core_views.copy_resource_public, name='copy_resource_public'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/share/(?P<privilege>[a-z]+)/group/(?P<group_id>[\w.@+-]+)/$',
-        ShareResourceGroup.as_view(), name='share_resource_group_public'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/share/(?P<privilege>[a-z]+)/group/(?P<group_id>[\w.@+-]+)/$',
+            ShareResourceGroup.as_view(), name='share_resource_group_public'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/share/(?P<privilege>[a-z]+)/user/(?P<user_id>[\w.@+-]+)/$',
-        ShareResourceUser.as_view(), name='share_resource_user_public'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/share/(?P<privilege>[a-z]+)/user/(?P<user_id>[\w.@+-]+)/$',
+            ShareResourceUser.as_view(), name='share_resource_user_public'),
 
     # DEPRECATED: use form above instead
-    url(r'^resource/accessRules/(?P<pk>[0-9a-f-]+)/$',
-        core_views.resource_rest_api.AccessRulesUpdate.as_view(),
-        name='DEPRECATED_update_access_rules'),
+    re_path(r'^resource/accessRules/(?P<pk>[0-9a-f-]+)/$',
+            core_views.resource_rest_api.AccessRulesUpdate.as_view(),
+            name='DEPRECATED_update_access_rules'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/sysmeta/$',
-        core_views.resource_rest_api.SystemMetadataRetrieve.as_view(),
-        name='get_system_metadata'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/sysmeta/$',
+            core_views.resource_rest_api.SystemMetadataRetrieve.as_view(),
+            name='get_system_metadata'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/scimeta/$',
-        core_views.resource_rest_api.ScienceMetadataRetrieveUpdate.as_view(),
-        name='get_update_science_metadata'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/scimeta/$',
+            core_views.resource_rest_api.ScienceMetadataRetrieveUpdate.as_view(),
+            name='get_update_science_metadata'),
 
     # Resource metadata editing
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/scimeta/elements/$',
-        core_views.resource_metadata_rest_api.MetadataElementsRetrieveUpdate.as_view(),
-        name='get_update_science_metadata_elements'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/scimeta/elements/$',
+            core_views.resource_metadata_rest_api.MetadataElementsRetrieveUpdate.as_view(),
+            name='get_update_science_metadata_elements'),
 
     # Update key-value metadata
-    url(r'^resource/(?P<id>[0-9a-f-]+)/scimeta/custom/$',
-        core_views.update_key_value_metadata_public,
-        name='update_custom_metadata'),
+    re_path(r'^resource/(?P<id>[0-9a-f-]+)/scimeta/custom/$',
+            core_views.update_key_value_metadata_public,
+            name='update_custom_metadata'),
 
     # DEPRECATED: use from above instead
-    url(r'^scimeta/(?P<pk>[0-9a-f-]+)/$',
-        core_views.resource_rest_api.ScienceMetadataRetrieveUpdate.as_view(),
-        name='DEPRECATED_get_update_science_metadata'),
+    re_path(r'^scimeta/(?P<pk>[0-9a-f-]+)/$',
+            core_views.resource_rest_api.ScienceMetadataRetrieveUpdate.as_view(),
+            name='DEPRECATED_get_update_science_metadata'),
 
-    url(r'^resource/(?P<pk>[A-z0-9]+)/map/$',
-        core_views.resource_rest_api.ResourceMapRetrieve.as_view(),
-        name='get_resource_map'),
+    re_path(r'^resource/(?P<pk>[A-z0-9]+)/map/$',
+            core_views.resource_rest_api.ResourceMapRetrieve.as_view(),
+            name='get_resource_map'),
 
-    url(r'resource/(?P<pk>[0-9a-f-]+)/files/metadata/(?P<pathname>.*)/$',
-        FileMetaDataRetrieveUpdateDestroy.as_view(), name="get_update_resource_file_metadata"),
+    re_path(r'resource/(?P<pk>[0-9a-f-]+)/files/metadata/(?P<pathname>.*)/$',
+            FileMetaDataRetrieveUpdateDestroy.as_view(), name="get_update_resource_file_metadata"),
 
     # Patterns are now checked in the view class.
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/files/(?P<pathname>.+)/$',
-        core_views.resource_rest_api.ResourceFileCRUD.as_view(),
-        name='get_update_delete_resource_file'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/files/(?P<pathname>.+)/$',
+            core_views.resource_rest_api.ResourceFileCRUD.as_view(),
+            name='get_update_delete_resource_file'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/files/$',
-        core_views.resource_rest_api.ResourceFileListCreate.as_view(),
-        name='list_create_resource_file'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/files/$',
+            core_views.resource_rest_api.ResourceFileListCreate.as_view(),
+            name='list_create_resource_file'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/ingest_metadata/$',
-        ingest_metadata_files,
-        name='ingest_metadata_files'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/ingest_metadata/$',
+            ingest_metadata_files,
+            name='ingest_metadata_files'),
 
-    url(r'^resource/data-store-add-reference/$',
-        data_store_add_reference_public),
+    path('resource/data-store-add-reference/',
+         data_store_add_reference_public),
 
-    url(r'^resource/data_store_edit_reference_url/$',
-        data_store_edit_reference_url_public),
+    path('resource/data_store_edit_reference_url/',
+         data_store_edit_reference_url_public),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/folders/(?P<pathname>.*)/$',
-        core_views.resource_folder_rest_api.ResourceFolders.as_view(),
-        name='list_manipulate_folders'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/folders/(?P<pathname>.*)/$',
+            core_views.resource_folder_rest_api.ResourceFolders.as_view(),
+            name='list_manipulate_folders'),
 
     # iRODS tickets
     # write disabled;change (?P<op>read) to (?P<op>read|write) when ready
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/ticket/(?P<op>read)/(?P<pathname>.*)/$',
-        core_views.resource_ticket_rest_api.CreateResourceTicket.as_view(),
-        name='create_ticket'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/ticket/(?P<op>read)/(?P<pathname>.*)/$',
+            core_views.resource_ticket_rest_api.CreateResourceTicket.as_view(),
+            name='create_ticket'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/ticket/bag/$',
-        core_views.resource_ticket_rest_api.CreateBagTicket.as_view(),
-        name='create_bag_ticket'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/ticket/bag/$',
+            core_views.resource_ticket_rest_api.CreateBagTicket.as_view(),
+            name='create_bag_ticket'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/ticket/(?P<ticket>.*)/$',
-        core_views.resource_ticket_rest_api.ManageResourceTicket.as_view(),
-        name='manage_ticket'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/ticket/(?P<ticket>.*)/$',
+            core_views.resource_ticket_rest_api.ManageResourceTicket.as_view(),
+            name='manage_ticket'),
 
     # public unzip endpoint
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/functions/unzip/(?P<pathname>.*)/$',
-        core_views.resource_folder_hierarchy.data_store_folder_unzip_public),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/functions/unzip/(?P<pathname>.*)/$',
+            core_views.resource_folder_hierarchy.data_store_folder_unzip_public),
 
     # public zip folder endpoint
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/functions/zip/$',
-        core_views.resource_folder_hierarchy.data_store_folder_zip_public),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/functions/zip/$',
+            core_views.resource_folder_hierarchy.data_store_folder_zip_public),
 
     # public zip aggregation by file endpoint
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/functions/zip-by-aggregation-file/$',
-        core_views.resource_folder_hierarchy.zip_aggregation_file_public),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/functions/zip-by-aggregation-file/$',
+            core_views.resource_folder_hierarchy.zip_aggregation_file_public),
 
     # public move or rename
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/functions/move-or-rename/$',
-        core_views.resource_folder_hierarchy.data_store_file_or_folder_move_or_rename_public),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/functions/move-or-rename/$',
+            core_views.resource_folder_hierarchy.data_store_file_or_folder_move_or_rename_public),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/functions/set-file-type/(?P<file_path>.*)/'
-        r'(?P<hs_file_type>[A-z]+)/$',
-        file_type_views.set_file_type_public,
-        name="set_file_type_public"),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/functions/set-file-type/(?P<file_path>.*)/'
+            r'(?P<hs_file_type>[A-z]+)/$',
+            file_type_views.set_file_type_public,
+            name="set_file_type_public"),
 
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/functions/remove-file-type/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_path>.*)/$',
-        file_type_views.remove_aggregation_public,
-        name="remove_aggregation_public"),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/functions/remove-file-type/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_path>.*)/$',
+            file_type_views.remove_aggregation_public,
+            name="remove_aggregation_public"),
 
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/functions/delete-file-type/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_path>.*)/$',
-        file_type_views.delete_aggregation_public,
-        name="delete_aggregation_public"),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/functions/delete-file-type/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_path>.*)/$',
+            file_type_views.delete_aggregation_public,
+            name="delete_aggregation_public"),
 
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/'
-        r'(?P<hs_file_type>[A-z]+)/(?P<file_path>.*)/functions/move-file-type/(?P<tgt_path>.*)$',
-        file_type_views.move_aggregation_public,
-        name="move_aggregation_public"),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/'
+            r'(?P<hs_file_type>[A-z]+)/(?P<file_path>.*)/functions/move-file-type/(?P<tgt_path>.*)$',
+            file_type_views.move_aggregation_public,
+            name="move_aggregation_public"),
 
     # DEPRECATED: use form above instead. Added unused POST for simplicity
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/file_list/$',
-        core_views.resource_rest_api.ResourceFileListCreate.as_view(),
-        name='DEPRECATED_get_resource_file_list'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/file_list/$',
+            core_views.resource_rest_api.ResourceFileListCreate.as_view(),
+            name='DEPRECATED_get_resource_file_list'),
 
-    url(r'^taskstatus/(?P<task_id>[A-z0-9\-]+)/$',
-        core_views.resource_rest_api.CheckTaskStatus.as_view(),
-        name='get_task_status'),
+    re_path(r'^taskstatus/(?P<task_id>[A-z0-9\-]+)/$',
+            core_views.resource_rest_api.CheckTaskStatus.as_view(),
+            name='get_task_status'),
 
-    url(r'^user/$',
-        core_views.user_rest_api.UserInfo.as_view(), name='get_logged_in_user_info'),
+    path('user/',
+         core_views.user_rest_api.UserInfo.as_view(), name='get_logged_in_user_info'),
 
-    url(r'^userInfo/$',
-        core_views.user_rest_api.UserInfo.as_view(), name='get_logged_in_user_info'),
+    path('userInfo/',
+         core_views.user_rest_api.UserInfo.as_view(), name='get_logged_in_user_info'),
 
-    url(r'^userDetails/(?P<user_identifier>.+)/$',
-        core_views.hsapi_get_user, name='get_user_details'),
+    path('userDetails/<path:user_identifier>/',
+         core_views.hsapi_get_user, name='get_user_details'),
 
-    url(r'^userKeycloak/(?P<user_identifier>.+)$',
-        core_views.hsapi_get_user_for_keycloak, name='get_user_for_keycloak'),
+    path('userKeycloak/<path:user_identifier>',
+         core_views.hsapi_get_user_for_keycloak, name='get_user_for_keycloak'),
 
-    url(r'^dictionary/universities/$',
-        dict_views.ListUniversities.as_view(), name="get_dictionary"),
+    path('dictionary/universities/',
+         dict_views.ListUniversities.as_view(), name="get_dictionary"),
 
-    url(r'^dictionary/subject_areas/$',
-        dict_views.ListSubjectAreas.as_view(), name="get_subject_areas"),
+    path('dictionary/subject_areas/',
+         dict_views.ListSubjectAreas.as_view(), name="get_subject_areas"),
 
     # Resource Access
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/access/$',
-        core_views.resource_access_api.ResourceAccessUpdateDelete.as_view(),
-        name='get_update_delete_resource_access'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/access/$',
+            core_views.resource_access_api.ResourceAccessUpdateDelete.as_view(),
+            name='get_update_delete_resource_access'),
 
-    url(r'^resource/search$', DiscoverSearchView.as_view({'get': 'list'}), name='discover-hsapi'),
+    path('resource/search', DiscoverSearchView.as_view({'get': 'list'}), name='discover-hsapi'),
 
     # Gets a list of metadata template schema file names for model program aggregation
-    url(r'^modelprogram/template/meta/schemas/$', file_type_views.list_model_program_template_metadata_schemas,
-        name='list_model_program_template_meta_schemas'),
+    path('modelprogram/template/meta/schemas/', file_type_views.list_model_program_template_metadata_schemas,
+         name='list_model_program_template_meta_schemas'),
 
     # Gets JSON data for the specified template metadata schema file for model program aggregation
-    url(r'^modelprogram/template/meta/schema/(?P<schema_filename>.*)$',
-        file_type_views.get_model_program_template_metadata_schema,
-        name='get_model_program_template_meta_schema'),
+    re_path(r'^modelprogram/template/meta/schema/(?P<schema_filename>.*)$',
+            file_type_views.get_model_program_template_metadata_schema,
+            name='get_model_program_template_meta_schema'),
 
     # Assigns a specific metadata template schema to be used as the meta schema for a given model program aggregation
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/modelprogram/template/meta/schema/(?P<aggregation_path>.*)/'
-        r'(?P<schema_filename>.*)$',
-        file_type_views.use_template_metadata_schema_for_model_program,
-        name='use_template_meta_schema_for_model_program'),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/modelprogram/template/meta/schema/(?P<aggregation_path>.*)/'
+            r'(?P<schema_filename>.*)$',
+            file_type_views.use_template_metadata_schema_for_model_program,
+            name='use_template_meta_schema_for_model_program'),
 
     # Updates metadata schema for a given model instance aggregation using the schema from the linked model program
     # aggregation
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/modelinstance/meta/schema/(?P<aggregation_path>.*)$',
-        file_type_views.update_metadata_schema_for_model_instance,
-        name='update_meta_schema_for_model_instance'),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/modelinstance/meta/schema/(?P<aggregation_path>.*)$',
+            file_type_views.update_metadata_schema_for_model_instance,
+            name='update_meta_schema_for_model_instance'),
 
     # Gets/updates metadata for a given model program aggregation
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/modelprogram/meta/(?P<aggregation_path>.*)$',
-        file_type_views.model_program_metadata_in_json,
-        name='model_program_metadata_in_json'),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/modelprogram/meta/(?P<aggregation_path>.*)$',
+            file_type_views.model_program_metadata_in_json,
+            name='model_program_metadata_in_json'),
 
     # Gets/updates metadata for a given model instance aggregation
-    url(r'^resource/(?P<resource_id>[0-9a-f]+)/modelinstance/meta/(?P<aggregation_path>.*)$',
-        file_type_views.model_instance_metadata_in_json,
-        name='model_instance_metadata_in_json'),
+    re_path(r'^resource/(?P<resource_id>[0-9a-f]+)/modelinstance/meta/(?P<aggregation_path>.*)$',
+            file_type_views.model_instance_metadata_in_json,
+            name='model_instance_metadata_in_json'),
 ]
diff --git a/hs_rest_api2/urls.py b/hs_rest_api2/urls.py
index 3d9c8aeb4c..818b5cd0ba 100755
--- a/hs_rest_api2/urls.py
+++ b/hs_rest_api2/urls.py
@@ -1,17 +1,21 @@
-from django.conf.urls import url, include
-
-from .views.metadata import resource_metadata_json, geographic_feature_metadata_json, geographic_raster_metadata_json, \
-    time_series_metadata_json, file_set_metadata_json, multidimensional_metadata_json, \
-    referenced_time_series_metadata_json, single_file_metadata_json, model_instance_metadata_json, \
-    model_program_metadata_json
-
-from drf_yasg.views import get_schema_view
+from django.urls import include, path, re_path
 from drf_yasg import openapi
+from drf_yasg.views import get_schema_view
 from rest_framework import permissions
 
+from .views.metadata import (file_set_metadata_json,
+                             geographic_feature_metadata_json,
+                             geographic_raster_metadata_json,
+                             model_instance_metadata_json,
+                             model_program_metadata_json,
+                             multidimensional_metadata_json,
+                             referenced_time_series_metadata_json,
+                             resource_metadata_json, single_file_metadata_json,
+                             time_series_metadata_json)
+
 app_name = "hsapi2"
 hsapi2_urlpatterns = [
-    url('^hsapi2/', include('hs_rest_api2.urls', namespace='hsapi2')),
+    path('hsapi2/', include('hs_rest_api2.urls', namespace='hsapi2')),
 ]
 
 schema_view_yasg = get_schema_view(
@@ -24,54 +28,54 @@
     ),
     validators=[],
     public=True,
-    permission_classes=(permissions.AllowAny,),
+    permission_classes=[permissions.AllowAny],
 )
 
 urlpatterns = [
     # Swagger Docs View
-    url(r'^(?P<format>\.json|\.yaml)$', schema_view_yasg.without_ui(cache_timeout=None),
-        name='schema-json'),
-    url(r'^$', schema_view_yasg.with_ui('swagger', cache_timeout=None),
-        name='schema-swagger-ui'),
-    url(r'^redoc/$', schema_view_yasg.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
+    re_path(r'^(?P<format>\.json|\.yaml)$', schema_view_yasg.without_ui(cache_timeout=None),
+            name='schema-json'),
+    path('', schema_view_yasg.with_ui('swagger', cache_timeout=None),
+         name='schema-swagger-ui'),
+    path('redoc/', schema_view_yasg.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/$',
-        resource_metadata_json,
-        name='resource_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/$',
+            resource_metadata_json,
+            name='resource_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/GeographicFeature/(?P<aggregation_path>.*)$',
-        geographic_feature_metadata_json,
-        name='geographic_feature_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/GeographicFeature/(?P<aggregation_path>.*)$',
+            geographic_feature_metadata_json,
+            name='geographic_feature_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/GeographicRaster/(?P<aggregation_path>.*)$',
-        geographic_raster_metadata_json,
-        name='geographic_raster_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/GeographicRaster/(?P<aggregation_path>.*)$',
+            geographic_raster_metadata_json,
+            name='geographic_raster_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/TimeSeries/(?P<aggregation_path>.*)$',
-        time_series_metadata_json,
-        name='time_series_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/TimeSeries/(?P<aggregation_path>.*)$',
+            time_series_metadata_json,
+            name='time_series_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/FileSet/(?P<aggregation_path>.*)$',
-        file_set_metadata_json,
-        name='file_set_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/FileSet/(?P<aggregation_path>.*)$',
+            file_set_metadata_json,
+            name='file_set_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/Multidimensional/(?P<aggregation_path>.*)$',
-        multidimensional_metadata_json,
-        name='multidimensional_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/Multidimensional/(?P<aggregation_path>.*)$',
+            multidimensional_metadata_json,
+            name='multidimensional_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/ReferencedTimeSeries/(?P<aggregation_path>.*)$',
-        referenced_time_series_metadata_json,
-        name='referenced_time_series_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/ReferencedTimeSeries/(?P<aggregation_path>.*)$',
+            referenced_time_series_metadata_json,
+            name='referenced_time_series_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/SingleFile/(?P<aggregation_path>.*)$',
-        single_file_metadata_json,
-        name='single_file_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/SingleFile/(?P<aggregation_path>.*)$',
+            single_file_metadata_json,
+            name='single_file_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/ModelInstance/(?P<aggregation_path>.*)$',
-        model_instance_metadata_json,
-        name='model_instance_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/ModelInstance/(?P<aggregation_path>.*)$',
+            model_instance_metadata_json,
+            name='model_instance_metadata_json'),
 
-    url(r'^resource/(?P<pk>[0-9a-f-]+)/json/ModelProgram/(?P<aggregation_path>.*)$',
-        model_program_metadata_json,
-        name='model_program_metadata_json'),
+    re_path(r'^resource/(?P<pk>[0-9a-f-]+)/json/ModelProgram/(?P<aggregation_path>.*)$',
+            model_program_metadata_json,
+            name='model_program_metadata_json'),
 ]
diff --git a/hs_sitemap/urls.py b/hs_sitemap/urls.py
index c05abcc8ee..923271363a 100644
--- a/hs_sitemap/urls.py
+++ b/hs_sitemap/urls.py
@@ -1,8 +1,8 @@
-from django.conf.urls import url
-
 from django.contrib.sitemaps import views
+from django.urls import re_path
 
-from .sitemaps import PagesSitemap, ResourcesSitemap, CommunitiesSitemap, GroupsSitemap
+from .sitemaps import (CommunitiesSitemap, GroupsSitemap, PagesSitemap,
+                       ResourcesSitemap)
 
 sitemaps = {
     "resources": ResourcesSitemap,
@@ -14,6 +14,6 @@
 
 
 urlpatterns = [
-    url(r'^\.xml$', views.index, {'sitemaps': sitemaps}),
-    url(r'^-(?P<section>.+)\.xml$', views.sitemap, {'sitemaps': sitemaps}, name=sitemap_view),
+    re_path(r'^\.xml$', views.index, {'sitemaps': sitemaps}),
+    re_path(r'^-(?P<section>.+)\.xml$', views.sitemap, {'sitemaps': sitemaps}, name=sitemap_view),
 ]
diff --git a/hs_tracking/utils.py b/hs_tracking/utils.py
index f15175310f..b371bcdb34 100644
--- a/hs_tracking/utils.py
+++ b/hs_tracking/utils.py
@@ -1,7 +1,11 @@
+from urllib.parse import urlparse
+
 import robot_detection
 from ipware.ip import get_ip
-from hs_tools_resource.models import RequestUrlBase, RequestUrlBaseAggregation, RequestUrlBaseFile
-from urllib.parse import urlparse
+
+from hs_tools_resource.models import (RequestUrlBase,
+                                      RequestUrlBaseAggregation,
+                                      RequestUrlBaseFile)
 
 
 def get_client_ip(request):
@@ -38,7 +42,7 @@ def get_user_email_tld(session, emaildomain=None):
 
 
 def is_human(user_agent):
-    if robot_detection.is_robot(user_agent):
+    if robot_detection.is_robot(str(user_agent)):
         return False
     return True
 
@@ -48,7 +52,7 @@ def get_std_log_fields(request, session=None):
     This ensures that all activities are reporting a consistent set of metrics
     """
     try:
-        user_agent = request.META['HTTP_USER_AGENT']
+        user_agent = request.headers['user-agent']
         human = is_human(user_agent)
     except KeyError:
         user_agent = None
diff --git a/hsctl b/hsctl
index 9db03e869c..9f1bc21b2f 100755
--- a/hsctl
+++ b/hsctl
@@ -188,6 +188,8 @@ loaddb_hs() {
     docker exec -u hydro-service hydroshare createdb -U postgres -h postgis postgres --encoding UNICODE --template=template1
     echo "  - docker exec -u hydro-service hydroshare psql -U postgres -h postgis -d postgres -w -c 'SET client_min_messages TO WARNING;'"
     docker exec -u hydro-service hydroshare psql -U postgres -h postgis -d postgres -w -c 'SET client_min_messages TO WARNING;'
+    echo " - docker exec -u hydro-service hydroshare psql -U postgres -h postgis -d postgres -w -c 'SET idle_in_transaction_session_timeout TO 70000;'"
+    docker exec -u hydro-service hydroshare psql -U postgres -h postgis -d postgres -w -c 'SET idle_in_transaction_session_timeout TO 70000;'
     echo "  - docker exec -u hydro-service hydroshare psql -U postgres -h postgis -d postgres -q -f ${HS_DATABASE}"
     docker exec -u hydro-service hydroshare psql -U postgres -h postgis -d postgres -q -f ${HS_DATABASE}
 }
diff --git a/hydroshare/local_settings.cutover b/hydroshare/local_settings.cutover
index 6224466212..3d2c64516f 100644
--- a/hydroshare/local_settings.cutover
+++ b/hydroshare/local_settings.cutover
@@ -34,6 +34,12 @@ POSTGIS_DB = os.environ.get('POSTGIS_DB', 'postgres')
 POSTGIS_PASSWORD = os.environ.get('POSTGIS_PASSWORD', 'postgres')
 POSTGIS_USER = os.environ.get('POSTGIS_USER', 'postgres')
 
+# number of seconds database connections should persist
+# https://docs.djangoproject.com/en/3.2/ref/settings/#std-setting-CONN_MAX_AGE
+# this must be shorter than the database's timeout 
+# https://www.postgresql.org/docs/11/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT
+CONN_MAX_AGE = 60
+
 IPYTHON_SETTINGS = []
 IPYTHON_BASE = '/hydroshare/static/media/ipython-notebook'
 IPYTHON_HOST = '127.0.0.1'
diff --git a/hydroshare/local_settings.template b/hydroshare/local_settings.template
index a4427b503b..e9201fdee0 100644
--- a/hydroshare/local_settings.template
+++ b/hydroshare/local_settings.template
@@ -32,6 +32,12 @@ POSTGIS_DB = os.environ.get('POSTGIS_DB', 'postgres')
 POSTGIS_PASSWORD = os.environ.get('POSTGIS_PASSWORD', 'postgres')
 POSTGIS_USER = os.environ.get('POSTGIS_USER', 'postgres')
 
+# number of seconds database connections should persist
+# https://docs.djangoproject.com/en/3.2/ref/settings/#std-setting-CONN_MAX_AGE
+# this must be shorter than the database's timeout 
+# https://www.postgresql.org/docs/11/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT
+CONN_MAX_AGE = 60
+
 IPYTHON_SETTINGS = []
 IPYTHON_BASE = '/hydroshare/static/media/ipython-notebook'
 IPYTHON_HOST = '127.0.0.1'
diff --git a/hydroshare/settings.py b/hydroshare/settings.py
index c1e727e92c..0e0668eea2 100755
--- a/hydroshare/settings.py
+++ b/hydroshare/settings.py
@@ -1,8 +1,9 @@
 # TEST_RUNNER='django_nose.NoseTestSuiteRunner'
+import os
 import sys
 
 from PIL import ImageFile
-import os
+
 TEST_RUNNER = "hs_core.tests.runner.CustomTestSuiteRunner"
 TEST_WITHOUT_MIGRATIONS_COMMAND = "django_nose.management.commands.test.Command"
 
@@ -299,7 +300,14 @@ def _(s): return s # noqa
 # using this storage class might cause issues for future tests
 # The documentation suggests using the default storage backend when testing
 # https://docs.djangoproject.com/en/1.11/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict
-STATICFILES_STORAGE = "hydroshare.storage.ForgivingManifestStaticFilesStorage"
+STORAGES = {
+    "default": {
+        "BACKEND": "django.core.files.storage.FileSystemStorage",
+    },
+    "staticfiles": {
+        "BACKEND": "hydroshare.storage.ForgivingManifestStaticFilesStorage",
+    },
+}
 
 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
 # trailing slash.
@@ -370,7 +378,8 @@ def _(s): return s # noqa
 
 INSTALLED_APPS = (
     "test_without_migrations",
-    "autocomplete_light",
+    "dal",
+    "dal_select2",
     "django.contrib.admin",
     "django.contrib.auth",
     "oauth2_provider",
@@ -399,6 +408,7 @@ def _(s): return s # noqa
     "mezzanine.pages",
     "mezzanine.galleries",
     "crispy_forms",
+    "crispy_bootstrap3",
     "mezzanine.accounts",
     "haystack",
     "rest_framework",
@@ -593,7 +603,7 @@ def _(s): return s # noqa
 
 
 ACCOUNTS_PROFILE_MODEL = "theme.UserProfile"
-CRISPY_TEMPLATE_PACK = "bootstrap"
+CRISPY_TEMPLATE_PACK = "bootstrap3"
 
 DEFAULT_AUTHENTICATION_CLASSES = (
     "rest_framework.authentication.BasicAuthentication",
diff --git a/hydroshare/urls.py b/hydroshare/urls.py
index b6413a3a2f..50935d58c4 100755
--- a/hydroshare/urls.py
+++ b/hydroshare/urls.py
@@ -1,24 +1,25 @@
-from autocomplete_light import shortcuts as autocomplete_light
-from django.conf.urls import include, url
+# from autocomplete_light import shortcuts as autocomplete_light
 from django.conf.urls.i18n import i18n_patterns
 from django.contrib import admin
+from django.urls import include, path, re_path
 from django.views.generic.base import RedirectView
 from mezzanine.conf import settings
-from mezzanine.core.views import direct_to_template # noqa
+from mezzanine.core.views import direct_to_template  # noqa
 from mezzanine.pages.views import page
 
 import hs_communities.views.communities
+from hs_core import autocomplete_light_registry as alr
 from hs_core import views as hs_core_views
 from hs_core.views.oauth2_view import GroupAuthorizationView
 from hs_discover.views import SearchAPI, SearchView
-from hs_rest_api.urls import hsapi_urlpatterns
 from hs_rest_api2.urls import hsapi2_urlpatterns
+from hs_rest_api.urls import hsapi_urlpatterns
 from hs_sitemap.views import sitemap
 from hs_tracking import views as tracking
 from theme import views as theme
-from theme.views import delete_resource_comment, oidc_signup, LogoutView
+from theme.views import LogoutView, delete_resource_comment, oidc_signup
 
-autocomplete_light.autodiscover()
+# autocomplete_light.autodiscover()
 admin.autodiscover()
 
 # Add the urlpatterns for any custom Django applications here.
@@ -27,153 +28,154 @@
 urlpatterns = []
 if settings.ENABLE_OIDC_AUTHENTICATION:
     urlpatterns += i18n_patterns(
-        url(r"^admin/login/$", RedirectView.as_view(url='/oidc/authenticate'), name="admin_login"),
-        url(r"^sign-up/$", oidc_signup, name='sign-up'),
-        url(r"^accounts/logout/$", LogoutView.as_view(), name='logout'),
-        url(r"^accounts/login/$", RedirectView.as_view(url='/oidc/authenticate'), name="login"),
-        url('oidc/', include('mozilla_django_oidc.urls')),
+        path("admin/login/", RedirectView.as_view(url='/oidc/authenticate'), name="admin_login"),
+        path("sign-up/", oidc_signup, name='sign-up'),
+        path("accounts/logout/", LogoutView.as_view(), name='logout'),
+        path("accounts/login/", RedirectView.as_view(url='/oidc/authenticate'), name="login"),
+        path('oidc/', include('mozilla_django_oidc.urls')),
     )
 
 urlpatterns += i18n_patterns(
     # Change the admin prefix here to use an alternate URL for the
     # admin interface, which would be marginally more secure.
-    url("^admin/", include(admin.site.urls)),
-    url(r"^o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
-    url(
-        "^o/groupauthorize/(?P<group_id>[0-9]+)/$",
+    path("admin/", include(admin.site.urls)),
+    path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
+    path(
+        "o/groupauthorize/<int:group_id>/",
         GroupAuthorizationView.as_view(),
         name="group-authorize",
     ),
-    url("^r/(?P<shortkey>[A-z0-9\-_]+)", hs_core_views.short_url), # noqa
-    url(
-        r"^tracking/reports/profiles/$",
+    re_path("^r/(?P<shortkey>[A-z0-9\-_]+)", hs_core_views.short_url), # noqa
+    path(
+        "tracking/reports/profiles/",
         tracking.VisitorProfileReport.as_view(),
         name="tracking-report-profiles",
     ),
-    url(
-        r"^tracking/reports/history/$",
+    path(
+        "tracking/reports/history/",
         tracking.HistoryReport.as_view(),
         name="tracking-report-history",
     ),
-    url(r"^tracking/$", tracking.UseTrackingView.as_view(), name="tracking"),
-    url(
-        r"^tracking/applaunch/", tracking.AppLaunch.as_view(), name="tracking-applaunch"
-    ),
-    url(r"^user/$", theme.UserProfileView.as_view()),
-    url(r"^user/(?P<user>.*)/", theme.UserProfileView.as_view()),
-    url(r"^comment/$", theme.comment),
-    url(
+    path("tracking/", tracking.UseTrackingView.as_view(), name="tracking"),
+    path("tracking/applaunch/", tracking.AppLaunch.as_view(), name="tracking-applaunch"),
+    path("user/", theme.UserProfileView.as_view()),
+    re_path(r"^user/(?P<user>.*)/", theme.UserProfileView.as_view()),
+    path("comment/", theme.comment),
+    re_path(
         r"^comment/delete/(?P<id>.*)/$",
         delete_resource_comment,
         name="delete_resource_comment",
     ),
-    url(r"^rating/$", theme.rating),
-    url(
+    path("rating/", theme.rating),
+    re_path(
         r"^profile/(?P<profile_user_id>.*)/$",
         theme.update_user_profile,
         name="update_profile",
     ),
-    url(r'^act-on-quota-request/(?P<quota_request_id>[0-9]+)/(?P<action>[a-z]+)/$',
-        theme.act_on_quota_request, name='act_on_quota_request_noauth'),
-    url(r'^act-on-quota-request/(?P<quota_request_id>[0-9]+)/(?P<action>[a-z]+)/(?P<uidb36>[-\w]+)/(?P<token>[-\w]+)/$',
+    re_path(r"^act-on-quota-request/(?P<quota_request_id>[0-9]+)/(?P<action>[a-z]+)/$",
+            theme.act_on_quota_request, name='act_on_quota_request_noauth'),
+    re_path(
+        r'^act-on-quota-request/(?P<quota_request_id>[0-9]+)/(?P<action>[a-z]+)/(?P<uidb36>[-\w]+)/(?P<token>[-\w]+)/$',
         theme.act_on_quota_request, name='act_on_quota_request'),
-    url(r'^quota-request/$', theme.quota_request, name='quota_request'),
-    url(r"^update_password/$", theme.update_user_password, name="update_password"),
-    url(
+    path("quota-request/", theme.quota_request, name='quota_request'),
+    path("update_password/", theme.update_user_password, name="update_password"),
+    re_path(
         r"^resend_verification_email/(?P<email>.*)/",
         theme.resend_verification_email,
         name="resend_verification_email",
     ),
-    url(
-        r"^reset_password_request/$",
+    path(
+        "reset_password_request/",
         theme.request_password_reset,
         name="reset_password_request",
     ),
-    url(
-        r"^new_password_for_reset/$",
+    path(
+        "new_password_for_reset/",
         theme.UserPasswordResetView.as_view(),
         name="new_password_for_reset",
     ),
-    url(
-        r"^confirm_reset_password/$",
+    path(
+        "confirm_reset_password/",
         theme.reset_user_password,
         name="confirm_reset_password",
     ),
-    url(r"^deactivate_account/$", theme.deactivate_user, name="deactivate_account"),
-    url(r"^landingPage/$", theme.landingPage, name="landing_page"),
-    url(r"^home/$", theme.dashboard, name="dashboard"),
-    url(r"^$", theme.home_router, name="home_router"),
-    url(
+    path("deactivate_account/", theme.deactivate_user, name="deactivate_account"),
+    path("landingPage/", theme.landingPage, name="landing_page"),
+    path("home/", theme.dashboard, name="dashboard"),
+    path("", theme.home_router, name="home_router"),
+    re_path(
         r"^email_verify/(?P<new_email>.*)/(?P<token>[-\w]+)/(?P<uidb36>[-\w]+)/",
         theme.email_verify,
         name="email_verify",
     ),
-    url(
+    re_path(
         r"^email_verify_password_reset/(?P<token>[-\w]+)/(?P<uidb36>[-\w]+)/",
         theme.email_verify_password_reset,
         name="email_verify_password_reset",
     ),
-    url(r"^verify/(?P<token>[0-9a-zA-Z:_\-]*)/", hs_core_views.verify),
-    url(r"^django_irods/", include("django_irods.urls")),
-    url(r"^autocomplete/", include("autocomplete_light.urls")),
-    url(r"^discoverapi/$", SearchAPI.as_view(), name="DiscoverAPI"),
-    url(r"^search/$", SearchView.as_view(), name="Discover"),
-    url(
-        r"^topics/$",
+    re_path(r"^verify/(?P<token>[0-9a-zA-Z:_\-]*)/", hs_core_views.verify),
+    path("django_irods/", include("django_irods.urls")),
+    # path("autocomplete/", include("autocomplete_light.urls")),
+    path("discoverapi/", SearchAPI.as_view(), name="DiscoverAPI"),
+    path("search/", SearchView.as_view(), name="Discover"),
+    path(
+        "topics/",
         hs_communities.views.communities.TopicsView.as_view(),
         name="topics",
     ),
-    url(r"^sitemap/$", sitemap, name="sitemap"),
-    url(r"^sitemap", include("hs_sitemap.urls")),
-    url(r"^groups", hs_core_views.FindGroupsView.as_view(), name="groups"),
-    url(
-        r"^communities/$",
+    path("sitemap/", sitemap, name="sitemap"),
+    path("sitemap", include("hs_sitemap.urls")),
+    path("groups", hs_core_views.FindGroupsView.as_view(), name="groups"),
+    path(
+        "communities/",
         hs_communities.views.communities.FindCommunitiesView.as_view(),
         name="communities",
     ),
-    url(
-        r"^community/(?P<community_id>[0-9]+)/$",
+    path(
+        "community/<int:community_id>/",
         hs_communities.views.communities.CommunityView.as_view(),
         name="community",
     ),
-    url(
-        r"^communities/manage-requests/$",
+    path(
+        "communities/manage-requests/",
         hs_communities.views.communities.CommunityCreationRequests.as_view(),
         name="manage_requests",
     ),
-    url(
-        r"^communities/manage-requests/(?P<rid>[0-9]+)/$",
+    path(
+        "communities/manage-requests/<int:rid>/",
         hs_communities.views.communities.CommunityCreationRequest.as_view(),
         name="manage_request"
     ),
-    url(
-        r"^collaborate/$",
+    path(
+        "collaborate/",
         hs_communities.views.communities.CollaborateView.as_view(),
         name="collaborate",
     ),
-    url(r"^my-resources/$", hs_core_views.my_resources, name="my_resources"),
-    url(
-        r"^my-resources-counts/$",
+    path("my-resources/", hs_core_views.my_resources, name="my_resources"),
+    path(
+        "my-resources-counts/",
         hs_core_views.my_resources_filter_counts,
         name="my_resources_counts",
     ),
-    url(r"^my-groups/$", hs_core_views.MyGroupsView.as_view(), name="my_groups"),
-    url(
-        r"^my-communities/$",
+    path("my-groups/", hs_core_views.MyGroupsView.as_view(), name="my_groups"),
+    path(
+        "my-communities/",
         hs_communities.views.communities.MyCommunitiesView.as_view(),
         name="my_communities",
     ),
-    url(
+    re_path(
         r"^group/(?P<group_id>[0-9]+)", hs_core_views.GroupView.as_view(), name="group"
     ),
-    url(r"^apps/$", hs_core_views.apps.AppsView.as_view(), name="apps"),
+    path("apps/", hs_core_views.apps.AppsView.as_view(), name="apps"),
+    path("user-autocomplete/", alr.UserAutocompleteView.as_view(), name="user-autocomplete"),
+    path("group-autocomplete/", alr.GroupAutocompleteView.as_view(), name="group-autocomplete"),
 )
 
 # Filebrowser admin media library.
 if getattr(settings, "PACKAGE_NAME_FILEBROWSER") in settings.INSTALLED_APPS:
     urlpatterns += i18n_patterns(
-        url(
-            "^admin/media-library/",
+        path(
+            "admin/media-library/",
             include("%s.urls" % settings.PACKAGE_NAME_FILEBROWSER),
         ),
     )
@@ -182,33 +184,33 @@
 
 # Put API URLs before Mezzanine so that Mezzanine doesn't consume them
 urlpatterns += [
-    url("", include("hs_core.resourcemap_urls")),
-    url("", include("hs_core.metadata_terms_urls")),
-    url("", include("hs_core.debug_urls")),
-    url("^irods/", include("irods_browser_app.urls")),
-    url("^access/", include("hs_access_control.urls")),
-    url("^hs_metrics/", include("hs_metrics.urls")),
+    path("", include("hs_core.resourcemap_urls")),
+    path("", include("hs_core.metadata_terms_urls")),
+    path("", include("hs_core.debug_urls")),
+    path("irods/", include("irods_browser_app.urls")),
+    path("access/", include("hs_access_control.urls")),
+    path("hs_metrics/", include("hs_metrics.urls")),
 ]
 
 # robots.txt URLs for django-robots
 urlpatterns += [
-    url(r"^robots\.txt", include("robots.urls")),
+    path(r"robots\.txt", include("robots.urls")),
 ]
-from django.views.static import serve # noqa
+from django.views.static import serve  # noqa
 
 if settings.DEBUG is False and not settings.ENABLE_STATIC_CLOUD_STORAGE:
     # if DEBUG is True it will be served automatically
     urlpatterns += [
-        url(r"^static/(?P<path>.*)$", serve, {"document_root": settings.STATIC_ROOT}),
+        re_path(r"^static/(?P<path>.*)$", serve, {"document_root": settings.STATIC_ROOT}),
     ]
 
 if "heartbeat" in settings.INSTALLED_APPS:
     from heartbeat.urls import urlpatterns as heartbeat_urls
 
-    urlpatterns += [url(r"^heartbeat/", include(heartbeat_urls))]
+    urlpatterns += [path("heartbeat/", include(heartbeat_urls))]
 
 if "health_check" in settings.INSTALLED_APPS:
-    urlpatterns += [url(r'^ht/', include('health_check.urls'))]
+    urlpatterns += [path('ht/', include('health_check.urls'))]
 
 urlpatterns += [
     # We don't want to presume how your homepage works, so here are a
@@ -220,7 +222,7 @@
     # one homepage pattern, so if you use a different one, comment this
     # one out.
     # url("^$", direct_to_template, {"template": "index.html"}, name="home"),
-    url(r"^tests/$", direct_to_template, {"template": "tests.html"}, name="tests"),
+    path("tests/", direct_to_template, {"template": "tests.html"}, name="tests"),
     # HOMEPAGE AS AN EDITABLE PAGE IN THE PAGE TREE
     # ---------------------------------------------
     # This pattern gives us a normal ``Page`` object, so that your
@@ -234,7 +236,7 @@
     # "/.html" - so for this case, the template "pages/index.html"
     # should be used if you want to customize the homepage's template.
     # Any impact on this with the new home routing mechanism.
-    url("^$", page, {"slug": "/"}, name="home"),
+    path("", page, {"slug": "/"}, name="home"),
     # HOMEPAGE FOR A BLOG-ONLY SITE
     # -----------------------------
     # This pattern points the homepage to the blog post listing page,
@@ -244,8 +246,8 @@
     # page tree in the admin if it was installed.
     # url("^$", "mezzanine.blog.views.blog_post_list", name="home"),
     # Override Mezzanine URLs here, before the Mezzanine URL include
-    url("^accounts/signup/", theme.signup),
-    url("^accounts/verify/(?P<uidb36>[-\w]+)/(?P<token>[-\w]+)", theme.signup_verify), # noqa
+    re_path("^accounts/signup/", theme.signup),
+    re_path("^accounts/verify/(?P<uidb36>[-\w]+)/(?P<token>[-\w]+)", theme.signup_verify), # noqa
     # MEZZANINE'S URLS
     # ----------------
     # ADD YOUR OWN URLPATTERNS *ABOVE* THE LINE BELOW.
@@ -256,7 +258,7 @@
     # ``mezzanine.urls``, go right ahead and take the parts you want
     # from it, and use them directly below instead of using
     # ``mezzanine.urls``.
-    url("^", include("mezzanine.urls")),
+    path("", include("mezzanine.urls")),
     # MOUNTING MEZZANINE UNDER A PREFIX
     # ---------------------------------
     # You can also mount all of Mezzanine's urlpatterns under a
diff --git a/irods_browser_app/urls.py b/irods_browser_app/urls.py
index 6e1574f86f..b7ae4a0144 100644
--- a/irods_browser_app/urls.py
+++ b/irods_browser_app/urls.py
@@ -1,8 +1,9 @@
-from django.conf.urls import url
+from django.urls import path
+
 from irods_browser_app import views
 
 urlpatterns = [
-    url(r'^login/$', views.login, name='irods_login'),
-    url(r'^store/$', views.store, name='irods_store'),
-    url(r'^upload_add/$', views.upload_add, name='irods_upload_add'),
+    path('login/', views.login, name='irods_login'),
+    path('store/', views.store, name='irods_store'),
+    path('upload_add/', views.upload_add, name='irods_upload_add'),
 ]
diff --git a/local-dev-first-start-only.sh b/local-dev-first-start-only.sh
index 9c4b72ec11..fc73866a09 100755
--- a/local-dev-first-start-only.sh
+++ b/local-dev-first-start-only.sh
@@ -285,10 +285,6 @@ cd irods/
 cd ..
 sleep 2
 
-echo "Chown root items"
-echo " - exec hydroshare bash scripts/chown-root-items"
-docker exec hydroshare bash scripts/chown-root-items
-
 echo
 echo '########################################################################################################################'
 echo -e " Setting up PostgreSQL container and Importing Django DB"
@@ -337,11 +333,9 @@ echo " Migrating data"
 echo '########################################################################################################################'
 echo
 
-docker exec hydroshare bash scripts/chown-root-items
-
-echo "  -docker exec -u hydro-service hydroshare python manage.py collectstatic -v0 --noinput"
+echo "  - docker exec hydroshare chown -R hydro-service:storage-hydro /tmp /shared_tmp"
+docker exec hydroshare chown -R hydro-service:storage-hydro /tmp /shared_tmp
 echo
-docker exec -u hydro-service hydroshare python manage.py collectstatic -v0 --noinput
 
 echo
 echo "  - docker exec -u hydro-service hydroshare python manage.py migrate sites --noinput"
diff --git a/theme/admin.py b/theme/admin.py
index 873a0ca76c..a8f3ddad4a 100755
--- a/theme/admin.py
+++ b/theme/admin.py
@@ -1,11 +1,11 @@
-from django.contrib import admin
 from django import forms
+from django.contrib import admin
 from django.contrib.auth.models import User
-
 from mezzanine.core.admin import TabularDynamicInlineAdmin
 from mezzanine.utils.admin import SingletonAdmin
 
-from .models import SiteConfiguration, HomePage, IconBox, UserQuota, QuotaMessage, QuotaRequest
+from .models import (HomePage, IconBox, QuotaMessage, QuotaRequest,
+                     SiteConfiguration, UserQuota)
 
 
 class IconBoxInline(TabularDynamicInlineAdmin):
diff --git a/theme/forms.py b/theme/forms.py
index 133b7841a7..b20acc31b0 100644
--- a/theme/forms.py
+++ b/theme/forms.py
@@ -1,29 +1,28 @@
 import requests
-
 from django import forms
-from django.utils.translation import gettext, gettext_lazy as _
-from django_comments.signals import comment_was_posted
-from django_comments.forms import CommentSecurityForm
-from django_comments.models import Comment
+from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
-from django.utils.encoding import force_text
 from django.utils import timezone
-from django.contrib.auth.models import User
-
+from django.utils.encoding import force_str
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
+from django_comments.forms import CommentSecurityForm
+from django_comments.models import Comment
+from django_comments.signals import comment_was_posted
+from mezzanine.conf import settings
 from mezzanine.core.forms import Html5Mixin
-from mezzanine.generic.models import ThreadedComment, Rating
-from mezzanine.utils.views import ip_for_request
-from mezzanine.utils.email import split_addresses, send_mail_template
+from mezzanine.generic.models import Rating, ThreadedComment
 from mezzanine.utils.cache import add_cache_bypass
-from mezzanine.conf import settings
+from mezzanine.utils.email import send_mail_template, split_addresses
+from mezzanine.utils.views import ip_for_request
 
-from .models import UserProfile
 from hs_core.hydroshare.users import create_account
-from hs_core.templatetags.hydroshare_tags import best_name
 from hs_core.models import Party
-
+from hs_core.templatetags.hydroshare_tags import best_name
 from hydroshare import settings as hydroshare_settings
 
+from .models import UserProfile
+
 COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
 
 
@@ -72,7 +71,7 @@ def get_comment_create_data(self):
         """
         return dict(
             content_type=ContentType.objects.get_for_model(self.target_object),
-            object_pk=force_text(self.target_object._get_pk_val()),
+            object_pk=force_str(self.target_object._get_pk_val()),
             comment=self.cleaned_data["comment"],
             submit_date=timezone.now(),
             site_id=settings.SITE_ID,
diff --git a/theme/static/js/collection-resource.js b/theme/static/js/collection-resource.js
index 9676052afb..82c480a114 100644
--- a/theme/static/js/collection-resource.js
+++ b/theme/static/js/collection-resource.js
@@ -197,6 +197,7 @@ function remove_collection_item_ajax(res_id, move_to_candidate_list) {
         type: "POST",
         url: $form.attr('action'),
         dataType: "json",
+        traditional: true,
         data: {"resource_id_list": resource_id_list, "update_type": "remove"},
         success: function(result) {
             /* The div contains now the updated form */
@@ -285,6 +286,7 @@ function add_collection_item_ajax() {
         type: "POST",
         url: $form.attr('action'),
         dataType: "json",
+        traditional: true,
         data: {"resource_id_list": resource_id_list, "update_type": "add"},
         success: function(result) {
             /* The div contains now the updated form */
diff --git a/theme/static/js/custom.js b/theme/static/js/custom.js
index 3e11d5866b..a63a06e307 100755
--- a/theme/static/js/custom.js
+++ b/theme/static/js/custom.js
@@ -255,14 +255,14 @@ $(document).ready(function () {
 
     // Fixes positioning of user and group autocomplete input.
     // Solution found here: https://github.com/yourlabs/django-autocomplete-light/issues/253#issuecomment-35863432
-    yourlabs.Autocomplete.prototype.fixPosition = function (html) {
-        // Make sure overflow won't hide the select
-        this.input.parents().filter(function () {
-            return $(this).css('overflow') === 'hidden';
-        }).first().css('overflow', 'visible');
-
-        this.box.insertAfter(this.input).css({top: 35, left: 0});
-    };
+    // yourlabs.Autocomplete.prototype.fixPosition = function (html) {
+    //     // Make sure overflow won't hide the select
+    //     this.input.parents().filter(function () {
+    //         return $(this).css('overflow') === 'hidden';
+    //     }).first().css('overflow', 'visible');
+    //
+    //     this.box.insertAfter(this.input).css({top: 35, left: 0});
+    // };
 
     // Prevent clicking on list items dismissing the modal window
     $(".autocomplete-light-widget > input.autocomplete").each(function (i, el) {
@@ -346,4 +346,4 @@ async function checkProfileComplete(){
         localStorage.setItem('missing-profile-fields', missing_profile_fields.toString())
         return [user, missing_profile_fields];
     }
-}
\ No newline at end of file
+}
diff --git a/theme/static/js/group-actions.js b/theme/static/js/group-actions.js
index e1bf556f0d..6026b74af7 100644
--- a/theme/static/js/group-actions.js
+++ b/theme/static/js/group-actions.js
@@ -1,8 +1,10 @@
 function group_invite_ajax_submit() {
-  if (!$("#id_user-deck > .hilight").length) {
-    return false; // If no user selected, ignore the request
+  // get the value of the selet from id_user_invite_to_group
+  let share_with = $("#id_user_invite_to_group").find(':selected').val();
+  // if no user is selected, ignore the request
+  if (!share_with) {
+    return false;
   }
-  var share_with = $("#id_user-deck > .hilight")[0].getAttribute("data-value");
   var form = $("#invite-to-group");
   var datastring = form.serialize();
   var url = form.attr("action") + share_with + "/";
diff --git a/theme/static/js/hs-vue/left-header-app.js b/theme/static/js/hs-vue/left-header-app.js
index 3c35032c27..3155274347 100644
--- a/theme/static/js/hs-vue/left-header-app.js
+++ b/theme/static/js/hs-vue/left-header-app.js
@@ -133,8 +133,7 @@ Vue.component('add-author-modal', {
             let vue = this;
             vue.addAuthorError = null;
 
-            let autoComplete = $("#add-author-modal #user-autocomplete").yourlabsAutocomplete().data;
-            let userId = autoComplete.exclude ? autoComplete.exclude[0] : null;
+            let userId = $("#id_author").find(':selected').val();
 
             if (!userId) {
                 vue.addAuthorError = "Select a user to add as an author";
diff --git a/theme/static/js/hs-vue/manage-access-app.js b/theme/static/js/hs-vue/manage-access-app.js
index f7aa02fe89..0901467271 100644
--- a/theme/static/js/hs-vue/manage-access-app.js
+++ b/theme/static/js/hs-vue/manage-access-app.js
@@ -11,6 +11,7 @@ let manageAccessApp = new Vue({
             return user;
         }),
         currentUser: CURRENT_USER_ID,
+        targetUserId: null,
         selfAccessLevel: SELF_ACCESS_LEVEL,
         quotaHolder: USERS_JSON.find(function(user) {
             return user.id === QUOTA_HOLDER_PK;
@@ -265,26 +266,12 @@ let manageAccessApp = new Vue({
             });
         },
         grantAccess: function () {
-            let targetUserId;
-
+            let targetUserId = this.targetUserId;
             if (this.isInviteUsers) {
-                if ($("#user-deck > .hilight").length > 0) {
-                    targetUserId = parseInt($("#user-deck > .hilight")[0].getAttribute("data-value"));
-                }
-                else {
-                    return false;   // No user selected
-                }
             }
             else {
-                if ($("#id_group-deck > .hilight").length > 0) {
-                    targetUserId = parseInt($("#id_group-deck > .hilight")[0].getAttribute("data-value"));
-                }
-                else {
-                    return false;   // No group selected
-                }
             }
 
-            $(".hilight > span").click(); // Clear the autocomplete
             this.error = "";
             let vue = this;
 
@@ -337,6 +324,9 @@ let manageAccessApp = new Vue({
                         newUserAccess.loading = false;
                         vue.users.push(newUserAccess);
                     }
+                    // clear the select
+                    $('#id_user_resource_permission').val(null).trigger('change');
+                    $('#id_group').val(null).trigger('change');
                 }
                 else {
                     vue.error = resp.error_msg;
@@ -501,3 +491,21 @@ let manageAccessApp = new Vue({
         }
     },
 });
+
+$(document).ready(function() {
+    // initialize the select2 event listeners
+    $('#id_user_resource_permission').on('select2:select', function (e) {
+        let userId = e.params.data.id;
+        manageAccessApp.targetUserId = userId;
+    });
+    $('#id_user_resource_permission').on('select2:unselect', function (e) {
+        manageAccessApp.targetUserId = null;
+    });
+    $('#id_group').on('select2:select', function (e) {
+        let groupId = e.params.data.id;
+        manageAccessApp.targetUserId = groupId;
+    });
+    $('#id_group').on('select2:unselect', function (e) {
+        manageAccessApp.targetUserId = null;
+    });
+});
\ No newline at end of file
diff --git a/theme/static/js/profile.js b/theme/static/js/profile.js
index e9cf2950b1..e919055a74 100755
--- a/theme/static/js/profile.js
+++ b/theme/static/js/profile.js
@@ -160,24 +160,6 @@ function onFormRequiredChange() {
     }
 }
 
-function irods_account_link(data_target, text) {
-    return "<a data-toggle='modal' data-target='" + data_target + "'>" + text + "</a>";
-}
-
-function irods_status_info(alert_type, status, title) {
-    return "<div class=\"col-sm-12\">" +
-            "<div class=\"alert " + alert_type + " alert-dismissible\" role=\"alert\">" +
-            "<button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>" +
-            "<strong>" + title + "</strong><div>" + status + "</div></div></div>"
-}
-
-function create_irods_account() {
-    let email = 'help@cuahsi.org';
-    let subject = 'Request for new iRODS account';
-    let emailBody = 'I would like to request an iRODS account.';
-    document.location = "mailto:"+email+"?subject="+subject+"&body="+emailBody;
-}
-
 function getUrlVars()
 {
     var vars = [], hash;
@@ -297,10 +279,6 @@ $(document).ready(function () {
     // Multiple orgs are a string delimited by ";" --wrap them so we can style them
     $("#organization").splitAndWrapWithClass(";", "organization-divider");
     
-    $("#btn-request-irods-account").click(() => {
-        $('#request-irods-account-dialog').modal('hide');
-        create_irods_account();
-    });
 
     // File name preview for Add CV
     $('.btn-primary.btn-file :file').on('fileselect', function (event, numFiles, label) {
diff --git a/theme/static/js/resourceLandingAjax.js b/theme/static/js/resourceLandingAjax.js
index 8551cfbbe9..68454b4045 100755
--- a/theme/static/js/resourceLandingAjax.js
+++ b/theme/static/js/resourceLandingAjax.js
@@ -626,12 +626,10 @@ function update_sqlite_file_ajax_submit() {
 }
 
 function get_user_info_ajax_submit(url, obj) {
-    var entry = $(obj).closest("div[data-contributor-type]").find("#user-deck > .hilight");
-    if (entry.length < 1) {
+    let userID = $("#id_contributor").find(':selected').val();
+    if (!userID) {
         return false;
     }
-
-    var userID = entry[0].getAttribute("data-value");
     url = url + userID + "/false";
 
     $.ajax({
diff --git a/theme/templates/accounts/profile.html b/theme/templates/accounts/profile.html
index b13583fb4e..84e8d379d8 100755
--- a/theme/templates/accounts/profile.html
+++ b/theme/templates/accounts/profile.html
@@ -84,11 +84,6 @@
 
                                     {#        Account deactivation       #}
                                     <div><a data-toggle="modal" data-target="#deactivate-account-dialog">Deactivate HydroShare account</a></div>
-                                    <hr>
-                                    {# Account creation/deletion in iRODS user zone #}
-                                    <div id="irods-account-container">
-                                        <a data-toggle="modal" data-target="#request-irods-account-dialog">Request an iRODS user account</a>
-                                    </div>
                                 </div>
                             </div>
                             {% endif %}
@@ -865,37 +860,6 @@ <h4 class="modal-title" id="deleteCV">Delete CV</h4>
                     </div>
                 </div>
             </div>
-
-            <!-- Request iRODS User Account Modal -->
-            <div class="modal fade" id="request-irods-account-dialog" tabindex="-1" role="dialog"
-                 aria-labelledby="myModalLabel" aria-hidden="true">
-                <div class="modal-dialog modal-sm">
-                    <div class="modal-content">
-                        <div class="modal-header">
-                            <button type="button" class="close" data-dismiss="modal"
-                                    aria-label="Close"><span aria-hidden="true">&times;</span>
-                            </button>
-                            <h4 class="modal-title" id="myModalLabel">Request an iRODS user account</h4>
-                        </div>
-                        <div class="modal-body">
-                            {% csrf_token %}
-                            <div class="row">
-                                <div class="col-sm-10 col-xs-12">
-                                    <div>You can request that a user account be created in the HydroShare user zone.
-                                        This account will allow you to access your data using iRODS commands and clients.
-                                        You will be able to access your data in the user zone at <strong>users.hydroshare.org</strong>.
-                                        Note that your request must be sent from the email associated with your HydroShare account.
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="modal-footer">
-                            <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-                            <button id="btn-request-irods-account" class="btn btn-primary" type="button">Email Request</button>
-                        </div>
-                    </div>
-                </div>
-            </div>
         </form>
         {% endif %}
         {% if profile_user.id == user.id or user.is_superuser %}
diff --git a/theme/templates/base.html b/theme/templates/base.html
index fbed4a9427..ff2c2766e8 100755
--- a/theme/templates/base.html
+++ b/theme/templates/base.html
@@ -54,7 +54,7 @@
 {% endblock %}
 
 {% endcompress %}
-{% include 'autocomplete_light/static.html' %}
+{#{% include 'autocomplete_light/static.html' %}#}
 
 {% block extra_head %}{% endblock %}
 {% if not page|is_debug %}
@@ -63,6 +63,9 @@
       heap.load("1320547260");
 </script>
 {% endif %}
+<script>
+    const STATIC_URL = "{{ STATIC_URL }}" || "/static/static/";
+</script>
 </head>
 
 <body id="{% block body_id %}body{% endblock %}">
diff --git a/theme/templates/newbase.html b/theme/templates/newbase.html
index ee6bd08bc5..2848211934 100755
--- a/theme/templates/newbase.html
+++ b/theme/templates/newbase.html
@@ -87,7 +87,7 @@
     <script src="{% static 'js/scrolltopcontrol.js' %}"></script>
     <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
     <script src="{% static 'js/bootstrap-extras.js' %}"></script>
-    {% include 'autocomplete_light/static.html' %}
+{#    {% include 'autocomplete_light/static.html' %}#}
 
     {% if not page|is_debug %}
         <script type="text/javascript">
@@ -107,7 +107,7 @@
         </script>
     {% endif %}
     <script>
-        const STATIC_URL = "{{ settings.STATIC_URL }}" || "/static/static/";
+        const STATIC_URL = "{{ STATIC_URL }}" || "/static/static/";
     </script>
 </head>
 
diff --git a/theme/templates/pages/group.html b/theme/templates/pages/group.html
index 6798980b60..2977975406 100644
--- a/theme/templates/pages/group.html
+++ b/theme/templates/pages/group.html
@@ -8,7 +8,9 @@
 {% endblock %}
 
 {% block meta_title %}{{ group.name|default:"Group" }}{% endblock %}
-
+{%  block extra_head %}
+    {{ add_view_user_form.media }}
+{% endblock %}
 {% block main %}
 {% include 'includes/groups-nav.html' with active='my_groups' %}
 
@@ -310,7 +312,7 @@ <h4><i>This group is currently inactive.</i></h4>
                                     <label>Privacy *</label>
                                     <div class="radio">
                                         <label>
-                                            <input type="radio" name="privacy_level" value="public" 
+                                            <input type="radio" name="privacy_level" value="public"
                                                 {% if group.gaccess.public and group.gaccess.discoverable %}checked{% endif%}>Public
                                             <div class="text-muted">Other users can find the group and see who has
                                                 membership.
@@ -387,7 +389,7 @@ <h4 class="modal-title" id="Invite">Invite People</h4>
                 <div class="modal-body">
                     <div id="div-invite-people" class="flex">
                         {% csrf_token %}
-                        {{ add_view_user_form.user }}
+                        {{ add_view_user_form.user_invite_to_group }}
                         <form action="">
                             <a id="btn-group-invite"
                                 class="btn btn-add-access btn-success glyphicon glyphicon-send has-space-left"><span
@@ -587,7 +589,7 @@ <h4 class="modal-title" id="myModalLabel">Edit Group Details</h4>
 <script type="text/javascript" src="{% static 'js/group.js' %}"></script>
 <script type="text/javascript">
   var resourceTable;
-  
+
   var ACTIONS_COL = 0;
   var RESOURCE_TYPE_COL = 1;
   var TITLE_COL = 2;
@@ -603,7 +605,7 @@ <h4 class="modal-title" id="myModalLabel">Edit Group Details</h4>
   var SHARING_STATUS_COL = 12;
   var DATE_CREATED_SORT_COL = 13;
   var ACCESS_GRANTOR_COL = 14;
-  
+
   var colDefs = [
   {
     targets: [ACTIONS_COL], // Row selector and controls
@@ -674,5 +676,9 @@ <h4 class="modal-title" id="myModalLabel">Edit Group Details</h4>
   },
 ];
 </script>
-
-{% endblock %}
\ No newline at end of file
+<script>
+    // https://github.com/select2/select2/issues/1436#issuecomment-21028474
+    // incompatibility between Select2 and Bootstrap modals
+    $.fn.modal.Constructor.prototype.enforceFocus = function() {};
+</script>
+{% endblock %}
diff --git a/theme/templates/resource-landing-page/manage-access.html b/theme/templates/resource-landing-page/manage-access.html
index d8ef5c5242..59568d15b4 100644
--- a/theme/templates/resource-landing-page/manage-access.html
+++ b/theme/templates/resource-landing-page/manage-access.html
@@ -77,7 +77,7 @@ <h3 class="panel-title">Give access</h3>
 
                         <div class="row">
                             <div v-show="isInviteUsers" class="add-view-user-form col-xs-12">
-                                {{ add_view_invite_user_form.user }}
+                                {{ add_view_resource_permission_form.user_resource_permission }}
                             </div>
 
                             <div v-show="!isInviteUsers" class="add-view-group-form col-xs-12">
@@ -159,8 +159,8 @@ <h3 class="panel-title">Sharing status</h3>
                                             @click="setResourceAccess('make_public')"
                                             :class="{active: resAccess.isPublic}"
                                             class="btn btn-default"
-                                            id="btn-public" data-toggle="tooltip" data-placement="auto" 
-                                            :title="!canChangeResourceFlags ? this.accessDeniedTitle : 'Can be viewed and downloaded by anyone.'" 
+                                            id="btn-public" data-toggle="tooltip" data-placement="auto"
+                                            :title="!canChangeResourceFlags ? this.accessDeniedTitle : 'Can be viewed and downloaded by anyone.'"
                                             type="button">
                                             Public
                                         </button>
@@ -170,7 +170,7 @@ <h3 class="panel-title">Sharing status</h3>
                                                 @click="setResourceAccess('make_discoverable')"
                                                 :class="{active: resAccess.isDiscoverable && !resAccess.isPublic}"
                                                 class="btn btn-default"
-                                                id="btn-discoverable" data-toggle="tooltip" data-placement="auto" 
+                                                id="btn-discoverable" data-toggle="tooltip" data-placement="auto"
                                                 :title="!canChangeResourceFlags ? this.accessDeniedTitle : 'Metadata is public but data are protected.'"
                                                 type="button">
                                             Discoverable
@@ -262,4 +262,4 @@ <h3 class="panel-title">Sharing status</h3>
     </div>
 </div>
 
-<script type="text/javascript" src="{% static 'js/hs-vue/manage-access-app.js' %}"></script>
\ No newline at end of file
+<script type="text/javascript" src="{% static 'js/hs-vue/manage-access-app.js' %}"></script>
diff --git a/theme/templates/resource-landing-page/modals.html b/theme/templates/resource-landing-page/modals.html
index 2ec9a75895..1beffaff6a 100755
--- a/theme/templates/resource-landing-page/modals.html
+++ b/theme/templates/resource-landing-page/modals.html
@@ -64,7 +64,7 @@ <h4 class="modal-title" id="add-creator"> Add Author </h4>
                         {#  Existing HydroShare User  #}
                         <div v-show="authorType === authorTypes.EXISTING_HS_USER">
                             <div class="modal-body">
-                                {{ add_view_hs_user_form.user }}
+                                {{ add_view_hs_user_form.author }}
 
                                 <div v-if="addAuthorError" class="alert alert-danger has-space-top">
                                     ${addAuthorError}
@@ -427,7 +427,7 @@ <h4 class="modal-title" id="add-contributor"> Add Contributor </h4>
                 {% with "user-type-hs-user" as div_id %}
                 <div data-contributor-type="hs_user">
                     <div class="modal-body">
-                        {{ add_view_contrib_user_form.user }}
+                        {{ add_view_contrib_user_form.contributor }}
                     </div>
 
                     <form action="/hsapi/_internal/{{ cm.short_id }}/contributor/add-metadata/" method="POST"
@@ -1461,7 +1461,7 @@ <h4 class="modal-title" id="submit-for-publication-title">Modifications necessar
                                 <p>
                                     This resource doesn't meet the
                                     <a href="https://help.hydroshare.org/publishing-in-hydroshare/minimum-metadata-requirements-for-publishing-in-hydroshare/" target="_blank">minimum metadata standards</a>
-                                    for publication and adherence to 
+                                    for publication and adherence to
                                     <a href="https://help.hydroshare.org/publishing-in-hydroshare/terms-of-use-and-community-guidelines/" target="_blank">community guidelines</a>:
                                     <ul>
                                         {% for missing in missing_metadata_elements_for_publication %}
@@ -1735,7 +1735,7 @@ <h4 class="modal-title">Get file URL</h4>
                                     <p class="discoverable">
                                         You have requested the URL for a file that is within a Discoverable resource.
                                         Only you and other HydroShare users who have been granted at least "view" permission will be able to access this URL.
-                                        If you want this URL to be publicly available, 
+                                        If you want this URL to be publicly available,
                                         change the sharing status of your resource to "public" or enable Private Link Sharing.
                                     </p>
                                     <p class="private_sharing">
@@ -1777,4 +1777,9 @@ <h4 class="modal-title">Get file URL</h4>
 {% block extra_js %}
     <script type="text/javascript" src="{% static 'js/modals.js' %}"></script>
     <script type="text/javascript" src="{% static 'js/identifiers-form.js' %}"></script>
+    <script>
+        // https://github.com/select2/select2/issues/1436#issuecomment-21028474
+        // incompatibility between Select2 and Bootstrap modals
+        $.fn.modal.Constructor.prototype.enforceFocus = function() {};
+    </script>
 {% endblock %}
diff --git a/theme/views.py b/theme/views.py
index ea0ca5a7ea..1d1287f132 100755
--- a/theme/views.py
+++ b/theme/views.py
@@ -50,6 +50,7 @@
 from hs_core.authentication import build_oidc_url
 from hs_core.hydroshare.utils import user_from_id
 from hs_core.models import Party
+from hs_core.views.utils import is_ajax
 from hs_dictionary.models import University, UncategorizedTerm
 from hs_tracking.models import Variable
 from theme.forms import RatingForm, UserProfileForm, UserForm
@@ -257,7 +258,7 @@ def quota_request(request, *args, **kwargs):
             err_msg = "You don't have permission to request additional quota"
             messages.error(request, err_msg)
 
-    return HttpResponseRedirect(request.META['HTTP_REFERER'])
+    return HttpResponseRedirect(request.headers['referer'])
 
 
 class UserPasswordResetView(TemplateView):
@@ -300,7 +301,7 @@ def comment(request, template="generic/comments.html"):
         #     cookie_value = post_data.get(field, "")
         #     set_cookie(response, cookie_name, cookie_value)
         return response
-    elif request.is_ajax() and form.errors:
+    elif is_ajax(request) and form.errors:
         return HttpResponse(dumps({"errors": form.errors}))
     # Show errors with stand-alone comment form.
     context = {"obj": obj, "posted_comment_form": form}
@@ -384,7 +385,7 @@ def signup(request, template="accounts/account_signup.html", extra_context=None)
                 )
             else:
                 messages.error(request, str(e))
-            return HttpResponseRedirect(request.META["HTTP_REFERER"])
+            return HttpResponseRedirect(request.headers["referer"])
         else:
             if not new_user.is_active:
                 if settings.ACCOUNTS_APPROVAL_REQUIRED:
@@ -432,7 +433,7 @@ def signup(request, template="accounts/account_signup.html", extra_context=None)
     # return render(request, template, context)
 
     # This one keeps the css but not able to retained user entered data.
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def signup_verify(request, uidb36=None, token=None):
@@ -469,7 +470,7 @@ def update_user_profile(request, profile_user_id):
         identifiers = post_data_dict.get("identifiers", {})
     except Exception as ex:
         messages.error(request, "Update failed. {}".format(str(ex)))
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     org_items = request.POST["organization"].split(";")
     for org_item in org_items:
@@ -550,7 +551,7 @@ def update_user_profile(request, profile_user_id):
     except Exception as ex:
         messages.error(request, str(ex))
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 def check_organization_terms(dict_items):
@@ -579,7 +580,7 @@ def resend_verification_email(request, email):
         return redirect(reverse("login"))
     send_verification_mail(request, user, "signup_verify")
     messages.error(request, _("Resent verification email to " + user.email))
-    return redirect(request.META["HTTP_REFERER"])
+    return redirect(request.headers["referer"])
 
 
 def request_password_reset(request):
@@ -588,7 +589,7 @@ def request_password_reset(request):
         user = user_from_id(username_or_email)
     except Exception:
         messages.error(request, "No user is found for the provided username or email")
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     messages.info(
         request,
@@ -604,7 +605,7 @@ def request_password_reset(request):
     # send an email to the the user notifying the password reset request
     send_verification_mail_for_password_reset(request, user)
 
-    return HttpResponseRedirect(request.META["HTTP_REFERER"])
+    return HttpResponseRedirect(request.headers["referer"])
 
 
 @login_required
@@ -617,18 +618,18 @@ def update_user_password(request):
     password2 = password2.strip()
     if not user.check_password(old_password):
         messages.error(request, "Your current password does not match.")
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     if len(password1) < 6:
         messages.error(request, "Password must be at least 6 characters long.")
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     if password1 == password2:
         user.set_password(password1)
         user.save()
     else:
         messages.error(request, "Passwords do not match.")
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     messages.info(request, "Password reset was successful")
     return HttpResponseRedirect("/user/{}/".format(user.id))
@@ -644,14 +645,14 @@ def reset_user_password(request):
 
     if len(password1) < 6:
         messages.error(request, "Password must be at least 6 characters long.")
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     if password1 == password2:
         user.set_password(password1)
         user.save()
     else:
         messages.error(request, "Passwords do not match.")
-        return HttpResponseRedirect(request.META["HTTP_REFERER"])
+        return HttpResponseRedirect(request.headers["referer"])
 
     messages.info(request, "Password reset was successful")
     # redirect to home page


Test patch:
diff --git a/hs_composite_resource/tests/test_composite_resource.py b/hs_composite_resource/tests/test_composite_resource.py
index ec562d5a10..e3ee58c02e 100644
--- a/hs_composite_resource/tests/test_composite_resource.py
+++ b/hs_composite_resource/tests/test_composite_resource.py
@@ -27,6 +27,7 @@
                                  edit_reference_url_in_resource,
                                  move_or_rename_file_or_folder, remove_folder,
                                  unzip_file, zip_by_aggregation_file)
+from hs_file_types.enums import AggregationMetaFilePath
 from hs_file_types.models import (FileSetLogicalFile, GenericFileMetaData,
                                   GenericLogicalFile, GeoFeatureLogicalFile,
                                   GeoRasterLogicalFile,
@@ -34,7 +35,6 @@
                                   ModelProgramLogicalFile, NetCDFLogicalFile,
                                   RefTimeseriesLogicalFile,
                                   TimeSeriesLogicalFile)
-from hs_file_types.enums import AggregationMetaFilePath
 from hs_file_types.tests.utils import CompositeResourceTestMixin
 
 
@@ -3967,6 +3967,28 @@ def _test_zip_file_contents(self, zipfile, aggregation):
                 self.assertIn(f, aggr_files)
             shutil.rmtree(os.path.dirname(temp_zip_file))
 
+    def test_composite_resource_my_resources_none(self):
+        # test that only a fixed number of db queries (based on the number of resources) are
+        # generated for "my_resources" page - this test is with no resource.
+
+        # there should not be any resource at this point
+        self.assertEqual(BaseResource.objects.count(), 0)
+
+        self.client.login(username='user1', password='mypassword1')
+        # navigating to home page for initializing db queries
+        response = self.client.get(reverse("home"), follow=True)
+        self.assertTrue(response.status_code == 200)
+
+        # there should be no resources at this point
+        number_of_resources = BaseResource.objects.count()
+        self.assertEqual(number_of_resources, 0)
+        expected_query_count = self._get_expected_query_count(number_of_resources)
+        with self.assertNumQueries(expected_query_count):
+            response = self.client.get(reverse("my_resources"), follow=True)
+            self.assertTrue(response.status_code == 200)
+
+        self.composite_resource = None
+
     def test_composite_resource_my_resources_one(self):
         # test that only a fixed number of db queries (based on the number of resources) are
         # generated for "my_resources" page - this test is with one resource.
@@ -4041,7 +4063,7 @@ def test_composite_resource_landing_scales(self):
         # test that db queries for landing page have constant time complexity
 
         # expected number of queries for landing page when the resource has no resource file
-        _LANDING_PAGE_NO_RES_FILE_QUERY_COUNT = 161
+        _LANDING_PAGE_NO_RES_FILE_QUERY_COUNT = 169
 
         # expected number of queries for landing page when the resource has resource file
         _LANDING_PAGE_WITH_RES_FILE_QUERY_COUNT = _LANDING_PAGE_NO_RES_FILE_QUERY_COUNT + 16
@@ -4102,12 +4124,15 @@ def _get_expected_query_count(self, number_of_resources):
         base_query_count = 15
 
         # this is additional number of queries per resource
-        # 3 are mezzanine queries (can't do much about it)
+        # 9 are mezzanine queries (can't do much about it)
         # 3 queries are our code in template
-        per_resource_query_count = 6
+        per_resource_query_count = 12
 
         # these are additional queries generated by get_my_resources_list() function
-        pre_template_query_count = 4 + number_of_resources
+        if number_of_resources > 0:
+            pre_template_query_count = 4 + number_of_resources
+        else:
+            pre_template_query_count = 0
 
         expected_query_count = base_query_count + pre_template_query_count
         expected_query_count += per_resource_query_count * number_of_resources
diff --git a/hs_core/testing.py b/hs_core/testing.py
index a8527e6abf..79a1966048 100644
--- a/hs_core/testing.py
+++ b/hs_core/testing.py
@@ -647,6 +647,6 @@ def set_request_message_attributes(request):
     def add_session_to_request(request):
         """Use SessionMiddleware to add a session to the request."""
         """Annotate a request object with a session"""
-        middleware = SessionMiddleware()
+        middleware = SessionMiddleware(request)
         middleware.process_request(request)
         request.session.save()
diff --git a/hs_core/tests/api/native/test_robots.py b/hs_core/tests/api/native/test_robots.py
index d7f2c31eb9..0a479f3a06 100644
--- a/hs_core/tests/api/native/test_robots.py
+++ b/hs_core/tests/api/native/test_robots.py
@@ -1,5 +1,6 @@
 
 from unittest import TestCase
+
 from hs_core.robots import RobotFilter
 
 
@@ -38,9 +39,9 @@ def setUp(self):
         ]
 
     def test_is_human(self):
-        self.robot = RobotFilter()
         request = MockRequest()
+        self.robot = RobotFilter(request)
         for agent, is_human in self.agents:
-            request.META['HTTP_USER_AGENT'] = agent
+            request.headers = {'user-agent': agent}
             self.robot.process_request(request)
             self.assertTrue(request.is_human == is_human)
diff --git a/hs_core/tests/api/views/test_add_delete_resource_files.py b/hs_core/tests/api/views/test_add_delete_resource_files.py
index eff55cb7ed..804042c8f8 100644
--- a/hs_core/tests/api/views/test_add_delete_resource_files.py
+++ b/hs_core/tests/api/views/test_add_delete_resource_files.py
@@ -3,13 +3,13 @@
 
 from django.contrib.auth.models import Group
 from django.urls import reverse
-
 from rest_framework import status
 
 from hs_core import hydroshare
 from hs_core.models import ResourceFile
-from hs_core.views import add_files_to_resource, delete_file, delete_multiple_files
 from hs_core.testing import MockIRODSTestCaseMixin, ViewTestCase
+from hs_core.views import (add_files_to_resource, delete_file,
+                           delete_multiple_files)
 
 
 class TestAddDeleteResourceFiles(MockIRODSTestCaseMixin, ViewTestCase):
@@ -128,7 +128,7 @@ def test_delete_file(self):
         self.add_session_to_request(request)
         response = delete_file(request, shortkey=self.gen_res.short_id, f=res_file.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response["Location"], request.headers["referer"])
         self.assertEqual(self.gen_res.files.count(), 0)
 
         hydroshare.delete_resource(self.gen_res.short_id)
@@ -166,7 +166,7 @@ def test_delete_multiple_files(self):
         self.add_session_to_request(request)
         response = delete_multiple_files(request, shortkey=self.gen_res.short_id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response["Location"], request.headers["referer"])
         self.assertEqual(self.gen_res.files.count(), 0)
 
         hydroshare.delete_resource(self.gen_res.short_id)
diff --git a/hs_core/tests/api/views/test_group.py b/hs_core/tests/api/views/test_group.py
index 68cde6652a..ead9037827 100644
--- a/hs_core/tests/api/views/test_group.py
+++ b/hs_core/tests/api/views/test_group.py
@@ -1,22 +1,23 @@
+import json
 import os
 import shutil
-import json
-from mock import patch
 
-from django.test import Client
-from django.urls import reverse
 from django.contrib.auth.models import Group
 from django.db import transaction
+from django.test import Client
+from django.urls import reverse
 from django.utils.http import int_to_base36
-
+from mock import patch
 from rest_framework import status
 
+from hs_access_control.models import PrivilegeCodes
 from hs_core import hydroshare
-from hs_core.views import create_user_group, update_user_group, share_group_with_user, unshare_group_with_user, \
-    make_group_membership_request, act_on_group_membership_request, share_resource_with_group, \
-    unshare_resource_with_group, delete_user_group, restore_user_group
 from hs_core.testing import MockIRODSTestCaseMixin, ViewTestCase
-from hs_access_control.models import PrivilegeCodes
+from hs_core.views import (act_on_group_membership_request, create_user_group,
+                           delete_user_group, make_group_membership_request,
+                           restore_user_group, share_group_with_user,
+                           share_resource_with_group, unshare_group_with_user,
+                           unshare_resource_with_group, update_user_group)
 from theme.backends import without_login_date_token_generator
 
 
@@ -124,7 +125,7 @@ def test_group_create_failures(self):
         request.META['HTTP_REFERER'] = "/some_url/"
         response = create_user_group(request)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         new_group = Group.objects.filter(gaccess__description='This is a cool group').first()
         self.assertEqual(new_group, None)
         # at this point there should be only one group
@@ -140,7 +141,7 @@ def test_group_create_failures(self):
         request.META['HTTP_REFERER'] = "/some_url/"
         response = create_user_group(request)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         new_group = Group.objects.filter(name='Test Group').first()
         self.assertEqual(new_group, None)
         # at this point there should be only one group
@@ -156,7 +157,7 @@ def test_group_create_failures(self):
         request.META['HTTP_REFERER'] = "/some_url/"
         response = create_user_group(request)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         new_group = Group.objects.filter(name='Test Group').first()
         self.assertEqual(new_group, None)
         # at this point there should be only one group
@@ -172,7 +173,7 @@ def test_group_create_failures(self):
         request.META['HTTP_REFERER'] = "/some_url/"
         response = create_user_group(request)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         new_group = Group.objects.filter(name='Test Group').first()
         self.assertEqual(new_group, None)
         # at this point there should be only one group
@@ -201,7 +202,7 @@ def test_group_create_failures(self):
         with transaction.atomic():
             response = create_user_group(request)
             self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-            self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+            self.assertEqual(response['Location'], request.headers['referer'])
             # at this point there should be still 2 groups
         self.assertEqual(Group.objects.count(), 2)
 
@@ -250,7 +251,7 @@ def test_update_group(self):
         self.assertEqual(new_group.gaccess.active, True)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # update group to remove purpose
         grp_data = {'name': 'Test Group-2', 'description': 'This is a cool group-2',
@@ -270,7 +271,7 @@ def test_update_group(self):
         self.assertEqual(new_group.gaccess.active, True)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # update privacy_level (set to private)- this set public to false and discoverable to false
         grp_data = {'name': 'Test Group-2', 'description': 'This is a cool group-2',
@@ -290,7 +291,7 @@ def test_update_group(self):
         self.assertEqual(new_group.gaccess.active, True)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # update privacy_level (set to public) - this set public to true and discoverable to true
         grp_data = {'name': 'Test Group-2', 'description': 'This is a cool group-2',
@@ -310,7 +311,7 @@ def test_update_group(self):
         self.assertEqual(new_group.gaccess.active, True)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # update privacy_level (set to discoverable) - this should set discoverable to
         # true and public to false
@@ -331,7 +332,7 @@ def test_update_group(self):
         self.assertEqual(new_group.gaccess.active, True)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
     def test_delete_restore_group(self):
         # test a group can be deleted or restored
@@ -360,7 +361,7 @@ def test_delete_restore_group(self):
         self.assertEqual(new_group.gaccess.active, False)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # test undeleting the group
         url = reverse('restore_user_group', kwargs=post_data)
@@ -374,7 +375,7 @@ def test_delete_restore_group(self):
         self.assertEqual(new_group.gaccess.active, True)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
     def test_group_update_failure(self):
         # test that post data for 'name', 'description', and 'privacy_level' are required
@@ -406,7 +407,7 @@ def test_group_update_failure(self):
         self.assertNotEqual(new_group.gaccess.description, 'This is a cool group-2')
         self.assertEqual(new_group.gaccess.description, 'This is a cool group-1')
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # test description is required -> update should fail
         grp_data = {'name': 'Test Group-2', 'purpose': 'This group has purpose',
@@ -435,7 +436,7 @@ def test_group_update_failure(self):
         updated_group = Group.objects.filter(name='Hydroshare Author').first()
         self.assertNotEqual(updated_group.id, new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
     def test_share_group_with_user(self):
         # create a group to share
@@ -756,7 +757,7 @@ def _update_failure(self, group, request):
         self.assertNotEqual(original_group, None)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
     def _share_resource_with_group(self, group, privilege):
         url_params = {'shortkey': self.resource.short_id, 'privilege': privilege, 'group_id': group.id}
@@ -779,7 +780,7 @@ def _owner_act_on_request(self, membership_request, action):
                                                    action=action)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
     def _user_act_on_invitation(self, membership_request, action):
         url_params = {'membership_request_id': membership_request.id, 'action': action}
@@ -792,7 +793,7 @@ def _user_act_on_invitation(self, membership_request, action):
                                                    action=action)
 
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
     def _generate_user_request_to_join_group(self, group):
         url_params = {'group_id': group.id}
@@ -837,7 +838,7 @@ def _share_group_with_user(self, group, privilege):
         request.user = self.john
         response = share_group_with_user(request, group_id=group.id, user_id=self.mike.id, privilege=privilege)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
 
         # check mike is a member of the group
         self.assertIn(self.mike, group.gaccess.members)
diff --git a/hs_core/tests/api/views/test_group_CRUD.py b/hs_core/tests/api/views/test_group_CRUD.py
index 861e322bc5..b549900cc7 100644
--- a/hs_core/tests/api/views/test_group_CRUD.py
+++ b/hs_core/tests/api/views/test_group_CRUD.py
@@ -4,13 +4,12 @@
 from django.contrib.auth.models import Group
 from django.contrib.messages import get_messages
 from django.urls import reverse
-
 from rest_framework import status
 
-from hs_core.testing import ViewTestCase
 from hs_core import hydroshare
-from hs_core.views import create_user_group, update_user_group, delete_user_group, \
-    restore_user_group
+from hs_core.testing import ViewTestCase
+from hs_core.views import (create_user_group, delete_user_group,
+                           restore_user_group, update_user_group)
 
 
 class TestGroupCRUD(ViewTestCase):
@@ -62,7 +61,7 @@ def test_create_group(self):
         self.add_session_to_request(request)
         response = create_user_group(request)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'success']
         self.assertNotEqual(len(success_messages), 0)
@@ -120,7 +119,7 @@ def test_update_group(self):
         self.add_session_to_request(request)
         response = update_user_group(request, group_id=new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'success']
         self.assertNotEqual(len(success_messages), 0)
@@ -153,7 +152,7 @@ def test_update_group_failure(self):
         self.add_session_to_request(request)
         response = update_user_group(request, group_id=new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         err_messages = [m for m in flag_messages if m.tags == 'error']
         self.assertNotEqual(len(err_messages), 0)
@@ -186,7 +185,7 @@ def test_delete_group(self):
         self.add_session_to_request(request)
         response = delete_user_group(request, group_id=new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'success']
         self.assertNotEqual(len(success_messages), 0)
@@ -220,7 +219,7 @@ def test_delete_group_failure(self):
         self.add_session_to_request(request)
         response = delete_user_group(request, group_id=new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         err_messages = [m for m in flag_messages if m.tags == 'error']
         self.assertNotEqual(len(err_messages), 0)
@@ -257,7 +256,7 @@ def test_restore_group(self):
         self.add_session_to_request(request)
         response = restore_user_group(request, group_id=new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'success']
         self.assertNotEqual(len(success_messages), 0)
@@ -295,7 +294,7 @@ def test_restore_group_failure(self):
         self.add_session_to_request(request)
         response = restore_user_group(request, group_id=new_group.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         err_messages = [m for m in flag_messages if m.tags == 'error']
         self.assertNotEqual(len(err_messages), 0)
diff --git a/hs_core/tests/api/views/test_group_share.py b/hs_core/tests/api/views/test_group_share.py
index e6e45d07ff..292e2ff0fa 100644
--- a/hs_core/tests/api/views/test_group_share.py
+++ b/hs_core/tests/api/views/test_group_share.py
@@ -5,14 +5,14 @@
 from django.contrib.auth.models import Group
 from django.contrib.messages import get_messages
 from django.urls import reverse
-
 from rest_framework import status
 
-from hs_core.testing import ViewTestCase
-from hs_core import hydroshare
-from hs_core.views import share_group_with_user, unshare_group_with_user, \
-    make_group_membership_request, act_on_group_membership_request
 from hs_access_control.models import PrivilegeCodes
+from hs_core import hydroshare
+from hs_core.testing import ViewTestCase
+from hs_core.views import (act_on_group_membership_request,
+                           make_group_membership_request,
+                           share_group_with_user, unshare_group_with_user)
 
 
 class TestShareGroup(ViewTestCase):
@@ -106,7 +106,7 @@ def test_share_group_failure(self):
         response = share_group_with_user(request, group_id=self.test_group.id,
                                          privilege=privilege, user_id=self.mike.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'error']
         self.assertNotEqual(len(success_messages), 0)
@@ -137,7 +137,7 @@ def test_unshare_group(self):
         response = unshare_group_with_user(request, group_id=self.test_group.id,
                                            user_id=self.mike.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'success']
         self.assertNotEqual(len(success_messages), 0)
@@ -168,7 +168,7 @@ def test_unshare_group_failure(self):
         response = unshare_group_with_user(request, group_id=self.test_group.id,
                                            user_id=self.john.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         err_messages = [m for m in flag_messages if m.tags == 'error']
         self.assertNotEqual(len(err_messages), 0)
@@ -376,7 +376,7 @@ def _check_share_with_user(self, privilege):
         response = share_group_with_user(request, group_id=self.test_group.id,
                                          privilege=privilege, user_id=self.mike.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         success_messages = [m for m in flag_messages if m.tags == 'success']
         self.assertNotEqual(len(success_messages), 0)
@@ -427,7 +427,7 @@ def _check_act_on_membership_request(self, acting_user, membership_request, acti
                                                    membership_request_id=membership_request.id,
                                                    action=action)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         flag_messages = get_messages(request)
         tag = 'success' if success else 'error'
         session_messages = [m for m in flag_messages if m.tags == tag]
diff --git a/hs_core/tests/api/views/test_metadata_CRUD.py b/hs_core/tests/api/views/test_metadata_CRUD.py
index 447ac30413..f0e494533d 100644
--- a/hs_core/tests/api/views/test_metadata_CRUD.py
+++ b/hs_core/tests/api/views/test_metadata_CRUD.py
@@ -1,15 +1,15 @@
+import json
 import os
 import shutil
-import json
 
 from django.contrib.auth.models import Group
 from django.urls import reverse
-
 from rest_framework import status
 
 from hs_core import hydroshare
-from hs_core.views import add_metadata_element, update_metadata_element, delete_metadata_element
 from hs_core.testing import MockIRODSTestCaseMixin, ViewTestCase
+from hs_core.views import (add_metadata_element, delete_metadata_element,
+                           update_metadata_element)
 
 
 class TestCRUDMetadata(MockIRODSTestCaseMixin, ViewTestCase):
@@ -124,7 +124,7 @@ def test_CRUD_metadata(self):
         response = delete_metadata_element(request, shortkey=self.gen_res.short_id,
                                            element_name='contributor', element_id=contributor.id)
         self.assertEqual(response.status_code, status.HTTP_302_FOUND)
-        self.assertEqual(response['Location'], request.META['HTTP_REFERER'])
+        self.assertEqual(response['Location'], request.headers['referer'])
         self.gen_res.refresh_from_db()
         # there should be no contributors
         self.assertEqual(self.gen_res.metadata.contributors.count(), 0)
diff --git a/hs_file_types/tests/test_view_functions.py b/hs_file_types/tests/test_view_functions.py
index 43f6aa192c..79cbf18acd 100644
--- a/hs_file_types/tests/test_view_functions.py
+++ b/hs_file_types/tests/test_view_functions.py
@@ -1867,7 +1867,7 @@ def test_fileset_meta_xml_file_download(self):
     def add_session_to_request(request):
         """Use SessionMiddleware to add a session to the request."""
         """Annotate a request object with a session"""
-        middleware = SessionMiddleware()
+        middleware = SessionMiddleware(request)
         middleware.process_request(request)
         request.session.save()
 
diff --git a/hs_tracking/tests/test_dashboard.py b/hs_tracking/tests/test_dashboard.py
index 37a2d1cdc9..cd1415a250 100644
--- a/hs_tracking/tests/test_dashboard.py
+++ b/hs_tracking/tests/test_dashboard.py
@@ -1,10 +1,11 @@
-from django.test import TestCase
+import socket
+
 from django.contrib.auth.models import Group
-from hs_tracking.models import Variable
-from hs_core import hydroshare
+from django.test import Client, TestCase
 from rest_framework import status
-import socket
-from django.test import Client
+
+from hs_core import hydroshare
+from hs_tracking.models import Variable
 
 
 class TestDashboard(TestCase):
@@ -12,7 +13,7 @@ class TestDashboard(TestCase):
     def setUp(self):
         self.hostname = socket.gethostname()
         self.resource_url = "/resource/{res_id}/"
-        self.client = Client(HTTP_USER_AGENT='Mozilla/5.0')  # fake use of a real browser
+        self.client = Client(headers={"user-agent": 'Mozilla/5.0'})  # fake use of a real browser
 
         self.group, _ = Group.objects.get_or_create(name='Hydroshare Author')
 
diff --git a/hs_tracking/tests/test_tracking.py b/hs_tracking/tests/test_tracking.py
index ac20e649e5..3c7dc0000c 100644
--- a/hs_tracking/tests/test_tracking.py
+++ b/hs_tracking/tests/test_tracking.py
@@ -1,22 +1,22 @@
-from datetime import datetime, timedelta
 import csv
+import urllib.error
+import urllib.parse
+import urllib.request
+from datetime import datetime, timedelta
 from io import StringIO
+from pprint import pprint
 
-from django.test import TestCase
-from django.contrib.auth.models import User, Group
-from django.test import Client
+from django.contrib.auth.models import Group, User
 from django.http import HttpRequest, QueryDict, response
-from mock import patch, Mock
+from django.test import Client, TestCase
+from mock import Mock, patch
 
-from hs_tracking.models import Variable, Session, Visitor, SESSION_TIMEOUT, VISITOR_FIELDS
-from hs_core import hydroshare
-from hs_tracking.views import AppLaunch
 import hs_tracking.utils as utils
+from hs_core import hydroshare
 from hs_tools_resource.models import RequestUrlBase
-import urllib.request
-import urllib.parse
-import urllib.error
-from pprint import pprint
+from hs_tracking.models import (SESSION_TIMEOUT, VISITOR_FIELDS, Session,
+                                Variable, Visitor)
+from hs_tracking.views import AppLaunch
 
 
 class ViewTests(TestCase):
@@ -51,6 +51,7 @@ def setUp(self):
 
     def createRequest(self, user=None):
         self.request = Mock()
+        self.request.headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '}
         if user is not None:
             self.request.user = user
 
@@ -176,6 +177,7 @@ def setUp(self):
 
     def createRequest(self, user=None):
         request = Mock()
+        request.headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '}
         if user is not None:
             request.user = user