Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8e652d2
rebase - serializers
claire-peters Aug 29, 2024
96cdbba
add created_before/created_after filtering to allocation and project …
claire-peters Jun 28, 2024
243bf03
update api config
claire-peters Jul 2, 2024
aa213da
add custom admin renderer
claire-peters Jul 1, 2024
5178008
remove basicauthentication, add knox dependencies
claire-peters Jul 2, 2024
81217f0
change allocationchangerequest api response content
claire-peters Jul 2, 2024
a13f376
remove knox for now
claire-peters Sep 4, 2024
094a343
update allocation request api view
claire-peters Sep 11, 2024
9ef1d09
update allocation change request values
claire-peters Sep 11, 2024
b797d08
update documentation
claire-peters Sep 12, 2024
b6b84f7
add csv export button
claire-peters Sep 13, 2024
a83aefd
Merge pull request #338 from fasrc/cp_api_auth
claire-peters Sep 13, 2024
5f819ea
update filters
claire-peters Sep 13, 2024
616d0ce
use filter class for ordering
claire-peters Sep 13, 2024
37d4ff3
replace action imports with signals
claire-peters May 21, 2024
209acc0
use signals for info grab in project views
claire-peters May 21, 2024
3ecbb68
fix signal name
claire-peters May 21, 2024
ada5fba
add holylfs06
claire-peters Sep 4, 2024
cb3a391
tier 2 gets billed
claire-peters Sep 4, 2024
e87ffa6
fix variable name
claire-peters Sep 4, 2024
8103e2b
update project_filter_users_to_remove signal processing
claire-peters Sep 4, 2024
873125c
work on testing
claire-peters Sep 9, 2024
f648182
fix addusers test
claire-peters Sep 14, 2024
0e6c6c0
add removeuser signal test
claire-peters Sep 17, 2024
8350376
remove test
claire-peters Sep 17, 2024
6bbbe6b
update test
claire-peters Sep 18, 2024
d683899
update logging
claire-peters Sep 18, 2024
faa494f
Merge pull request #304 from fasrc/cp_usesignals
claire-peters Sep 18, 2024
ef6ac0e
Merge pull request #339 from fasrc/ajk_tidyup
aaronk Sep 23, 2024
3bc65a5
Update ifxbilling / fiine.client; improves synchronization (including…
aaronk Sep 23, 2024
f140107
Merge pull request #341 from fasrc/ajk_ifxbilling_update
aaronk Sep 23, 2024
921785d
Merge branch 'master' into development
aaronk Oct 10, 2024
a0ab227
give department approvers user permissions for child projects
claire-peters Oct 16, 2024
9059cdd
fix circular import
claire-peters Oct 16, 2024
e61f48b
fix circular import
claire-peters Oct 16, 2024
63968d9
Merge branch 'master' into development
aaronk Nov 4, 2024
5b7d1d4
Merge branch 'master' into development
aaronk Nov 4, 2024
c459147
Merge branch 'master' into development
aaronk Nov 26, 2024
fa3ccc9
Merge branch 'master' into development
aaronk Dec 6, 2024
f83f3c9
update project user removal query option signal
claire-peters Dec 10, 2024
0a3d63f
Merge pull request #346 from fasrc/cp_deptmanagerpermissions
claire-peters Dec 10, 2024
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
14 changes: 0 additions & 14 deletions coldfront/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
'django_tables2',
'table',
'rest_framework_datatables',
'rest_framework',
'easy_pdf',
]

Expand Down Expand Up @@ -148,19 +147,6 @@
},
]

REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework_datatables.renderers.DatatablesRenderer',
),
'DEFAULT_FILTER_BACKENDS': (
'rest_framework_datatables.filters.DatatablesFilterBackend',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework_datatables.pagination.DatatablesPageNumberPagination',
'PAGE_SIZE': 500,
}

# Add local site templates files if set
SITE_TEMPLATES = ENV.str('SITE_TEMPLATES', default='')
if len(SITE_TEMPLATES) > 0:
Expand Down
8 changes: 4 additions & 4 deletions coldfront/config/plugins/api.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from coldfront.config.base import INSTALLED_APPS

INSTALLED_APPS += [
'django_filters',
'coldfront.plugins.api'
]
'django_filters',
'rest_framework',
'coldfront.plugins.api',
]

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
Expand Down
12 changes: 12 additions & 0 deletions coldfront/core/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from model_utils.models import TimeStampedModel
from simple_history.models import HistoricalRecords

from ifxuser.models import Organization
from coldfront.core.field_of_science.models import FieldOfScience
from coldfront.core.utils.common import import_from_settings

Expand Down Expand Up @@ -214,6 +215,17 @@ def user_permissions(self, user):

if self.pi.id == user.id:
permissions.append(ProjectPermission.PI)

# if the user is an approver in a department connected to the project,
# give them user permissions
departments = Organization.objects.filter(
org_tree='Research Computing Storage Billing'
)
proj_departments = [d for d in departments if self in d.get_projects()]
for department in proj_departments:
if user in department.useraffiliation_set.filter(role='approver'):
permissions.append(ProjectPermission.USER)

return permissions

def has_perm(self, user, perm):
Expand Down
15 changes: 10 additions & 5 deletions coldfront/core/project/signals.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import django.dispatch


project_create = django.dispatch.Signal()
#providing_args=["project_title"]
project_post_create = django.dispatch.Signal()
#providing_args=["project_obj"]

project_make_projectuser = django.dispatch.Signal()
#providing_args=["user_name", "group_name"]

project_preremove_projectuser = django.dispatch.Signal()
#providing_args=["user_name", "group_name"]

project_activate_user = django.dispatch.Signal()
#providing_args=["project_user_pk"]
project_remove_user = django.dispatch.Signal()
#providing_args=["project_user_pk"]
project_filter_users_to_remove = django.dispatch.Signal()
#providing_args=["project_user_list"]
# return tuple of (no_removal, can_remove)
56 changes: 52 additions & 4 deletions coldfront/core/project/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging

from django.test import TestCase, tag
from django.test import TestCase, tag, override_settings
from django.urls import reverse
from unittest.mock import patch

from coldfront.core.test_helpers import utils
from coldfront.core.test_helpers.factories import (
Expand All @@ -12,7 +13,11 @@
ProjectStatusChoiceFactory,
ProjectAttributeTypeFactory,
)
from coldfront.core.project.models import Project, ProjectUserStatusChoice
from coldfront.core.project.models import (
Project, ProjectUser,
ProjectUserRoleChoice,
ProjectUserStatusChoice
)

logging.disable(logging.CRITICAL)

Expand Down Expand Up @@ -161,7 +166,6 @@ def test_project_attribute_create_post_required_values(self):

def test_project_attribute_create_value_type_match(self):
"""ProjectAttributeCreate correctly flags value-type mismatch"""

self.client.force_login(self.admin_user,
backend='django.contrib.auth.backends.ModelBackend')
# test that value must be numeric if proj_attr_type is string
Expand Down Expand Up @@ -351,7 +355,7 @@ def test_projectnotecreateview_access(self):
self.project_access_tstbase(self.url)


class ProjectAddUsersSearchView(ProjectViewTestBase):
class ProjectAddUsersSearchViewTest(ProjectViewTestBase):
"""Tests for ProjectAddUsersSearchView"""
def setUp(self):
"""set up users and project for testing"""
Expand All @@ -365,6 +369,50 @@ def test_projectadduserssearchview_access(self):
utils.test_user_cannot_access(self, self.proj_datamanager, self.url)# data manager cannot access
utils.test_user_cannot_access(self, self.proj_allocation_user, self.url)# user cannot access

class ProjectAddUsersViewTest(ProjectViewTestBase):
"""Tests for ProjectAddUsersView"""
def setUp(self):
"""set up users and project for testing"""
self.url = reverse('project-add-users', kwargs={'pk': self.project.pk})
self.form_data = {
'q': self.nonproj_allocation_user.username,
'search_by': 'username_only',
'userform-TOTAL_FORMS': '1',
'userform-INITIAL_FORMS': '0',
'userform-MIN_NUM_FORMS': '0',
'userform-MAX_NUM_FORMS': '1',
'userform-0-selected': 'on',
'userform-0-role': ProjectUserRoleChoice.objects.get(name='User').pk,
'allocationform-allocation': [self.proj_allocation.pk]
}

@override_settings(PLUGIN_LDAP=True)
def test_projectaddusers_ldapsignalfail_messages(self):
"""Test the messages displayed when the add user signal fails"""
self.client.force_login(self.pi_user)

@patch('coldfront.core.project.signals.project_make_projectuser.send')
def test_projectaddusers_form_validation(self, mock_signal):
"""Test that the formset and allocation form are validated correctly"""
self.client.force_login(self.proj_accessmanager)
mock_signal.return_value = None
# Prepare form data for adding a user
response = self.client.post(self.url, data=self.form_data)
self.assertEqual(response.url, reverse('project-detail', kwargs={'pk': self.project.pk}))
self.assertEqual(response.status_code, 302)
# Check that user was added
self.assertTrue(ProjectUser.objects.filter(project=self.project, user=self.nonproj_allocation_user).exists())

@patch('coldfront.core.project.signals.project_make_projectuser.send')
def test_projectaddusers_signal_fail(self, mock_signal):
"""Test that the add users form fails when the signal sent to LDAP fails"""
self.client.force_login(self.proj_accessmanager)
mock_signal.side_effect = Exception("LDAP error occurred")
# Prepare form data for adding a user
response = self.client.post(self.url, data=self.form_data, follow=True)
self.assertContains(response, 'LDAP error occurred')
self.assertContains(response, 'Added 0 users')


class ProjectUserDetailViewTest(ProjectViewTestBase):
"""Tests for ProjectUserDetailView"""
Expand Down
132 changes: 60 additions & 72 deletions coldfront/core/project/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@
allocation_remove_user,
allocation_activate_user,
)
from coldfront.core.project.signals import project_create, project_post_create
from coldfront.core.project.signals import (
project_filter_users_to_remove,
project_preremove_projectuser,
project_make_projectuser,
project_create,
project_post_create
)
from coldfront.core.grant.models import Grant
from coldfront.core.project.forms import (
ProjectReviewForm,
Expand Down Expand Up @@ -68,9 +74,6 @@
if 'django_q' in settings.INSTALLED_APPS:
from django_q.tasks import Task

if 'coldfront.plugins.ldap' in settings.INSTALLED_APPS:
from coldfront.plugins.ldap.utils import LDAPConn

ALLOCATION_ENABLE_ALLOCATION_RENEWAL = import_from_settings(
'ALLOCATION_ENABLE_ALLOCATION_RENEWAL', True)
ALLOCATION_DEFAULT_ALLOCATION_LENGTH = import_from_settings(
Expand Down Expand Up @@ -609,10 +612,7 @@ def post(self, request, *args, **kwargs):
allocation_form = ProjectAddUsersToAllocationForm(
request.user, project_obj.pk, request.POST, prefix='allocationform'
)

added_users_count = 0
if 'coldfront.plugins.ldap' in settings.INSTALLED_APPS:
ldap_conn = LDAPConn()

if formset.is_valid() and allocation_form.is_valid():
projuserstatus_active = ProjectUserStatusChoice.objects.get(name='Active')
Expand Down Expand Up @@ -649,28 +649,19 @@ def post(self, request, *args, **kwargs):

role_choice = user_form_data.get('role')

if 'coldfront.plugins.ldap' in settings.INSTALLED_APPS:
try:
ldap_conn.add_user_to_group(
user_obj.username, project_obj.title,
)
logger.info(
"P678: Coldfront user %s added AD User for %s to AD Group %s",
self.request.user,
user_obj.username,
project_obj.title,
)
except Exception as e:
error = f"Could not add user {user_obj} to AD Group for {project_obj.title}: {e}\nPlease contact Coldfront administration for further assistance."
logger.error(
"P685: user %s could not add AD user of %s to AD Group of %s: %s",
self.request.user, user_obj, project_obj.title, e
)
errors.append(error)
continue
success_msg = f"User {user_obj} added by {request.user} to AD Group for {project_obj.title}"
logger.info(success_msg)
successes.append(success_msg)
try:
project_make_projectuser.send(
sender=self.__class__,
user_name=user_obj.username, group_name=project_obj.title
)
except Exception as e:
error = f"Could not add user {user_obj} to AD Group for {project_obj.title}: {e}\nPlease contact Coldfront administration for further assistance."
logger.exception('P646: %s', e)
errors.append(error)
continue
success_msg = f"User {user_obj} added by {request.user} to AD Group for {project_obj.title}"
logger.info(success_msg)
successes.append(success_msg)

# Is the user already in the project?
project_obj.projectuser_set.update_or_create(
Expand All @@ -680,7 +671,6 @@ def post(self, request, *args, **kwargs):
'status': projuserstatus_active,
}
)

added_users_count += 1
for allocation in Allocation.objects.filter(
pk__in=allocation_form_data
Expand Down Expand Up @@ -757,18 +747,18 @@ def get_users_to_remove(self, project_obj):
def get(self, request, *args, **kwargs):
pk = self.kwargs.get('pk')
project_obj = get_object_or_404(Project, pk=pk)
users_to_remove = self.get_users_to_remove(project_obj)
users_no_removal = None
users_list = self.get_users_to_remove(project_obj)

# if ldap is activated, prevent selection of users with project corresponding to primary group
if 'coldfront.plugins.ldap' in settings.INSTALLED_APPS:
signal_response = project_filter_users_to_remove.send(
sender=self.__class__, users_to_remove=users_list, project=project_obj
)
if signal_response:
users_to_remove = signal_response[0][1]
else:
users_to_remove = users_list

usernames = [u['username'] for u in users_to_remove]
ldap_conn = LDAPConn()
users_main_group = ldap_conn.users_in_primary_group(
usernames, project_obj.title)
ingroup = lambda u: u['username'] in users_main_group
users_no_removal, users_to_remove = sort_by(users_to_remove, ingroup, how="condition")
users_no_removal = [u for u in users_list if u not in users_to_remove]

context = {}

Expand All @@ -789,20 +779,19 @@ def post(self, request, *args, **kwargs):

users_to_remove = self.get_users_to_remove(project_obj)
# if ldap is activated, prevent selection of users with project corresponding to primary group
if 'coldfront.plugins.ldap' in settings.INSTALLED_APPS:

usernames = [u['username'] for u in users_to_remove]
ldap_conn = LDAPConn()
users_main_group = ldap_conn.users_in_primary_group(
usernames, project_obj.title)
ingroup = lambda u: u['username'] in users_main_group
users_no_removal, users_to_remove = sort_by(users_to_remove, ingroup, how="condition")

signal_response = project_filter_users_to_remove.send(
sender=self.__class__, users_to_remove=users_to_remove, project=project_obj
)
if signal_response:
users_to_remove = signal_response[0][1]
else:
users_to_remove = users_to_remove

formset = formset_factory(ProjectRemoveUserForm, max_num=len(users_to_remove))
formset = formset(request.POST, initial=users_to_remove, prefix='userform')

remove_users_count = 0
failed_user_removals = []

if formset.is_valid():
projectuser_status_removed = ProjectUserStatusChoice.objects.get(
Expand All @@ -822,30 +811,27 @@ def post(self, request, *args, **kwargs):

project_user_obj = project_obj.projectuser_set.get(user=user_obj)

if 'coldfront.plugins.ldap' in settings.INSTALLED_APPS:
try:
ldap_conn.remove_member_from_group(
user_obj.username, project_obj.title,
)
logger.info(
"P835: Coldfront user %s removed AD User for %s from AD Group for %s",
self.request.user,
user_obj.username,
project_obj.title,
)
except Exception as e:
messages.error(
request,
f"could not remove user {user_obj}: {e}"
)
logger.error(
"P846: Coldfront user %s could NOT remove AD User for %s from AD Group for %s: %s",
self.request.user,
user_obj.username,
project_obj.title,
e
)
continue
try:
project_preremove_projectuser.send(
sender=self.__class__,
user_name=user_obj.username, group_name=project_obj.title
)
logger.info(
"P815: Coldfront user %s removed AD User for %s from AD Group for %s",
self.request.user,
user_obj.username,
project_obj.title,
)
except Exception as e:
failed_user_removals += [f"could not remove user {user_obj}: {e}"]
logger.exception(
"P815: Coldfront user %s could NOT remove AD User for %s from AD Group for %s: %s",
self.request.user,
user_obj.username,
project_obj.title,
e
)
continue

project_user_obj.status = projectuser_status_removed
project_user_obj.save()
Expand All @@ -866,6 +852,8 @@ def post(self, request, *args, **kwargs):
)
remove_users_count += 1
user_pl = 'user' if remove_users_count == 1 else 'users'
for fail in failed_user_removals:
messages.error(request, fail)
messages.success(
request, f'Removed {remove_users_count} {user_pl} from project.'
)
Expand Down
Loading
Loading