[#8364] remove user roles if they are emptied
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index ae55943..064108e 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -1167,6 +1167,9 @@
             return dict(error='%s (%s) is not in the group %s.' % (user.display_name, username, group.name))
         M.AuditLog.log('remove user %s from %s', username, group.name)
         user_role.roles.remove(group._id)
+        if len(user_role.roles) == 0:
+            # user has no roles in this project any more, so don't leave a useless doc around
+            user_role.delete()
         g.post_event('project_updated')
         return dict()
 
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 01b6572..3c678b3 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -829,14 +829,13 @@
 
 class ProjectRole(MappedClass):
     """
-    Per-project roles, called "Groups" in the UI.
-    This can be a proxy for a single user.  It can also inherit roles.
+    The roles that a single user holds in a project.
+    Also the named roles (called "Groups" in the UI) are in this model (and can include other named roles)
 
-    :var user_id: used if this role is for a single user
-    :var project_id:
-    :var name:
-    :var roles: a list of other :class:`ProjectRole` ``ObjectId`` values.  These roles are delegated through the
-                current role.
+    :var user_id: used if this role is for a single user.  Empty for named roles
+    :var project_id: the project id
+    :var name: named roles (like Admin, Developer, custom-names-too)
+    :var roles: a list of other :class:`ProjectRole` ``ObjectId`` values which this user/group has access to
     """
 
     class __mongometa__:
diff --git a/scripts/add_user_to_group.py b/scripts/add_user_to_group.py
index f175faa..8c9a757 100644
--- a/scripts/add_user_to_group.py
+++ b/scripts/add_user_to_group.py
@@ -94,15 +94,18 @@
 
     found_any_replace_users = False
     for replace_user in replace_users:
-        replace_user_roles = M.ProjectRole.by_user(replace_user, project=project, upsert=True).roles
-        if project_role._id not in replace_user_roles:
+        replace_user_perm = M.ProjectRole.by_user(replace_user, project=project)
+        if not replace_user_perm or project_role._id not in replace_user_perm.roles:
             log.info('Cannot replace %s they are not %s of %s', replace_user.username, options.group, project.url())
             continue
         found_any_replace_users = True
         if options.dry_run:
             log.info('Would remove %s as %s of %s', replace_user.username, options.group, project.url())
         else:
-            replace_user_roles.remove(project_role._id)
+            replace_user_perm.roles.remove(project_role._id)
+            if len(replace_user_perm.roles) == 0:
+                # user has no roles in this project any more, so don't leave a useless doc around
+                replace_user_perm.delete()
             ThreadLocalORMSession.flush_all()
     if replace_users and not found_any_replace_users:
         log.info('Not adding %s since no replace-users found on %s', user.username, project.url())