Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3e46dcf
add warning to project_remove_users template re: primary user removal
claire-peters Oct 7, 2025
19a99fd
make primary group users selectable by admins
claire-peters Oct 7, 2025
031fcc2
add Deactivated ProjectUserStatusChoice to add_default_project_choice…
claire-peters Dec 10, 2025
abd074d
change ldap membership update to only assign "Removed" status to user…
claire-peters Dec 11, 2025
6cb493b
move ldap signals to separate file, add methods for user deactivation…
claire-peters Dec 12, 2025
2a0f9ac
add import_from_settings to ldap signals.py
claire-peters Dec 17, 2025
2d922ff
reconfigure user removal form creation and templating
claire-peters Dec 18, 2025
608c8d9
add AD user deactivation on signal post
claire-peters Dec 18, 2025
594247f
remove newly disabled users from secondary projects and their allocat…
claire-peters Dec 18, 2025
63f54af
add projectreactivateuserform
claire-peters Dec 18, 2025
c1de83e
improve projectadduserssearchview
claire-peters Dec 22, 2025
385c264
add user reactivation routine
claire-peters Dec 23, 2025
26377d4
change deactivated projectuser allocation reactivation
claire-peters Dec 23, 2025
5a2b314
add reactivate_user ldap signal
claire-peters Dec 23, 2025
b84c61a
add kwarg handling to signals call
claire-peters Dec 23, 2025
1148191
user reactivation view/form/signal fixes
claire-peters Dec 23, 2025
e4d4525
add error raising to user deactivation and reactivation
claire-peters Dec 23, 2025
cc3b4ae
improve project reactivation logging
claire-peters Jan 5, 2026
58102fc
fix formset bug
claire-peters Jan 6, 2026
1e37afa
enable simultaneous addition and reactivation
claire-peters Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions coldfront/core/project/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ class ProjectAddUserForm(forms.Form):
selected = forms.BooleanField(initial=False, required=False)


class ProjectReactivateUserForm(forms.Form):
username = forms.CharField(max_length=150, disabled=True)
first_name = forms.CharField(max_length=150, required=False, disabled=True)
last_name = forms.CharField(max_length=150, required=False, disabled=True)
email = forms.EmailField(max_length=100, required=False, disabled=True)
role = forms.ModelChoiceField(
queryset=ProjectUserRoleChoice.objects.all(), required=False, empty_label=None)
selected = forms.BooleanField(initial=False, required=False)


class ProjectAddUsersToAllocationForm(forms.Form):
allocation = forms.MultipleChoiceField(
widget=forms.CheckboxSelectMultiple(attrs={'checked': 'checked'}), required=False)
Expand All @@ -55,9 +65,17 @@ def __init__(self, request_user, project_pk, *args, **kwargs):
project_obj = get_object_or_404(Project, pk=project_pk)

allocation_query_set = project_obj.allocation_set.filter(
resources__is_allocatable=True, is_locked=False, status__name__in=PENDING_ACTIVE_ALLOCATION_STATUSES).distinct()
allocation_choices = [(allocation.id, "%s (%s) %s" % (allocation.get_parent_resource.name, allocation.get_parent_resource.resource_type.name,
allocation.description if allocation.description else '')) for allocation in allocation_query_set]
resources__is_allocatable=True, is_locked=False,
status__name__in=PENDING_ACTIVE_ALLOCATION_STATUSES
).distinct()
allocation_choices = [(
allocation.id, "%s (%s) %s" % (
allocation.get_parent_resource.name,
allocation.get_parent_resource.resource_type.name,
allocation.description if allocation.description else ''
)
) for allocation in allocation_query_set]

allocation_choices_sorted = []
allocation_choices_sorted = sorted(allocation_choices, key=lambda x: x[1][0].lower())
allocation_choices.insert(0, ('__select_all__', 'Select All'))
Expand All @@ -74,6 +92,7 @@ class ProjectRemoveUserForm(forms.Form):
last_name = forms.CharField(max_length=150, required=False, disabled=True)
email = forms.EmailField(max_length=100, required=False, disabled=True)
role = forms.CharField(max_length=30, disabled=True)
primary_group = forms.BooleanField(required=False, disabled=True)
selected = forms.BooleanField(initial=False, required=False)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ def handle(self, *args, **options):
for choice in ['User', 'General Manager', 'Storage Manager', 'Access Manager', 'PI']:
ProjectUserRoleChoice.objects.get_or_create(name=choice)

for choice in ['Active', 'Pending - Add', 'Pending - Remove', 'Denied', 'Removed', ]:
for choice in [
'Active', 'Pending - Add', 'Pending - Remove', 'Denied', 'Removed', 'Deactivated'
]:
ProjectUserStatusChoice.objects.get_or_create(name=choice)

for attribute_type in ('Date', 'Float', 'Int', 'Text', 'Yes/No'):
Expand Down
6 changes: 6 additions & 0 deletions coldfront/core/project/signals.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import logging
import django.dispatch

log = logging.getLogger(__name__)


project_create = django.dispatch.Signal()
#providing_args=["project_title"]
project_post_create = django.dispatch.Signal()
Expand All @@ -11,6 +15,8 @@
project_preremove_projectuser = django.dispatch.Signal()
#providing_args=["user_name", "group_name"]

project_reactivate_projectuser = django.dispatch.Signal()

project_filter_users_to_remove = django.dispatch.Signal()
#providing_args=["project_user_list"]
# return tuple of (no_removal, can_remove)
45 changes: 45 additions & 0 deletions coldfront/core/project/templates/project/project_add_users.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,51 @@ <h2>Add users to project: {{project.title}}</h2>
</div>
</div>
</div>

{% if deactivated_formset %}
<br>
<p>
The following users, which had this lab's AD Group as their primary group, are currently disabled.
You can reenable them by selecting them and clicking "Reactivate Users" below.
</p>
<div class="row">
<div class="col" id="deactivated_formset">
<form action="{% url 'project-add-users' project.pk %}" method="post">
{% csrf_token %}
<table class="table table-sm table-hover">
<thead>
<tr>
<th><input type="checkbox" class="check" id="selectAll"></th>
<th scope="col">Username</th>
<th scope="col">First Name</th>
<th scope="col">Last Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
</tr>
</thead>
<tbody>
{% for form in deactivated_formset %}
<tr>
<td>{{ form.selected }}</td>
<td>{{ form.username.value }}</td>
<td>{{ form.first_name.value }}</td>
<td>{{ form.last_name.value }}</td>
<td>{{ form.email.value }}</td>
<td>{{ form.role.value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ deactivated_formset.management_form }}
<button type="submit" class="btn btn-primary">
<i class="fas fa-user-plus" aria-hidden="true"></i> Reactivate Selected Users
</button>
</form>
</div>
</div>
{% endif %}


{% endblock %}


Expand Down
74 changes: 49 additions & 25 deletions coldfront/core/project/templates/project/project_remove_users.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<h2>Remove users from project: {{project.title}}</h2>
<hr>

{% if formset or users_no_removal %}
{% if formset or primary_group_users %}
<div class="card border-light">
<div class="card-body">
<p>
Expand All @@ -17,10 +17,17 @@ <h2>Remove users from project: {{project.title}}</h2>
remove that user's data from the allocations.
</p>
<p>
To be removed from a lab, the user must not have the lab as their primary
group. If you would like to remove a user that has your lab as their primary
group, please <a href="mailto:{{ EMAIL_TICKET_SYSTEM_ADDRESS }}?subject=User removal request for Project {{ project.title }}">
contact FASRC support</a>.
{% if request.user.is_superuser %}
As a superuser, you can remove any user from the project. Please be aware that
removing a user from their primary group will cause their account to be deactivated and
prevent access to computational resources. If you are unsure, please
contact FASRC support or the ColdFront system administrator.
{% else %}
To be removed from a lab, the user must not have the lab as their primary
group. If you would like to remove a user that has your lab as their primary
group, please <a href="mailto:{{ EMAIL_TICKET_SYSTEM_ADDRESS }}?subject=User removal request for Project {{ project.title }}">
contact FASRC support</a>.
{% endif %}
</p>
<form action="{% url 'project-remove-users' project.pk %}" method="post">
{% csrf_token %}
Expand All @@ -32,6 +39,7 @@ <h2>Remove users from project: {{project.title}}</h2>
<input type="checkbox" class="check" id="selectAll">
</th>
<th scope="col">#</th>
<th scope="col">Is Primary</th>
<th scope="col">Username</th>
<th scope="col">First Name</th>
<th scope="col">Last Name</th>
Expand All @@ -40,27 +48,43 @@ <h2>Remove users from project: {{project.title}}</h2>
</tr>
</thead>
<tbody>
{% for user in users_no_removal %}
<tr>
<td></td>
<td></td>
<td style="color: gray;">{{ user.username }}</td>
<td style="color: gray;">{{ user.first_name }}</td>
<td style="color: gray;">{{ user.last_name }}</td>
<td style="color: gray;">{{ user.email }}</td>
<td style="color: gray;">{{ user.role }}</td>
</tr>
{% endfor %}
{% for form in formset %}
<tr>
<td>{{ form.selected }}</td>
<td>{{ forloop.counter }}</td>
<td>{{ form.username.value }}</td>
<td>{{ form.first_name.value }}</td>
<td>{{ form.last_name.value }}</td>
<td>{{ form.email.value }}</td>
<td>{{ form.role.value }}</td>
</tr>
{% if form.primary_group.value %}
{% if request.user.is_superuser %}
<tr class="font-weight-bold">
<td>{{ form.selected }}</td>
<td>{{ forloop.counter }}</td>
<td><i class="fas fa-check" aria-hidden="true"></i></td>
<td>{{ form.username.value }}</td>
<td>{{ form.first_name.value }}</td>
<td>{{ form.last_name.value }}</td>
<td>{{ form.email.value }}</td>
<td>{{ form.role.value }}</td>
</tr>
{% else %}
<tr>
<td></td>
<td>{{ forloop.counter }}</td>
<td><i class="fas fa-check" aria-hidden="true"></i></td>
<td style="color: gray;">{{ form.username }}</td>
<td style="color: gray;">{{ form.first_name }}</td>
<td style="color: gray;">{{ form.last_name }}</td>
<td style="color: gray;">{{ form.email }}</td>
<td style="color: gray;">{{ form.role }}</td>
</tr>
{% endif %}
{% else %}
<tr>
<td>{{ form.selected }}</td>
<td>{{ forloop.counter }}</td>
<td></td>
<td>{{ form.username.value }}</td>
<td>{{ form.first_name.value }}</td>
<td>{{ form.last_name.value }}</td>
<td>{{ form.email.value }}</td>
<td>{{ form.role.value }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
Expand Down
Loading