Skip to content

Commit

Permalink
Merge pull request #303 from fasrc/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
claire-peters authored Jun 25, 2024
2 parents 5501c8e + 6eaa50a commit a81c55a
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 36 deletions.
18 changes: 11 additions & 7 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ on:
branches: [ "ci_cd", "development", "master" ]
pull_request:
branches: [ "ci_cd", "development", "master" ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8]
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down Expand Up @@ -51,16 +48,23 @@ jobs:
# stop the build if there are Python syntax errors or undefined names
isort .
isort --check --diff .
- name: Create .env file
run: echo -e "NEO4JP=${{ secrets.NEO4JP }}\nSFUSER=${{ secrets.SFUSER }}\nSFPASS=${{ secrets.SFPASS }}\nPLUGIN_FASRC=True" > .env
- name: Build the images and start the containers
run: |
export GITHUB_WORKFLOW=True
export MODE="Test"
export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1
make build
docker run -d --rm --name coldfront -v `pwd`:/usr/src/app -e PLUGIN_SFTOCF=True -e PLUGIN_IFX=True -e NEO4JP=${{ secrets.NEO4JP }} -e PLUGIN_FASRC=True -e PLUGIN_LDAP=True -e AUTH_LDAP_SERVER_URI=${{ secrets.AUTH_LDAP_SERVER_URI }} -e AUTH_LDAP_BIND_DN=${{ secrets.AUTH_LDAP_BIND_DN }} -e AUTH_LDAP_BIND_PASSWORD=${{ secrets.AUTH_LDAP_BIND_PASSWORD }} -e AUTH_LDAP_GROUP_SEARCH_BASE=${{ secrets.AUTH_LDAP_GROUP_SEARCH_BASE }} -e AUTH_LDAP_USER_SEARCH_BASE=${{ secrets.AUTH_LDAP_USER_SEARCH_BASE }} -p 9000:80 coldfront
docker run -d --rm --name coldfront -v `pwd`:/usr/src/app \
-e PLUGIN_SFTOCF=True -e SFUSER=${{ secrets.SFUSER }} -e SFPASS=${{ secrets.SFPASS }} \
-e PLUGIN_IFX=True -e PLUGIN_FASRC=True -e NEO4JP=${{ secrets.NEO4JP }} \
-e PLUGIN_API=True -e PLUGIN_LDAP=True \
-e AUTH_LDAP_SERVER_URI=${{ secrets.AUTH_LDAP_SERVER_URI }} \
-e AUTH_LDAP_BIND_DN=${{ secrets.AUTH_LDAP_BIND_DN }} \
-e AUTH_LDAP_BIND_PASSWORD=${{ secrets.AUTH_LDAP_BIND_PASSWORD }} \
-e AUTH_LDAP_GROUP_SEARCH_BASE=${{ secrets.AUTH_LDAP_GROUP_SEARCH_BASE }} \
-e AUTH_LDAP_USER_SEARCH_BASE=${{ secrets.AUTH_LDAP_USER_SEARCH_BASE }} \
-p 9000:80 coldfront
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- name: Run Tests
Expand Down
2 changes: 1 addition & 1 deletion coldfront/config/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
'when': 'midnight',
'backupCount': 10,
'formatter': 'key-events',
'level': 'WARNING',
'level': 'INFO',
},
# 'file': {
# 'class': 'logging.FileHandler',
Expand Down
10 changes: 8 additions & 2 deletions coldfront/config/plugins/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from coldfront.config.base import INSTALLED_APPS

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

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
Expand All @@ -10,5 +13,8 @@
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
]
],
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend'
],
}
4 changes: 1 addition & 3 deletions coldfront/core/project/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def setUpTestData(cls):
attributetype = PAttributeTypeFactory(name='string')
cls.projectattributetype = ProjectAttributeTypeFactory(attribute_type=attributetype)# ProjectAttributeType.objects.get(pk=1)


def project_access_tstbase(self, url):
"""Test basic access control for project views. For all project views:
- if not logged in, redirect to login page
Expand Down Expand Up @@ -74,6 +73,7 @@ def test_archived_projectlist(self):
url = '/project/archived/'#?show_all_projects=True&'
utils.page_contains_for_user(self, self.pi_user, url, self.project.title)


class ProjectDetailViewTest(ProjectViewTestBase):
"""tests for ProjectDetailView"""

Expand Down Expand Up @@ -126,7 +126,6 @@ def test_projectdetail_edituser_button_visibility(self):

utils.page_does_not_contain_for_user(self, self.project_user, self.url, 'fa-user-edit') # non-manager user cannot see edit button


# def test_projectdetail_addattribute_button_visibility(self):
# """Test visibility of project detail add attribute button to different projectuser levels"""
# utils.page_contains_for_user(self, self.admin_user, self.url, 'Add Attribute') # admin can see add attribute button
Expand Down Expand Up @@ -289,7 +288,6 @@ def test_project_list_access(self):
utils.test_user_can_access(self, self.project_user, self.url)
utils.test_user_can_access(self, self.nonproject_user, self.url)


### ProjectListView display tests ###

def test_project_list_display_members(self):
Expand Down
4 changes: 3 additions & 1 deletion coldfront/core/project/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,9 @@ def post(self, request, *args, **kwargs):
)
errors.append(error)
continue
successes.append(f"User {user_obj} added to AD Group for {project_obj.title}")
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 Down
Empty file.
149 changes: 143 additions & 6 deletions coldfront/plugins/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,61 @@
from rest_framework import serializers
from datetime import timedelta

from django.contrib.auth import get_user_model
from ifxuser.models import Organization, UserAffiliation
from rest_framework import serializers

from coldfront.core.resource.models import Resource
from coldfront.core.project.models import Project, ProjectUser
from coldfront.core.allocation.models import Allocation, AllocationUser
from coldfront.core.allocation.models import Allocation, AllocationChangeRequest
from coldfront.plugins.ifx.models import ProjectOrganization


class UserAffiliationSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(slug_field='full_name', read_only=True)
organization = serializers.SlugRelatedField(slug_field='ifxorg', read_only=True)

class Meta:
model = UserAffiliation
fields = (
'organization',
'user',
'role',
'active',
)


class OrganizationSerializer(serializers.ModelSerializer):
project = serializers.CharField(read_only=True)

class Meta:
model = Organization
fields = (
'ifxorg',
'name',
'rank',
'org_tree',
'project'
)


class UserSerializer(serializers.ModelSerializer):
primary_affiliation = serializers.SlugRelatedField(slug_field='name', read_only=True)
affiliations = UserAffiliationSerializer(source='useraffiliation_set', many=True, read_only=True)

class Meta:
model = get_user_model()
fields = ('id', 'full_name')
fields = (
'id',
'username',
'full_name',
'is_active',
'is_superuser',
'is_staff',
'date_joined',
'last_update',
'primary_affiliation',
'affiliations',
)


class ResourceSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -52,6 +97,86 @@ class Meta:
)


class AllocationRequestSerializer(serializers.ModelSerializer):
project = serializers.SlugRelatedField(slug_field='title', read_only=True)
resource = serializers.SlugRelatedField(slug_field='name', read_only=True)
status = serializers.SlugRelatedField(slug_field='name', read_only=True)
fulfilled_date = serializers.DateTimeField(read_only=True)
created_by = serializers.SerializerMethodField(read_only=True)
fulfilled_by = serializers.SerializerMethodField(read_only=True)
time_to_fulfillment = serializers.DurationField(read_only=True)

class Meta:
model = Allocation
fields = (
'id',
'project',
'resource',
'path',
'status',
'size',
'created',
'created_by',
'fulfilled_date',
'fulfilled_by',
'time_to_fulfillment',
)

def get_created_by(self, obj):
historical_record = obj.history.earliest()
creator = historical_record.history_user if historical_record else None
if not creator:
return None
return historical_record.history_user.username

def get_fulfilled_by(self, obj):
historical_records = obj.history.filter(status__name='Active')
if historical_records:
user = historical_records.earliest().history_user
if user:
return user.username
return None


class AllocationChangeRequestSerializer(serializers.ModelSerializer):
allocation = AllocationSerializer(read_only=True)
status = serializers.SlugRelatedField(slug_field='name', read_only=True)
created_by = serializers.SerializerMethodField(read_only=True)
fulfilled_date = serializers.DateTimeField(read_only=True)
fulfilled_by = serializers.SerializerMethodField(read_only=True)
time_to_fulfillment = serializers.DurationField(read_only=True)

class Meta:
model = AllocationChangeRequest
fields = (
'id',
'allocation',
'justification',
'status',
'created',
'created_by',
'fulfilled_date',
'fulfilled_by',
'time_to_fulfillment',
)

def get_created_by(self, obj):
historical_record = obj.history.earliest()
creator = historical_record.history_user if historical_record else None
if not creator:
return None
return historical_record.history_user.username

def get_fulfilled_by(self, obj):
if not obj.status.name == 'Approved':
return None
historical_record = obj.history.latest()
fulfiller = historical_record.history_user if historical_record else None
if not fulfiller:
return None
return historical_record.history_user.username


class ProjAllocationSerializer(serializers.ModelSerializer):
resource = serializers.ReadOnlyField(source='get_resources_as_string')
status = serializers.SlugRelatedField(slug_field='name', read_only=True)
Expand All @@ -75,9 +200,21 @@ class Meta:
class ProjectSerializer(serializers.ModelSerializer):
pi = serializers.SlugRelatedField(slug_field='full_name', read_only=True)
status = serializers.SlugRelatedField(slug_field='name', read_only=True)
users = ProjectUserSerializer(source='projectuser_set', many=True, read_only=True)
allocations = ProjAllocationSerializer(source='allocation_set', many=True, read_only=True)
project_users = serializers.SerializerMethodField()
allocations = serializers.SerializerMethodField()

class Meta:
model = Project
fields = ('id', 'title', 'pi', 'status', 'users', 'allocations')
fields = ('id', 'title', 'pi', 'status', 'project_users', 'allocations')

def get_project_users(self, obj):
request = self.context.get('request', None)
if request and request.query_params.get('project_users') == 'true':
return ProjectUserSerializer(obj.projectuser_set, many=True, read_only=True).data
return None

def get_allocations(self, obj):
request = self.context.get('request', None)
if request and request.query_params.get('allocations') == 'true':
return ProjAllocationSerializer(obj.allocation_set, many=True, read_only=True).data
return None
26 changes: 25 additions & 1 deletion coldfront/plugins/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ def setUpTestData(cls):
def test_requires_login(self):
"""Test that the API requires authentication"""
response = self.client.get('/api/')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_allocation_request_api_permissions(self):
"""Test that accessing the allocation-request API view as an admin returns all
allocations, and that accessing it as a user is forbidden"""
# login as admin
self.client.force_login(self.admin_user)
response = self.client.get('/api/allocation-requests/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

self.client.force_login(self.pi_user)
response = self.client.get('/api/allocation-requests/', format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_allocation_api_permissions(self):
Expand All @@ -49,9 +61,21 @@ def test_project_api_permissions(self):
self.client.force_login(self.admin_user)
response = self.client.get('/api/projects/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), Allocation.objects.all().count())
self.assertEqual(len(response.data), Project.objects.all().count())

self.client.force_login(self.pi_user)
response = self.client.get('/api/projects/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)

def test_user_api_permissions(self):
"""Test that accessing the user API view as an admin returns all
allocations, and that accessing it as a user is forbidden"""
# login as admin
self.client.force_login(self.admin_user)
response = self.client.get('/api/users/', format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

self.client.force_login(self.pi_user)
response = self.client.get('/api/users/', format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
6 changes: 5 additions & 1 deletion coldfront/plugins/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

router = routers.DefaultRouter()
router.register(r'allocations', views.AllocationViewSet, basename='allocations')
router.register(r'resources', views.ResourceViewSet, basename='resources')
router.register(r'allocation-requests', views.AllocationRequestViewSet, basename='allocation-requests')
router.register(r'allocation-change-requests', views.AllocationChangeRequestViewSet, basename='allocation-change-requests')
router.register(r'organizations', views.OrganizationViewSet, basename='organizations')
router.register(r'projects', views.ProjectViewSet, basename='projects')
router.register(r'resources', views.ResourceViewSet, basename='resources')
router.register(r'users', views.UserViewSet, basename='users')

urlpatterns = [
path('', include(router.urls)),
Expand Down
Loading

0 comments on commit a81c55a

Please sign in to comment.